commit a7d0bdc3c04e2b79d4bee43184775830eaa5eca3 Author: zhouzhongping Date: Sat Apr 25 22:20:29 2020 +0800 1.1.38 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f08e783 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea +composer.lock +*.log + +*.css.map +.sass-cache diff --git a/README.md b/README.md new file mode 100644 index 0000000..291b2c3 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# 萤火小程序商城-商业版(YoShop) + +#### 项目介绍 + +萤火小程序商城,是一款开源的电商系统,为中小企业提供最佳的新零售解决方案。采用稳定的MVC框架开发,执行效率、扩展性、稳定性值得信赖。帮助商家高效快捷的打造一款功能完善的购物小程序,速度快,无需下载安装,共享微信 8亿用户流量,开启新零售! + +#### 项目官网 + +官网地址:https://www.yiovo.com/ + +## 更新日志 + +### v1.1.38 + +修复:订单确认页收货地址提示 +修复:后台拼团商品列表状态筛选 +修复:秒杀订单并发库存问题 +修复:后台编辑门店后坐标不准确 +新增:后台分销中心编辑分销商用户 +优化:删除分销商清空推荐关系 +优化:删除用户时删除上级推荐关系 + +## 系统使用要求 + +## 小程序要求 + +``` +申请微信小程序 +通过微信认证 +申请微信支付商户 +``` + +## 服务器最低要求 + +``` +1核CPU +2G内存 +2M带宽 +``` + +## 系统环境要求 + +``` +PHP版本 >= 5.4 (推荐PHP7.1版本) +MySql版本 >= 5.5 (需支持innodb引擎) +Apache 或 Nginx +服务器支持https +``` \ No newline at end of file diff --git a/doc/database/install.sql b/doc/database/install.sql new file mode 100644 index 0000000..d081295 --- /dev/null +++ b/doc/database/install.sql @@ -0,0 +1,5598 @@ + +SET FOREIGN_KEY_CHECKS=0; + + +CREATE TABLE `yoshop_category` ( + `category_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品分类id', + `name` varchar(50) NOT NULL DEFAULT '' COMMENT '分类名称', + `parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '上级分类id', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分类图片id', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序方式(数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品分类表'; + + +CREATE TABLE `yoshop_comment` ( + `comment_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '评价id', + `score` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '评分 (10好评 20中评 30差评)', + `content` text NOT NULL COMMENT '评价内容', + `is_picture` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否为图片评价', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '评价排序', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0隐藏 1显示)', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `order_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单商品id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `is_delete` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '软删除', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`comment_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='订单评价记录表'; + + +CREATE TABLE `yoshop_comment_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `comment_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '评价id', + `image_id` int(11) NOT NULL DEFAULT '0' COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='订单评价图片记录表'; + + +CREATE TABLE `yoshop_coupon` ( + `coupon_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '优惠券id', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT '优惠券名称', + `color` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '优惠券颜色(10蓝 20红 30紫 40黄)', + `coupon_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '优惠券类型(10满减券 20折扣券)', + `reduce_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '满减券-减免金额', + `discount` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '折扣券-折扣率(0-100)', + `min_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '最低消费金额', + `expire_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '到期类型(10领取后生效 20固定时间)', + `expire_day` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '领取后生效-有效天数', + `start_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '固定时间-开始时间', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '固定时间-结束时间', + `apply_range` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '适用范围(10全部商品 20指定商品)', + `total_num` int(11) NOT NULL DEFAULT '0' COMMENT '发放总数量(-1为不限制)', + `receive_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '已领取数量', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序方式(数字越小越靠前)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '软删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`coupon_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='优惠券记录表'; + + +CREATE TABLE `yoshop_coupon_goods` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `coupon_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '优惠券id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='优惠券指定商品记录表'; + + +CREATE TABLE `yoshop_delivery` ( + `delivery_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '模板id', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT '模板名称', + `method` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '计费方式(10按件数 20按重量)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序d', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序方式(数字越小越靠前)', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`delivery_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10002 DEFAULT CHARSET=utf8 COMMENT='配送模板主表'; + +INSERT INTO `yoshop_delivery` VALUES ('10001', '全国包邮', '10', '10001', '100', '1535083514', '1535083514'); + + +CREATE TABLE `yoshop_delivery_rule` ( + `rule_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '规则id', + `delivery_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '配送模板id', + `region` text NOT NULL COMMENT '可配送区域(城市id集)', + `first` double unsigned NOT NULL DEFAULT '0' COMMENT '首件(个)/首重(Kg)', + `first_fee` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '运费(元)', + `additional` double unsigned NOT NULL DEFAULT '0' COMMENT '续件/续重', + `additional_fee` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '续费(元)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL COMMENT '创建时间', + PRIMARY KEY (`rule_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10002 DEFAULT CHARSET=utf8 COMMENT='配送模板区域及运费表'; + +INSERT INTO `yoshop_delivery_rule` VALUES ('10001', '10001', '2,20,38,61,76,84,104,124,150,168,180,197,208,221,232,244,250,264,271,278,290,304,319,337,352,362,372,376,389,398,407,422,430,442,449,462,467,481,492,500,508,515,522,530,537,545,553,558,566,574,581,586,597,607,614,619,627,634,640,646,656,675,692,702,711,720,730,748,759,764,775,782,793,802,821,833,842,853,861,871,880,887,896,906,913,920,927,934,948,960,972,980,986,993,1003,1010,1015,1025,1035,1047,1057,1066,1074,1081,1088,1093,1098,1110,1118,1127,1136,1142,1150,1155,1160,1169,1183,1190,1196,1209,1222,1234,1245,1253,1264,1274,1279,1285,1299,1302,1306,1325,1339,1350,1362,1376,1387,1399,1408,1415,1421,1434,1447,1459,1466,1471,1476,1479,1492,1504,1513,1522,1533,1546,1556,1572,1583,1593,1599,1612,1623,1630,1637,1643,1650,1664,1674,1685,1696,1707,1710,1724,1731,1740,1754,1764,1768,1774,1782,1791,1802,1809,1813,1822,1828,1838,1848,1854,1867,1880,1890,1900,1905,1912,1924,1936,1949,1955,1965,1977,1988,1999,2003,2011,2017,2025,2035,2041,2050,2056,2065,2070,2077,2082,2091,2123,2146,2150,2156,2163,2177,2189,2207,2215,2220,2225,2230,2236,2245,2258,2264,2276,2283,2292,2297,2302,2306,2324,2363,2368,2388,2395,2401,2409,2416,2426,2434,2440,2446,2458,2468,2475,2486,2493,2501,2510,2516,2521,2535,2554,2573,2584,2589,2604,2611,2620,2631,2640,2657,2671,2686,2696,2706,2712,2724,2730,2741,2750,2761,2775,2784,2788,2801,2807,2812,2817,2826,2845,2857,2870,2882,2890,2899,2913,2918,2931,2946,2958,2972,2984,2997,3008,3016,3023,3032,3036,3039,3045,3053,3058,3065,3073,3081,3090,3098,3108,3117,3127,3135,3142,3147,3152,3158,3165,3172,3179,3186,3190,3196,3202,3207,3216,3221,3225,3229,3237,3242,3252,3262,3267,3280,3289,3301,3309,3317,3326,3339,3378,3386,3416,3454,3458,3461,3491,3504,3518,3532,3551,3578,3592,3613,3632,3666,3683,3697,3704,3711,3739,3745,3747,3999', '1', '0.00', '0', '0.00', '10001', '1560214092'); + + +CREATE TABLE `yoshop_express` ( + `express_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '物流公司id', + `express_name` varchar(255) NOT NULL DEFAULT '' COMMENT '物流公司名称', + `express_code` varchar(30) NOT NULL DEFAULT '' COMMENT '物流公司代码 (快递100)', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序 (数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`express_id`), + KEY `express_code` (`express_code`) +) ENGINE=InnoDB AUTO_INCREMENT=10010 DEFAULT CHARSET=utf8 COMMENT='物流公司记录表'; + +INSERT INTO `yoshop_express` VALUES ('10001', '顺丰速运', 'shunfeng', '100', '10001', '1535160797', '1535160797'); +INSERT INTO `yoshop_express` VALUES ('10002', '邮政国内', 'yzguonei', '100', '10001', '1535942653', '1535942653'); +INSERT INTO `yoshop_express` VALUES ('10003', '圆通速递', 'yuantong', '100', '10001', '1535942675', '1535942675'); +INSERT INTO `yoshop_express` VALUES ('10004', '申通快递', 'shentong', '100', '10001', '1535942694', '1535942694'); +INSERT INTO `yoshop_express` VALUES ('10005', '韵达快递', 'yunda', '100', '10001', '1535942711', '1535942711'); +INSERT INTO `yoshop_express` VALUES ('10006', '百世快递', 'huitongkuaidi', '100', '10001', '1535942743', '1535942743'); +INSERT INTO `yoshop_express` VALUES ('10007', '中通快递', 'zhongtong', '100', '10001', '1535942764', '1535942764'); +INSERT INTO `yoshop_express` VALUES ('10008', '天天快递', 'tiantian', '100', '10001', '1535942783', '1535942783'); +INSERT INTO `yoshop_express` VALUES ('10009', '宅急送', 'zhaijisong', '100', '10001', '1535942798', '1535942798'); + + +CREATE TABLE `yoshop_goods` ( + `goods_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品id', + `goods_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称', + `selling_point` varchar(500) NOT NULL DEFAULT '' COMMENT '商品卖点', + `category_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品分类id', + `spec_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商品规格(10单规格 20多规格)', + `deduct_stock_type` tinyint(3) unsigned NOT NULL DEFAULT '20' COMMENT '库存计算方式(10下单减库存 20付款减库存)', + `content` longtext NOT NULL COMMENT '商品详情', + `sales_initial` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '初始销量', + `sales_actual` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '实际销量', + `goods_sort` int(11) unsigned NOT NULL DEFAULT '100' COMMENT '商品排序(数字越小越靠前)', + `delivery_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '配送模板id', + `is_points_gift` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否开启积分赠送(1开启 0关闭)', + `is_points_discount` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否允许使用积分抵扣(1允许 0不允许)', + `is_enable_grade` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否开启会员折扣(1开启 0关闭)', + `is_alone_grade` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '会员折扣设置(0默认等级折扣 1单独设置折扣)', + `alone_grade_equity` text COMMENT '单独设置折扣的配置', + `is_ind_dealer` TINYINT (3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启单独分销(0关闭 1开启)', + `dealer_money_type` TINYINT (3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '分销佣金类型(10百分比 20固定金额)', + `first_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)', + `second_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)', + `third_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)', + `goods_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '商品状态(10上架 20下架)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`goods_id`), + KEY `category_id` (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品记录表'; + + +CREATE TABLE `yoshop_goods_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `image_id` int(11) NOT NULL COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品图片记录表'; + + +CREATE TABLE `yoshop_goods_sku` ( + `goods_sku_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品规格id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '0' COMMENT '商品sku记录索引 (由规格id组成)', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格图片id', + `goods_no` varchar(100) NOT NULL DEFAULT '' COMMENT '商品编码', + `goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品价格', + `line_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品划线价', + `stock_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '当前库存数量', + `goods_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品销量', + `goods_weight` double unsigned NOT NULL DEFAULT '0' COMMENT '商品重量(Kg)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`goods_sku_id`), + UNIQUE KEY `sku_idx` (`goods_id`,`spec_sku_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品规格表'; + + +CREATE TABLE `yoshop_goods_spec_rel` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `spec_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格组id', + `spec_value_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格值id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品与规格值关系记录表'; + + +CREATE TABLE `yoshop_order` ( + `order_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单id', + `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号', + `total_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品总金额(不含优惠折扣)', + `order_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '订单金额(含优惠折扣)', + `coupon_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '优惠券id', + `coupon_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '优惠券抵扣金额', + `points_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '积分抵扣金额', + `points_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '积分抵扣数量', + `pay_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际付款金额(包含运费)', + `update_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '后台修改的订单金额(差价)', + `buyer_remark` varchar(255) NOT NULL DEFAULT '' COMMENT '买家留言', + `pay_type` tinyint(3) unsigned NOT NULL DEFAULT '20' COMMENT '支付方式(10余额支付 20微信支付)', + `pay_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '付款状态(10未付款 20已付款)', + `pay_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '付款时间', + `delivery_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '配送方式(10快递配送 20上门自提)', + `extract_shop_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '自提门店id', + `extract_clerk_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '核销店员id', + `express_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '运费金额', + `express_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '物流公司id', + `express_company` varchar(50) NOT NULL DEFAULT '' COMMENT '物流公司', + `express_no` varchar(50) NOT NULL DEFAULT '' COMMENT '物流单号', + `delivery_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '发货状态(10未发货 20已发货)', + `delivery_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '发货时间', + `receipt_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '收货状态(10未收货 20已收货)', + `receipt_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '收货时间', + `order_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '订单状态(10进行中 20取消 21待取消 30已完成)', + `points_bonus` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '赠送的积分数量', + `is_settled` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '订单是否已结算(0未结算 1已结算)', + `transaction_id` varchar(30) NOT NULL DEFAULT '' COMMENT '微信支付交易号', + `is_comment` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否已评价(0否 1是)', + `order_source` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '订单来源(10普通订单 20砍价订单)', + `order_source_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '来源记录id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_id`), + UNIQUE KEY `order_no` (`order_no`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='订单记录表'; + + +CREATE TABLE `yoshop_order_address` ( + `order_address_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '地址id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `province_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在省份id', + `city_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在城市id', + `region_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在区id', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`order_address_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='订单收货地址记录表'; + + +CREATE TABLE `yoshop_order_goods` ( + `order_goods_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `goods_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品封面图id', + `deduct_stock_type` tinyint(3) unsigned NOT NULL DEFAULT '20' COMMENT '库存计算方式(10下单减库存 20付款减库存)', + `spec_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '规格类型(10单规格 20多规格)', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '' COMMENT '商品sku标识', + `goods_sku_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品规格id', + `goods_attr` varchar(500) NOT NULL DEFAULT '' COMMENT '商品规格信息', + `content` longtext NOT NULL COMMENT '商品详情', + `goods_no` varchar(100) NOT NULL DEFAULT '' COMMENT '商品编码', + `goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品价格(单价)', + `line_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品划线价', + `goods_weight` double unsigned NOT NULL DEFAULT '0' COMMENT '商品重量(Kg)', + `is_user_grade` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否存在会员等级折扣', + `grade_ratio` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '会员折扣比例(0-10)', + `grade_goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '会员折扣的商品单价', + `grade_total_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '会员折扣的总额差', + `coupon_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '优惠券折扣金额', + `points_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '积分金额', + `points_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '积分抵扣数量', + `points_bonus` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '赠送的积分数量', + `total_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '购买数量', + `total_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品总价(数量×单价)', + `total_pay_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际付款价(折扣和优惠后)', + `is_ind_dealer` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否开启单独分销(0关闭 1开启)', + `dealer_money_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '分销佣金类型(10百分比 20固定金额)', + `first_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)', + `second_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)', + `third_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)', + `is_comment` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否已评价(0否 1是)', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`order_goods_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='订单商品记录表'; + + +CREATE TABLE `yoshop_region` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `pid` int(11) DEFAULT NULL COMMENT '父id', + `shortname` varchar(100) DEFAULT NULL COMMENT '简称', + `name` varchar(100) DEFAULT NULL COMMENT '名称', + `merger_name` varchar(255) DEFAULT NULL COMMENT '全称', + `level` tinyint(4) unsigned DEFAULT '0' COMMENT '层级 1 2 3 省市区县', + `pinyin` varchar(100) DEFAULT NULL COMMENT '拼音', + `code` varchar(100) DEFAULT NULL COMMENT '长途区号', + `zip_code` varchar(100) DEFAULT NULL COMMENT '邮编', + `first` varchar(50) DEFAULT NULL COMMENT '首字母', + `lng` varchar(100) DEFAULT NULL COMMENT '经度', + `lat` varchar(100) DEFAULT NULL COMMENT '纬度', + PRIMARY KEY (`id`), + KEY `name,level` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=50001 DEFAULT CHARSET=utf8; + +INSERT INTO `yoshop_region` VALUES ('1', '0', '北京', '北京市', '中国,北京', '1', 'beijing', '', '', 'B', '116.405285', '39.904989'); +INSERT INTO `yoshop_region` VALUES ('2', '1', '北京', '北京市', '中国,北京,北京市', '2', 'beijing', '010', '100000', 'B', '116.405285', '39.904989'); +INSERT INTO `yoshop_region` VALUES ('3', '2', '东城', '东城区', '中国,北京,北京市,东城区', '3', 'dongcheng', '010', '100010', 'D', '116.41005', '39.93157'); +INSERT INTO `yoshop_region` VALUES ('4', '2', '西城', '西城区', '中国,北京,北京市,西城区', '3', 'xicheng', '010', '100032', 'X', '116.36003', '39.9305'); +INSERT INTO `yoshop_region` VALUES ('5', '2', '朝阳', '朝阳区', '中国,北京,北京市,朝阳区', '3', 'chaoyang', '010', '100020', 'C', '116.48548', '39.9484'); +INSERT INTO `yoshop_region` VALUES ('6', '2', '丰台', '丰台区', '中国,北京,北京市,丰台区', '3', 'fengtai', '010', '100071', 'F', '116.28625', '39.8585'); +INSERT INTO `yoshop_region` VALUES ('7', '2', '石景山', '石景山区', '中国,北京,北京市,石景山区', '3', 'shijingshan', '010', '100043', 'S', '116.2229', '39.90564'); +INSERT INTO `yoshop_region` VALUES ('8', '2', '海淀', '海淀区', '中国,北京,北京市,海淀区', '3', 'haidian', '010', '100089', 'H', '116.29812', '39.95931'); +INSERT INTO `yoshop_region` VALUES ('9', '2', '门头沟', '门头沟区', '中国,北京,北京市,门头沟区', '3', 'mentougou', '010', '102300', 'M', '116.10137', '39.94043'); +INSERT INTO `yoshop_region` VALUES ('10', '2', '房山', '房山区', '中国,北京,北京市,房山区', '3', 'fangshan', '010', '102488', 'F', '116.14257', '39.74786'); +INSERT INTO `yoshop_region` VALUES ('11', '2', '通州', '通州区', '中国,北京,北京市,通州区', '3', 'tongzhou', '010', '101149', 'T', '116.65716', '39.90966'); +INSERT INTO `yoshop_region` VALUES ('12', '2', '顺义', '顺义区', '中国,北京,北京市,顺义区', '3', 'shunyi', '010', '101300', 'S', '116.65417', '40.1302'); +INSERT INTO `yoshop_region` VALUES ('13', '2', '昌平', '昌平区', '中国,北京,北京市,昌平区', '3', 'changping', '010', '102200', 'C', '116.2312', '40.22072'); +INSERT INTO `yoshop_region` VALUES ('14', '2', '大兴', '大兴区', '中国,北京,北京市,大兴区', '3', 'daxing', '010', '102600', 'D', '116.34149', '39.72668'); +INSERT INTO `yoshop_region` VALUES ('15', '2', '怀柔', '怀柔区', '中国,北京,北京市,怀柔区', '3', 'huairou', '010', '101400', 'H', '116.63168', '40.31602'); +INSERT INTO `yoshop_region` VALUES ('16', '2', '平谷', '平谷区', '中国,北京,北京市,平谷区', '3', 'pinggu', '010', '101200', 'P', '117.12133', '40.14056'); +INSERT INTO `yoshop_region` VALUES ('17', '2', '密云', '密云县', '中国,北京,北京市,密云县', '3', 'miyun', '010', '101500', 'M', '116.84295', '40.37618'); +INSERT INTO `yoshop_region` VALUES ('18', '2', '延庆', '延庆县', '中国,北京,北京市,延庆县', '3', 'yanqing', '010', '102100', 'Y', '115.97494', '40.45672'); +INSERT INTO `yoshop_region` VALUES ('19', '0', '天津', '天津市', '中国,天津', '1', 'tianjin', '', '', 'T', '117.190182', '39.125596'); +INSERT INTO `yoshop_region` VALUES ('20', '19', '天津', '天津市', '中国,天津,天津市', '2', 'tianjin', '022', '300000', 'T', '117.190182', '39.125596'); +INSERT INTO `yoshop_region` VALUES ('21', '20', '和平', '和平区', '中国,天津,天津市,和平区', '3', 'heping', '022', '300041', 'H', '117.21456', '39.11718'); +INSERT INTO `yoshop_region` VALUES ('22', '20', '河东', '河东区', '中国,天津,天津市,河东区', '3', 'hedong', '022', '300171', 'H', '117.22562', '39.12318'); +INSERT INTO `yoshop_region` VALUES ('23', '20', '河西', '河西区', '中国,天津,天津市,河西区', '3', 'hexi', '022', '300202', 'H', '117.22327', '39.10959'); +INSERT INTO `yoshop_region` VALUES ('24', '20', '南开', '南开区', '中国,天津,天津市,南开区', '3', 'nankai', '022', '300110', 'N', '117.15074', '39.13821'); +INSERT INTO `yoshop_region` VALUES ('25', '20', '河北', '河北区', '中国,天津,天津市,河北区', '3', 'hebei', '022', '300143', 'H', '117.19697', '39.14816'); +INSERT INTO `yoshop_region` VALUES ('26', '20', '红桥', '红桥区', '中国,天津,天津市,红桥区', '3', 'hongqiao', '022', '300131', 'H', '117.15145', '39.16715'); +INSERT INTO `yoshop_region` VALUES ('27', '20', '东丽', '东丽区', '中国,天津,天津市,东丽区', '3', 'dongli', '022', '300300', 'D', '117.31436', '39.0863'); +INSERT INTO `yoshop_region` VALUES ('28', '20', '西青', '西青区', '中国,天津,天津市,西青区', '3', 'xiqing', '022', '300380', 'X', '117.00927', '39.14123'); +INSERT INTO `yoshop_region` VALUES ('29', '20', '津南', '津南区', '中国,天津,天津市,津南区', '3', 'jinnan', '022', '300350', 'J', '117.38537', '38.99139'); +INSERT INTO `yoshop_region` VALUES ('30', '20', '北辰', '北辰区', '中国,天津,天津市,北辰区', '3', 'beichen', '022', '300400', 'B', '117.13217', '39.22131'); +INSERT INTO `yoshop_region` VALUES ('31', '20', '武清', '武清区', '中国,天津,天津市,武清区', '3', 'wuqing', '022', '301700', 'W', '117.04443', '39.38415'); +INSERT INTO `yoshop_region` VALUES ('32', '20', '宝坻', '宝坻区', '中国,天津,天津市,宝坻区', '3', 'baodi', '022', '301800', 'B', '117.3103', '39.71761'); +INSERT INTO `yoshop_region` VALUES ('33', '20', '滨海新区', '滨海新区', '中国,天津,天津市,滨海新区', '3', 'binhaixinqu', '022', '300451', 'B', '117.70162', '39.02668'); +INSERT INTO `yoshop_region` VALUES ('34', '20', '宁河', '宁河县', '中国,天津,天津市,宁河县', '3', 'ninghe', '022', '301500', 'N', '117.8255', '39.33048'); +INSERT INTO `yoshop_region` VALUES ('35', '20', '静海', '静海县', '中国,天津,天津市,静海县', '3', 'jinghai', '022', '301600', 'J', '116.97436', '38.94582'); +INSERT INTO `yoshop_region` VALUES ('36', '20', '蓟县', '蓟县', '中国,天津,天津市,蓟县', '3', 'jixian', '022', '301900', 'J', '117.40799', '40.04567'); +INSERT INTO `yoshop_region` VALUES ('37', '0', '河北', '河北省', '中国,河北省', '1', 'hebei', '', '', 'H', '114.502461', '38.045474'); +INSERT INTO `yoshop_region` VALUES ('38', '37', '石家庄', '石家庄市', '中国,河北省,石家庄市', '2', 'shijiazhuang', '0311', '050011', 'S', '114.502461', '38.045474'); +INSERT INTO `yoshop_region` VALUES ('39', '38', '长安', '长安区', '中国,河北省,石家庄市,长安区', '3', 'chang\'an', '0311', '050011', 'C', '114.53906', '38.03665'); +INSERT INTO `yoshop_region` VALUES ('40', '38', '桥西', '桥西区', '中国,河北省,石家庄市,桥西区', '3', 'qiaoxi', '0311', '050091', 'Q', '114.46977', '38.03221'); +INSERT INTO `yoshop_region` VALUES ('41', '38', '新华', '新华区', '中国,河北省,石家庄市,新华区', '3', 'xinhua', '0311', '050051', 'X', '114.46326', '38.05088'); +INSERT INTO `yoshop_region` VALUES ('42', '38', '井陉矿区', '井陉矿区', '中国,河北省,石家庄市,井陉矿区', '3', 'jingxingkuangqu', '0311', '050100', 'J', '114.06518', '38.06705'); +INSERT INTO `yoshop_region` VALUES ('43', '38', '裕华', '裕华区', '中国,河北省,石家庄市,裕华区', '3', 'yuhua', '0311', '050031', 'Y', '114.53115', '38.00604'); +INSERT INTO `yoshop_region` VALUES ('44', '38', '藁城', '藁城区', '中国,河北省,石家庄市,藁城区', '3', 'gaocheng', '0311', '052160', null, '114.84671', '38.02162'); +INSERT INTO `yoshop_region` VALUES ('45', '38', '鹿泉', '鹿泉区', '中国,河北省,石家庄市,鹿泉区', '3', 'luquan', '0311', '050200', 'L', '114.31347', '38.08782'); +INSERT INTO `yoshop_region` VALUES ('46', '38', '栾城', '栾城区', '中国,河北省,石家庄市,栾城区', '3', 'luancheng', '0311', '051430', null, '114.64834', '37.90022'); +INSERT INTO `yoshop_region` VALUES ('47', '38', '井陉', '井陉县', '中国,河北省,石家庄市,井陉县', '3', 'jingxing', '0311', '050300', 'J', '114.14257', '38.03688'); +INSERT INTO `yoshop_region` VALUES ('48', '38', '正定', '正定县', '中国,河北省,石家庄市,正定县', '3', 'zhengding', '0311', '050800', 'Z', '114.57296', '38.14445'); +INSERT INTO `yoshop_region` VALUES ('49', '38', '行唐', '行唐县', '中国,河北省,石家庄市,行唐县', '3', 'xingtang', '0311', '050600', 'X', '114.55316', '38.43654'); +INSERT INTO `yoshop_region` VALUES ('50', '38', '灵寿', '灵寿县', '中国,河北省,石家庄市,灵寿县', '3', 'lingshou', '0311', '050500', 'L', '114.38259', '38.30845'); +INSERT INTO `yoshop_region` VALUES ('51', '38', '高邑', '高邑县', '中国,河北省,石家庄市,高邑县', '3', 'gaoyi', '0311', '051330', 'G', '114.61142', '37.61556'); +INSERT INTO `yoshop_region` VALUES ('52', '38', '深泽', '深泽县', '中国,河北省,石家庄市,深泽县', '3', 'shenze', '0311', '052560', 'S', '115.20358', '38.18353'); +INSERT INTO `yoshop_region` VALUES ('53', '38', '赞皇', '赞皇县', '中国,河北省,石家庄市,赞皇县', '3', 'zanhuang', '0311', '051230', 'Z', '114.38775', '37.66135'); +INSERT INTO `yoshop_region` VALUES ('54', '38', '无极', '无极县', '中国,河北省,石家庄市,无极县', '3', 'wuji', '0311', '052460', 'W', '114.97509', '38.17653'); +INSERT INTO `yoshop_region` VALUES ('55', '38', '平山', '平山县', '中国,河北省,石家庄市,平山县', '3', 'pingshan', '0311', '050400', 'P', '114.186', '38.25994'); +INSERT INTO `yoshop_region` VALUES ('56', '38', '元氏', '元氏县', '中国,河北省,石家庄市,元氏县', '3', 'yuanshi', '0311', '051130', 'Y', '114.52539', '37.76668'); +INSERT INTO `yoshop_region` VALUES ('57', '38', '赵县', '赵县', '中国,河北省,石家庄市,赵县', '3', 'zhaoxian', '0311', '051530', 'Z', '114.77612', '37.75628'); +INSERT INTO `yoshop_region` VALUES ('58', '38', '辛集', '辛集市', '中国,河北省,石家庄市,辛集市', '3', 'xinji', '0311', '052360', 'X', '115.20626', '37.94079'); +INSERT INTO `yoshop_region` VALUES ('59', '38', '晋州', '晋州市', '中国,河北省,石家庄市,晋州市', '3', 'jinzhou', '0311', '052260', 'J', '115.04348', '38.03135'); +INSERT INTO `yoshop_region` VALUES ('60', '38', '新乐', '新乐市', '中国,河北省,石家庄市,新乐市', '3', 'xinle', '0311', '050700', 'X', '114.68985', '38.34417'); +INSERT INTO `yoshop_region` VALUES ('61', '37', '唐山', '唐山市', '中国,河北省,唐山市', '2', 'tangshan', '0315', '063000', 'T', '118.175393', '39.635113'); +INSERT INTO `yoshop_region` VALUES ('62', '61', '路南', '路南区', '中国,河北省,唐山市,路南区', '3', 'lunan', '0315', '063000', 'L', '118.15431', '39.62505'); +INSERT INTO `yoshop_region` VALUES ('63', '61', '路北', '路北区', '中国,河北省,唐山市,路北区', '3', 'lubei', '0315', '063000', 'L', '118.20079', '39.62436'); +INSERT INTO `yoshop_region` VALUES ('64', '61', '古冶', '古冶区', '中国,河北省,唐山市,古冶区', '3', 'guye', '0315', '063100', 'G', '118.45803', '39.71993'); +INSERT INTO `yoshop_region` VALUES ('65', '61', '开平', '开平区', '中国,河北省,唐山市,开平区', '3', 'kaiping', '0315', '063021', 'K', '118.26171', '39.67128'); +INSERT INTO `yoshop_region` VALUES ('66', '61', '丰南', '丰南区', '中国,河北省,唐山市,丰南区', '3', 'fengnan', '0315', '063300', 'F', '118.11282', '39.56483'); +INSERT INTO `yoshop_region` VALUES ('67', '61', '丰润', '丰润区', '中国,河北省,唐山市,丰润区', '3', 'fengrun', '0315', '064000', 'F', '118.12976', '39.8244'); +INSERT INTO `yoshop_region` VALUES ('68', '61', '曹妃甸', '曹妃甸区', '中国,河北省,唐山市,曹妃甸区', '3', 'caofeidian', '0315', '063200', 'C', '118.460379', '39.273070'); +INSERT INTO `yoshop_region` VALUES ('69', '61', '滦县', '滦县', '中国,河北省,唐山市,滦县', '3', 'luanxian', '0315', '063700', 'L', '118.70346', '39.74056'); +INSERT INTO `yoshop_region` VALUES ('70', '61', '滦南', '滦南县', '中国,河北省,唐山市,滦南县', '3', 'luannan', '0315', '063500', 'L', '118.6741', '39.5039'); +INSERT INTO `yoshop_region` VALUES ('71', '61', '乐亭', '乐亭县', '中国,河北省,唐山市,乐亭县', '3', 'laoting', '0315', '063600', 'L', '118.9125', '39.42561'); +INSERT INTO `yoshop_region` VALUES ('72', '61', '迁西', '迁西县', '中国,河北省,唐山市,迁西县', '3', 'qianxi', '0315', '064300', 'Q', '118.31616', '40.14587'); +INSERT INTO `yoshop_region` VALUES ('73', '61', '玉田', '玉田县', '中国,河北省,唐山市,玉田县', '3', 'yutian', '0315', '064100', 'Y', '117.7388', '39.90049'); +INSERT INTO `yoshop_region` VALUES ('74', '61', '遵化', '遵化市', '中国,河北省,唐山市,遵化市', '3', 'zunhua', '0315', '064200', 'Z', '117.96444', '40.18741'); +INSERT INTO `yoshop_region` VALUES ('75', '61', '迁安', '迁安市', '中国,河北省,唐山市,迁安市', '3', 'qian\'an', '0315', '064400', 'Q', '118.70068', '39.99833'); +INSERT INTO `yoshop_region` VALUES ('76', '37', '秦皇岛', '秦皇岛市', '中国,河北省,秦皇岛市', '2', 'qinhuangdao', '0335', '066000', 'Q', '119.586579', '39.942531'); +INSERT INTO `yoshop_region` VALUES ('77', '76', '海港', '海港区', '中国,河北省,秦皇岛市,海港区', '3', 'haigang', '0335', '066000', 'H', '119.61046', '39.9345'); +INSERT INTO `yoshop_region` VALUES ('78', '76', '山海关', '山海关区', '中国,河北省,秦皇岛市,山海关区', '3', 'shanhaiguan', '0335', '066200', 'S', '119.77563', '39.97869'); +INSERT INTO `yoshop_region` VALUES ('79', '76', '北戴河', '北戴河区', '中国,河北省,秦皇岛市,北戴河区', '3', 'beidaihe', '0335', '066100', 'B', '119.48388', '39.83408'); +INSERT INTO `yoshop_region` VALUES ('80', '76', '青龙', '青龙满族自治县', '中国,河北省,秦皇岛市,青龙满族自治县', '3', 'qinglong', '0335', '066500', 'Q', '118.95242', '40.40743'); +INSERT INTO `yoshop_region` VALUES ('81', '76', '昌黎', '昌黎县', '中国,河北省,秦皇岛市,昌黎县', '3', 'changli', '0335', '066600', 'C', '119.16595', '39.70884'); +INSERT INTO `yoshop_region` VALUES ('82', '76', '抚宁', '抚宁县', '中国,河北省,秦皇岛市,抚宁县', '3', 'funing', '0335', '066300', 'F', '119.24487', '39.87538'); +INSERT INTO `yoshop_region` VALUES ('83', '76', '卢龙', '卢龙县', '中国,河北省,秦皇岛市,卢龙县', '3', 'lulong', '0335', '066400', 'L', '118.89288', '39.89176'); +INSERT INTO `yoshop_region` VALUES ('84', '37', '邯郸', '邯郸市', '中国,河北省,邯郸市', '2', 'handan', '0310', '056002', 'H', '114.490686', '36.612273'); +INSERT INTO `yoshop_region` VALUES ('85', '84', '邯山', '邯山区', '中国,河北省,邯郸市,邯山区', '3', 'hanshan', '0310', '056001', 'H', '114.48375', '36.60006'); +INSERT INTO `yoshop_region` VALUES ('86', '84', '丛台', '丛台区', '中国,河北省,邯郸市,丛台区', '3', 'congtai', '0310', '056002', 'C', '114.49343', '36.61847'); +INSERT INTO `yoshop_region` VALUES ('87', '84', '复兴', '复兴区', '中国,河北省,邯郸市,复兴区', '3', 'fuxing', '0310', '056003', 'F', '114.45928', '36.61134'); +INSERT INTO `yoshop_region` VALUES ('88', '84', '峰峰矿区', '峰峰矿区', '中国,河北省,邯郸市,峰峰矿区', '3', 'fengfengkuangqu', '0310', '056200', 'F', '114.21148', '36.41937'); +INSERT INTO `yoshop_region` VALUES ('89', '84', '邯郸', '邯郸县', '中国,河北省,邯郸市,邯郸县', '3', 'handan', '0310', '056101', 'H', '114.53103', '36.59385'); +INSERT INTO `yoshop_region` VALUES ('90', '84', '临漳', '临漳县', '中国,河北省,邯郸市,临漳县', '3', 'linzhang', '0310', '056600', 'L', '114.6195', '36.33461'); +INSERT INTO `yoshop_region` VALUES ('91', '84', '成安', '成安县', '中国,河北省,邯郸市,成安县', '3', 'cheng\'an', '0310', '056700', 'C', '114.66995', '36.44411'); +INSERT INTO `yoshop_region` VALUES ('92', '84', '大名', '大名县', '中国,河北省,邯郸市,大名县', '3', 'daming', '0310', '056900', 'D', '115.15362', '36.27994'); +INSERT INTO `yoshop_region` VALUES ('93', '84', '涉县', '涉县', '中国,河北省,邯郸市,涉县', '3', 'shexian', '0310', '056400', 'S', '113.69183', '36.58072'); +INSERT INTO `yoshop_region` VALUES ('94', '84', '磁县', '磁县', '中国,河北省,邯郸市,磁县', '3', 'cixian', '0310', '056500', 'C', '114.37387', '36.37392'); +INSERT INTO `yoshop_region` VALUES ('95', '84', '肥乡', '肥乡县', '中国,河北省,邯郸市,肥乡县', '3', 'feixiang', '0310', '057550', 'F', '114.79998', '36.54807'); +INSERT INTO `yoshop_region` VALUES ('96', '84', '永年', '永年县', '中国,河北省,邯郸市,永年县', '3', 'yongnian', '0310', '057150', 'Y', '114.48925', '36.78356'); +INSERT INTO `yoshop_region` VALUES ('97', '84', '邱县', '邱县', '中国,河北省,邯郸市,邱县', '3', 'qiuxian', '0310', '057450', 'Q', '115.17407', '36.82082'); +INSERT INTO `yoshop_region` VALUES ('98', '84', '鸡泽', '鸡泽县', '中国,河北省,邯郸市,鸡泽县', '3', 'jize', '0310', '057350', 'J', '114.8742', '36.92374'); +INSERT INTO `yoshop_region` VALUES ('99', '84', '广平', '广平县', '中国,河北省,邯郸市,广平县', '3', 'guangping', '0310', '057650', 'G', '114.94653', '36.48046'); +INSERT INTO `yoshop_region` VALUES ('100', '84', '馆陶', '馆陶县', '中国,河北省,邯郸市,馆陶县', '3', 'guantao', '0310', '057750', 'G', '115.29913', '36.53719'); +INSERT INTO `yoshop_region` VALUES ('101', '84', '魏县', '魏县', '中国,河北省,邯郸市,魏县', '3', 'weixian', '0310', '056800', 'W', '114.93518', '36.36171'); +INSERT INTO `yoshop_region` VALUES ('102', '84', '曲周', '曲周县', '中国,河北省,邯郸市,曲周县', '3', 'quzhou', '0310', '057250', 'Q', '114.95196', '36.77671'); +INSERT INTO `yoshop_region` VALUES ('103', '84', '武安', '武安市', '中国,河北省,邯郸市,武安市', '3', 'wu\'an', '0310', '056300', 'W', '114.20153', '36.69281'); +INSERT INTO `yoshop_region` VALUES ('104', '37', '邢台', '邢台市', '中国,河北省,邢台市', '2', 'xingtai', '0319', '054001', 'X', '114.508851', '37.0682'); +INSERT INTO `yoshop_region` VALUES ('105', '104', '桥东', '桥东区', '中国,河北省,邢台市,桥东区', '3', 'qiaodong', '0319', '054001', 'Q', '114.50725', '37.06801'); +INSERT INTO `yoshop_region` VALUES ('106', '104', '桥西', '桥西区', '中国,河北省,邢台市,桥西区', '3', 'qiaoxi', '0319', '054000', 'Q', '114.46803', '37.05984'); +INSERT INTO `yoshop_region` VALUES ('107', '104', '邢台', '邢台县', '中国,河北省,邢台市,邢台县', '3', 'xingtai', '0319', '054001', 'X', '114.56575', '37.0456'); +INSERT INTO `yoshop_region` VALUES ('108', '104', '临城', '临城县', '中国,河北省,邢台市,临城县', '3', 'lincheng', '0319', '054300', 'L', '114.50387', '37.43977'); +INSERT INTO `yoshop_region` VALUES ('109', '104', '内丘', '内丘县', '中国,河北省,邢台市,内丘县', '3', 'neiqiu', '0319', '054200', 'N', '114.51212', '37.28671'); +INSERT INTO `yoshop_region` VALUES ('110', '104', '柏乡', '柏乡县', '中国,河北省,邢台市,柏乡县', '3', 'baixiang', '0319', '055450', 'B', '114.69332', '37.48242'); +INSERT INTO `yoshop_region` VALUES ('111', '104', '隆尧', '隆尧县', '中国,河北省,邢台市,隆尧县', '3', 'longyao', '0319', '055350', 'L', '114.77615', '37.35351'); +INSERT INTO `yoshop_region` VALUES ('112', '104', '任县', '任县', '中国,河北省,邢台市,任县', '3', 'renxian', '0319', '055150', 'R', '114.6842', '37.12575'); +INSERT INTO `yoshop_region` VALUES ('113', '104', '南和', '南和县', '中国,河北省,邢台市,南和县', '3', 'nanhe', '0319', '054400', 'N', '114.68371', '37.00488'); +INSERT INTO `yoshop_region` VALUES ('114', '104', '宁晋', '宁晋县', '中国,河北省,邢台市,宁晋县', '3', 'ningjin', '0319', '055550', 'N', '114.92117', '37.61696'); +INSERT INTO `yoshop_region` VALUES ('115', '104', '巨鹿', '巨鹿县', '中国,河北省,邢台市,巨鹿县', '3', 'julu', '0319', '055250', 'J', '115.03524', '37.21801'); +INSERT INTO `yoshop_region` VALUES ('116', '104', '新河', '新河县', '中国,河北省,邢台市,新河县', '3', 'xinhe', '0319', '055650', 'X', '115.24987', '37.52718'); +INSERT INTO `yoshop_region` VALUES ('117', '104', '广宗', '广宗县', '中国,河北省,邢台市,广宗县', '3', 'guangzong', '0319', '054600', 'G', '115.14254', '37.0746'); +INSERT INTO `yoshop_region` VALUES ('118', '104', '平乡', '平乡县', '中国,河北省,邢台市,平乡县', '3', 'pingxiang', '0319', '054500', 'P', '115.03002', '37.06317'); +INSERT INTO `yoshop_region` VALUES ('119', '104', '威县', '威县', '中国,河北省,邢台市,威县', '3', 'weixian', '0319', '054700', 'W', '115.2637', '36.9768'); +INSERT INTO `yoshop_region` VALUES ('120', '104', '清河', '清河县', '中国,河北省,邢台市,清河县', '3', 'qinghe', '0319', '054800', 'Q', '115.66479', '37.07122'); +INSERT INTO `yoshop_region` VALUES ('121', '104', '临西', '临西县', '中国,河北省,邢台市,临西县', '3', 'linxi', '0319', '054900', 'L', '115.50097', '36.87078'); +INSERT INTO `yoshop_region` VALUES ('122', '104', '南宫', '南宫市', '中国,河北省,邢台市,南宫市', '3', 'nangong', '0319', '055750', 'N', '115.39068', '37.35799'); +INSERT INTO `yoshop_region` VALUES ('123', '104', '沙河', '沙河市', '中国,河北省,邢台市,沙河市', '3', 'shahe', '0319', '054100', 'S', '114.4981', '36.8577'); +INSERT INTO `yoshop_region` VALUES ('124', '37', '保定', '保定市', '中国,河北省,保定市', '2', 'baoding', '0312', '071052', 'B', '115.482331', '38.867657'); +INSERT INTO `yoshop_region` VALUES ('125', '124', '新市', '新市区', '中国,河北省,保定市,新市区', '3', 'xinshi', '0312', '071051', 'X', '115.4587', '38.87751'); +INSERT INTO `yoshop_region` VALUES ('126', '124', '北市', '北市区', '中国,河北省,保定市,北市区', '3', 'beishi', '0312', '071000', 'B', '115.49715', '38.88322'); +INSERT INTO `yoshop_region` VALUES ('127', '124', '南市', '南市区', '中国,河北省,保定市,南市区', '3', 'nanshi', '0312', '071001', 'N', '115.52859', '38.85455'); +INSERT INTO `yoshop_region` VALUES ('128', '124', '满城', '满城县', '中国,河北省,保定市,满城县', '3', 'mancheng', '0312', '072150', 'M', '115.32296', '38.94972'); +INSERT INTO `yoshop_region` VALUES ('129', '124', '清苑', '清苑县', '中国,河北省,保定市,清苑县', '3', 'qingyuan', '0312', '071100', 'Q', '115.49267', '38.76709'); +INSERT INTO `yoshop_region` VALUES ('130', '124', '涞水', '涞水县', '中国,河北省,保定市,涞水县', '3', 'laishui', '0312', '074100', null, '115.71517', '39.39404'); +INSERT INTO `yoshop_region` VALUES ('131', '124', '阜平', '阜平县', '中国,河北省,保定市,阜平县', '3', 'fuping', '0312', '073200', 'F', '114.19683', '38.84763'); +INSERT INTO `yoshop_region` VALUES ('132', '124', '徐水', '徐水县', '中国,河北省,保定市,徐水县', '3', 'xushui', '0312', '072550', 'X', '115.65829', '39.02099'); +INSERT INTO `yoshop_region` VALUES ('133', '124', '定兴', '定兴县', '中国,河北省,保定市,定兴县', '3', 'dingxing', '0312', '072650', 'D', '115.80786', '39.26312'); +INSERT INTO `yoshop_region` VALUES ('134', '124', '唐县', '唐县', '中国,河北省,保定市,唐县', '3', 'tangxian', '0312', '072350', 'T', '114.98516', '38.74513'); +INSERT INTO `yoshop_region` VALUES ('135', '124', '高阳', '高阳县', '中国,河北省,保定市,高阳县', '3', 'gaoyang', '0312', '071500', 'G', '115.7788', '38.70003'); +INSERT INTO `yoshop_region` VALUES ('136', '124', '容城', '容城县', '中国,河北省,保定市,容城县', '3', 'rongcheng', '0312', '071700', 'R', '115.87158', '39.0535'); +INSERT INTO `yoshop_region` VALUES ('137', '124', '涞源', '涞源县', '中国,河北省,保定市,涞源县', '3', 'laiyuan', '0312', '074300', null, '114.69128', '39.35388'); +INSERT INTO `yoshop_region` VALUES ('138', '124', '望都', '望都县', '中国,河北省,保定市,望都县', '3', 'wangdu', '0312', '072450', 'W', '115.1567', '38.70996'); +INSERT INTO `yoshop_region` VALUES ('139', '124', '安新', '安新县', '中国,河北省,保定市,安新县', '3', 'anxin', '0312', '071600', 'A', '115.93557', '38.93532'); +INSERT INTO `yoshop_region` VALUES ('140', '124', '易县', '易县', '中国,河北省,保定市,易县', '3', 'yixian', '0312', '074200', 'Y', '115.4981', '39.34885'); +INSERT INTO `yoshop_region` VALUES ('141', '124', '曲阳', '曲阳县', '中国,河北省,保定市,曲阳县', '3', 'quyang', '0312', '073100', 'Q', '114.70123', '38.62154'); +INSERT INTO `yoshop_region` VALUES ('142', '124', '蠡县', '蠡县', '中国,河北省,保定市,蠡县', '3', 'lixian', '0312', '071400', null, '115.57717', '38.48974'); +INSERT INTO `yoshop_region` VALUES ('143', '124', '顺平', '顺平县', '中国,河北省,保定市,顺平县', '3', 'shunping', '0312', '072250', 'S', '115.1347', '38.83854'); +INSERT INTO `yoshop_region` VALUES ('144', '124', '博野', '博野县', '中国,河北省,保定市,博野县', '3', 'boye', '0312', '071300', 'B', '115.47033', '38.4564'); +INSERT INTO `yoshop_region` VALUES ('145', '124', '雄县', '雄县', '中国,河北省,保定市,雄县', '3', 'xiongxian', '0312', '071800', 'X', '116.10873', '38.99442'); +INSERT INTO `yoshop_region` VALUES ('146', '124', '涿州', '涿州市', '中国,河北省,保定市,涿州市', '3', 'zhuozhou', '0312', '072750', null, '115.98062', '39.48622'); +INSERT INTO `yoshop_region` VALUES ('147', '124', '定州', '定州市', '中国,河北省,保定市,定州市', '3', 'dingzhou', '0312', '073000', 'D', '114.9902', '38.51623'); +INSERT INTO `yoshop_region` VALUES ('148', '124', '安国', '安国市', '中国,河北省,保定市,安国市', '3', 'anguo', '0312', '071200', 'A', '115.32321', '38.41391'); +INSERT INTO `yoshop_region` VALUES ('149', '124', '高碑店', '高碑店市', '中国,河北省,保定市,高碑店市', '3', 'gaobeidian', '0312', '074000', 'G', '115.87368', '39.32655'); +INSERT INTO `yoshop_region` VALUES ('150', '37', '张家口', '张家口市', '中国,河北省,张家口市', '2', 'zhangjiakou', '0313', '075000', 'Z', '114.884091', '40.811901'); +INSERT INTO `yoshop_region` VALUES ('151', '150', '桥东', '桥东区', '中国,河北省,张家口市,桥东区', '3', 'qiaodong', '0313', '075000', 'Q', '114.8943', '40.78844'); +INSERT INTO `yoshop_region` VALUES ('152', '150', '桥西', '桥西区', '中国,河北省,张家口市,桥西区', '3', 'qiaoxi', '0313', '075061', 'Q', '114.86962', '40.81945'); +INSERT INTO `yoshop_region` VALUES ('153', '150', '宣化', '宣化区', '中国,河北省,张家口市,宣化区', '3', 'xuanhua', '0313', '075100', 'X', '115.06543', '40.60957'); +INSERT INTO `yoshop_region` VALUES ('154', '150', '下花园', '下花园区', '中国,河北省,张家口市,下花园区', '3', 'xiahuayuan', '0313', '075300', 'X', '115.28744', '40.50236'); +INSERT INTO `yoshop_region` VALUES ('155', '150', '宣化', '宣化县', '中国,河北省,张家口市,宣化县', '3', 'xuanhua', '0313', '075100', 'X', '115.15497', '40.56618'); +INSERT INTO `yoshop_region` VALUES ('156', '150', '张北', '张北县', '中国,河北省,张家口市,张北县', '3', 'zhangbei', '0313', '076450', 'Z', '114.71432', '41.15977'); +INSERT INTO `yoshop_region` VALUES ('157', '150', '康保', '康保县', '中国,河北省,张家口市,康保县', '3', 'kangbao', '0313', '076650', 'K', '114.60031', '41.85225'); +INSERT INTO `yoshop_region` VALUES ('158', '150', '沽源', '沽源县', '中国,河北省,张家口市,沽源县', '3', 'guyuan', '0313', '076550', 'G', '115.68859', '41.66959'); +INSERT INTO `yoshop_region` VALUES ('159', '150', '尚义', '尚义县', '中国,河北省,张家口市,尚义县', '3', 'shangyi', '0313', '076750', 'S', '113.97134', '41.07782'); +INSERT INTO `yoshop_region` VALUES ('160', '150', '蔚县', '蔚县', '中国,河北省,张家口市,蔚县', '3', 'yuxian', '0313', '075700', 'W', '114.58892', '39.84067'); +INSERT INTO `yoshop_region` VALUES ('161', '150', '阳原', '阳原县', '中国,河北省,张家口市,阳原县', '3', 'yangyuan', '0313', '075800', 'Y', '114.15051', '40.10361'); +INSERT INTO `yoshop_region` VALUES ('162', '150', '怀安', '怀安县', '中国,河北省,张家口市,怀安县', '3', 'huai\'an', '0313', '076150', 'H', '114.38559', '40.67425'); +INSERT INTO `yoshop_region` VALUES ('163', '150', '万全', '万全县', '中国,河北省,张家口市,万全县', '3', 'wanquan', '0313', '076250', 'W', '114.7405', '40.76694'); +INSERT INTO `yoshop_region` VALUES ('164', '150', '怀来', '怀来县', '中国,河北省,张家口市,怀来县', '3', 'huailai', '0313', '075400', 'H', '115.51773', '40.41536'); +INSERT INTO `yoshop_region` VALUES ('165', '150', '涿鹿', '涿鹿县', '中国,河北省,张家口市,涿鹿县', '3', 'zhuolu', '0313', '075600', null, '115.22403', '40.37636'); +INSERT INTO `yoshop_region` VALUES ('166', '150', '赤城', '赤城县', '中国,河北省,张家口市,赤城县', '3', 'chicheng', '0313', '075500', 'C', '115.83187', '40.91438'); +INSERT INTO `yoshop_region` VALUES ('167', '150', '崇礼', '崇礼县', '中国,河北省,张家口市,崇礼县', '3', 'chongli', '0313', '076350', 'C', '115.27993', '40.97519'); +INSERT INTO `yoshop_region` VALUES ('168', '37', '承德', '承德市', '中国,河北省,承德市', '2', 'chengde', '0314', '067000', 'C', '117.939152', '40.976204'); +INSERT INTO `yoshop_region` VALUES ('169', '168', '双桥', '双桥区', '中国,河北省,承德市,双桥区', '3', 'shuangqiao', '0314', '067000', 'S', '117.9432', '40.97466'); +INSERT INTO `yoshop_region` VALUES ('170', '168', '双滦', '双滦区', '中国,河北省,承德市,双滦区', '3', 'shuangluan', '0314', '067001', 'S', '117.74487', '40.95375'); +INSERT INTO `yoshop_region` VALUES ('171', '168', '鹰手营子矿区', '鹰手营子矿区', '中国,河北省,承德市,鹰手营子矿区', '3', 'yingshouyingzikuangqu', '0314', '067200', 'Y', '117.65985', '40.54744'); +INSERT INTO `yoshop_region` VALUES ('172', '168', '承德', '承德县', '中国,河北省,承德市,承德县', '3', 'chengde', '0314', '067400', 'C', '118.17639', '40.76985'); +INSERT INTO `yoshop_region` VALUES ('173', '168', '兴隆', '兴隆县', '中国,河北省,承德市,兴隆县', '3', 'xinglong', '0314', '067300', 'X', '117.50073', '40.41709'); +INSERT INTO `yoshop_region` VALUES ('174', '168', '平泉', '平泉县', '中国,河北省,承德市,平泉县', '3', 'pingquan', '0314', '067500', 'P', '118.70196', '41.01839'); +INSERT INTO `yoshop_region` VALUES ('175', '168', '滦平', '滦平县', '中国,河北省,承德市,滦平县', '3', 'luanping', '0314', '068250', 'L', '117.33276', '40.94148'); +INSERT INTO `yoshop_region` VALUES ('176', '168', '隆化', '隆化县', '中国,河北省,承德市,隆化县', '3', 'longhua', '0314', '068150', 'L', '117.7297', '41.31412'); +INSERT INTO `yoshop_region` VALUES ('177', '168', '丰宁', '丰宁满族自治县', '中国,河北省,承德市,丰宁满族自治县', '3', 'fengning', '0314', '068350', 'F', '116.6492', '41.20481'); +INSERT INTO `yoshop_region` VALUES ('178', '168', '宽城', '宽城满族自治县', '中国,河北省,承德市,宽城满族自治县', '3', 'kuancheng', '0314', '067600', 'K', '118.49176', '40.60829'); +INSERT INTO `yoshop_region` VALUES ('179', '168', '围场', '围场满族蒙古族自治县', '中国,河北省,承德市,围场满族蒙古族自治县', '3', 'weichang', '0314', '068450', 'W', '117.7601', '41.94368'); +INSERT INTO `yoshop_region` VALUES ('180', '37', '沧州', '沧州市', '中国,河北省,沧州市', '2', 'cangzhou', '0317', '061001', 'C', '116.857461', '38.310582'); +INSERT INTO `yoshop_region` VALUES ('181', '180', '新华', '新华区', '中国,河北省,沧州市,新华区', '3', 'xinhua', '0317', '061000', 'X', '116.86643', '38.31438'); +INSERT INTO `yoshop_region` VALUES ('182', '180', '运河', '运河区', '中国,河北省,沧州市,运河区', '3', 'yunhe', '0317', '061001', 'Y', '116.85706', '38.31352'); +INSERT INTO `yoshop_region` VALUES ('183', '180', '沧县', '沧县', '中国,河北省,沧州市,沧县', '3', 'cangxian', '0317', '061000', 'C', '116.87817', '38.29361'); +INSERT INTO `yoshop_region` VALUES ('184', '180', '青县', '青县', '中国,河北省,沧州市,青县', '3', 'qingxian', '0317', '062650', 'Q', '116.80316', '38.58345'); +INSERT INTO `yoshop_region` VALUES ('185', '180', '东光', '东光县', '中国,河北省,沧州市,东光县', '3', 'dongguang', '0317', '061600', 'D', '116.53668', '37.8857'); +INSERT INTO `yoshop_region` VALUES ('186', '180', '海兴', '海兴县', '中国,河北省,沧州市,海兴县', '3', 'haixing', '0317', '061200', 'H', '117.49758', '38.13958'); +INSERT INTO `yoshop_region` VALUES ('187', '180', '盐山', '盐山县', '中国,河北省,沧州市,盐山县', '3', 'yanshan', '0317', '061300', 'Y', '117.23092', '38.05647'); +INSERT INTO `yoshop_region` VALUES ('188', '180', '肃宁', '肃宁县', '中国,河北省,沧州市,肃宁县', '3', 'suning', '0317', '062350', 'S', '115.82971', '38.42272'); +INSERT INTO `yoshop_region` VALUES ('189', '180', '南皮', '南皮县', '中国,河北省,沧州市,南皮县', '3', 'nanpi', '0317', '061500', 'N', '116.70224', '38.04109'); +INSERT INTO `yoshop_region` VALUES ('190', '180', '吴桥', '吴桥县', '中国,河北省,沧州市,吴桥县', '3', 'wuqiao', '0317', '061800', 'W', '116.3847', '37.62546'); +INSERT INTO `yoshop_region` VALUES ('191', '180', '献县', '献县', '中国,河北省,沧州市,献县', '3', 'xianxian', '0317', '062250', 'X', '116.12695', '38.19228'); +INSERT INTO `yoshop_region` VALUES ('192', '180', '孟村', '孟村回族自治县', '中国,河北省,沧州市,孟村回族自治县', '3', 'mengcun', '0317', '061400', 'M', '117.10412', '38.05338'); +INSERT INTO `yoshop_region` VALUES ('193', '180', '泊头', '泊头市', '中国,河北省,沧州市,泊头市', '3', 'botou', '0317', '062150', 'B', '116.57824', '38.08359'); +INSERT INTO `yoshop_region` VALUES ('194', '180', '任丘', '任丘市', '中国,河北省,沧州市,任丘市', '3', 'renqiu', '0317', '062550', 'R', '116.1033', '38.71124'); +INSERT INTO `yoshop_region` VALUES ('195', '180', '黄骅', '黄骅市', '中国,河北省,沧州市,黄骅市', '3', 'huanghua', '0317', '061100', 'H', '117.33883', '38.3706'); +INSERT INTO `yoshop_region` VALUES ('196', '180', '河间', '河间市', '中国,河北省,沧州市,河间市', '3', 'hejian', '0317', '062450', 'H', '116.0993', '38.44549'); +INSERT INTO `yoshop_region` VALUES ('197', '37', '廊坊', '廊坊市', '中国,河北省,廊坊市', '2', 'langfang', '0316', '065000', 'L', '116.713873', '39.529244'); +INSERT INTO `yoshop_region` VALUES ('198', '197', '安次', '安次区', '中国,河北省,廊坊市,安次区', '3', 'anci', '0316', '065000', 'A', '116.70308', '39.52057'); +INSERT INTO `yoshop_region` VALUES ('199', '197', '广阳', '广阳区', '中国,河北省,廊坊市,广阳区', '3', 'guangyang', '0316', '065000', 'G', '116.71069', '39.52278'); +INSERT INTO `yoshop_region` VALUES ('200', '197', '固安', '固安县', '中国,河北省,廊坊市,固安县', '3', 'gu\'an', '0316', '065500', 'G', '116.29916', '39.43833'); +INSERT INTO `yoshop_region` VALUES ('201', '197', '永清', '永清县', '中国,河北省,廊坊市,永清县', '3', 'yongqing', '0316', '065600', 'Y', '116.50091', '39.32069'); +INSERT INTO `yoshop_region` VALUES ('202', '197', '香河', '香河县', '中国,河北省,廊坊市,香河县', '3', 'xianghe', '0316', '065400', 'X', '117.00634', '39.76133'); +INSERT INTO `yoshop_region` VALUES ('203', '197', '大城', '大城县', '中国,河北省,廊坊市,大城县', '3', 'daicheng', '0316', '065900', 'D', '116.65353', '38.70534'); +INSERT INTO `yoshop_region` VALUES ('204', '197', '文安', '文安县', '中国,河北省,廊坊市,文安县', '3', 'wen\'an', '0316', '065800', 'W', '116.45846', '38.87325'); +INSERT INTO `yoshop_region` VALUES ('205', '197', '大厂', '大厂回族自治县', '中国,河北省,廊坊市,大厂回族自治县', '3', 'dachang', '0316', '065300', 'D', '116.98916', '39.88649'); +INSERT INTO `yoshop_region` VALUES ('206', '197', '霸州', '霸州市', '中国,河北省,廊坊市,霸州市', '3', 'bazhou', '0316', '065700', 'B', '116.39154', '39.12569'); +INSERT INTO `yoshop_region` VALUES ('207', '197', '三河', '三河市', '中国,河北省,廊坊市,三河市', '3', 'sanhe', '0316', '065200', 'S', '117.07229', '39.98358'); +INSERT INTO `yoshop_region` VALUES ('208', '37', '衡水', '衡水市', '中国,河北省,衡水市', '2', 'hengshui', '0318', '053000', 'H', '115.665993', '37.735097'); +INSERT INTO `yoshop_region` VALUES ('209', '208', '桃城', '桃城区', '中国,河北省,衡水市,桃城区', '3', 'taocheng', '0318', '053000', 'T', '115.67529', '37.73499'); +INSERT INTO `yoshop_region` VALUES ('210', '208', '枣强', '枣强县', '中国,河北省,衡水市,枣强县', '3', 'zaoqiang', '0318', '053100', 'Z', '115.72576', '37.51027'); +INSERT INTO `yoshop_region` VALUES ('211', '208', '武邑', '武邑县', '中国,河北省,衡水市,武邑县', '3', 'wuyi', '0318', '053400', 'W', '115.88748', '37.80181'); +INSERT INTO `yoshop_region` VALUES ('212', '208', '武强', '武强县', '中国,河北省,衡水市,武强县', '3', 'wuqiang', '0318', '053300', 'W', '115.98226', '38.04138'); +INSERT INTO `yoshop_region` VALUES ('213', '208', '饶阳', '饶阳县', '中国,河北省,衡水市,饶阳县', '3', 'raoyang', '0318', '053900', 'R', '115.72558', '38.23529'); +INSERT INTO `yoshop_region` VALUES ('214', '208', '安平', '安平县', '中国,河北省,衡水市,安平县', '3', 'anping', '0318', '053600', 'A', '115.51876', '38.23388'); +INSERT INTO `yoshop_region` VALUES ('215', '208', '故城', '故城县', '中国,河北省,衡水市,故城县', '3', 'gucheng', '0318', '053800', 'G', '115.97076', '37.34773'); +INSERT INTO `yoshop_region` VALUES ('216', '208', '景县', '景县', '中国,河北省,衡水市,景县', '3', 'jingxian', '0318', '053500', 'J', '116.26904', '37.6926'); +INSERT INTO `yoshop_region` VALUES ('217', '208', '阜城', '阜城县', '中国,河北省,衡水市,阜城县', '3', 'fucheng', '0318', '053700', 'F', '116.14431', '37.86881'); +INSERT INTO `yoshop_region` VALUES ('218', '208', '冀州', '冀州市', '中国,河北省,衡水市,冀州市', '3', 'jizhou', '0318', '053200', 'J', '115.57934', '37.55082'); +INSERT INTO `yoshop_region` VALUES ('219', '208', '深州', '深州市', '中国,河北省,衡水市,深州市', '3', 'shenzhou', '0318', '053800', 'S', '115.55993', '38.00109'); +INSERT INTO `yoshop_region` VALUES ('220', '0', '山西', '山西省', '中国,山西省', '1', 'shanxi', '', '', 'S', '112.549248', '37.857014'); +INSERT INTO `yoshop_region` VALUES ('221', '220', '太原', '太原市', '中国,山西省,太原市', '2', 'taiyuan', '0351', '030082', 'T', '112.549248', '37.857014'); +INSERT INTO `yoshop_region` VALUES ('222', '221', '小店', '小店区', '中国,山西省,太原市,小店区', '3', 'xiaodian', '0351', '030032', 'X', '112.56878', '37.73565'); +INSERT INTO `yoshop_region` VALUES ('223', '221', '迎泽', '迎泽区', '中国,山西省,太原市,迎泽区', '3', 'yingze', '0351', '030002', 'Y', '112.56338', '37.86326'); +INSERT INTO `yoshop_region` VALUES ('224', '221', '杏花岭', '杏花岭区', '中国,山西省,太原市,杏花岭区', '3', 'xinghualing', '0351', '030009', 'X', '112.56237', '37.88429'); +INSERT INTO `yoshop_region` VALUES ('225', '221', '尖草坪', '尖草坪区', '中国,山西省,太原市,尖草坪区', '3', 'jiancaoping', '0351', '030023', 'J', '112.48709', '37.94193'); +INSERT INTO `yoshop_region` VALUES ('226', '221', '万柏林', '万柏林区', '中国,山西省,太原市,万柏林区', '3', 'wanbailin', '0351', '030024', 'W', '112.51553', '37.85923'); +INSERT INTO `yoshop_region` VALUES ('227', '221', '晋源', '晋源区', '中国,山西省,太原市,晋源区', '3', 'jinyuan', '0351', '030025', 'J', '112.47985', '37.72479'); +INSERT INTO `yoshop_region` VALUES ('228', '221', '清徐', '清徐县', '中国,山西省,太原市,清徐县', '3', 'qingxu', '0351', '030400', 'Q', '112.35888', '37.60758'); +INSERT INTO `yoshop_region` VALUES ('229', '221', '阳曲', '阳曲县', '中国,山西省,太原市,阳曲县', '3', 'yangqu', '0351', '030100', 'Y', '112.67861', '38.05989'); +INSERT INTO `yoshop_region` VALUES ('230', '221', '娄烦', '娄烦县', '中国,山西省,太原市,娄烦县', '3', 'loufan', '0351', '030300', 'L', '111.79473', '38.06689'); +INSERT INTO `yoshop_region` VALUES ('231', '221', '古交', '古交市', '中国,山西省,太原市,古交市', '3', 'gujiao', '0351', '030200', 'G', '112.16918', '37.90983'); +INSERT INTO `yoshop_region` VALUES ('232', '220', '大同', '大同市', '中国,山西省,大同市', '2', 'datong', '0352', '037008', 'D', '113.295259', '40.09031'); +INSERT INTO `yoshop_region` VALUES ('233', '232', '城区', '城区', '中国,山西省,大同市,城区', '3', 'chengqu', '0352', '037008', 'C', '113.298', '40.07566'); +INSERT INTO `yoshop_region` VALUES ('234', '232', '矿区', '矿区', '中国,山西省,大同市,矿区', '3', 'kuangqu', '0352', '037003', 'K', '113.1772', '40.03685'); +INSERT INTO `yoshop_region` VALUES ('235', '232', '南郊', '南郊区', '中国,山西省,大同市,南郊区', '3', 'nanjiao', '0352', '037001', 'N', '113.14947', '40.00539'); +INSERT INTO `yoshop_region` VALUES ('236', '232', '新荣', '新荣区', '中国,山西省,大同市,新荣区', '3', 'xinrong', '0352', '037002', 'X', '113.13504', '40.25618'); +INSERT INTO `yoshop_region` VALUES ('237', '232', '阳高', '阳高县', '中国,山西省,大同市,阳高县', '3', 'yanggao', '0352', '038100', 'Y', '113.75012', '40.36256'); +INSERT INTO `yoshop_region` VALUES ('238', '232', '天镇', '天镇县', '中国,山西省,大同市,天镇县', '3', 'tianzhen', '0352', '038200', 'T', '114.0931', '40.42299'); +INSERT INTO `yoshop_region` VALUES ('239', '232', '广灵', '广灵县', '中国,山西省,大同市,广灵县', '3', 'guangling', '0352', '037500', 'G', '114.28204', '39.76082'); +INSERT INTO `yoshop_region` VALUES ('240', '232', '灵丘', '灵丘县', '中国,山西省,大同市,灵丘县', '3', 'lingqiu', '0352', '034400', 'L', '114.23672', '39.44043'); +INSERT INTO `yoshop_region` VALUES ('241', '232', '浑源', '浑源县', '中国,山西省,大同市,浑源县', '3', 'hunyuan', '0352', '037400', 'H', '113.69552', '39.69962'); +INSERT INTO `yoshop_region` VALUES ('242', '232', '左云', '左云县', '中国,山西省,大同市,左云县', '3', 'zuoyun', '0352', '037100', 'Z', '112.70266', '40.01336'); +INSERT INTO `yoshop_region` VALUES ('243', '232', '大同', '大同县', '中国,山西省,大同市,大同县', '3', 'datong', '0352', '037300', 'D', '113.61212', '40.04012'); +INSERT INTO `yoshop_region` VALUES ('244', '220', '阳泉', '阳泉市', '中国,山西省,阳泉市', '2', 'yangquan', '0353', '045000', 'Y', '113.583285', '37.861188'); +INSERT INTO `yoshop_region` VALUES ('245', '244', '城区', '城区', '中国,山西省,阳泉市,城区', '3', 'chengqu', '0353', '045000', 'C', '113.60069', '37.8474'); +INSERT INTO `yoshop_region` VALUES ('246', '244', '矿区', '矿区', '中国,山西省,阳泉市,矿区', '3', 'kuangqu', '0353', '045000', 'K', '113.55677', '37.86895'); +INSERT INTO `yoshop_region` VALUES ('247', '244', '郊区', '郊区', '中国,山西省,阳泉市,郊区', '3', 'jiaoqu', '0353', '045011', 'J', '113.58539', '37.94139'); +INSERT INTO `yoshop_region` VALUES ('248', '244', '平定', '平定县', '中国,山西省,阳泉市,平定县', '3', 'pingding', '0353', '045200', 'P', '113.65789', '37.78601'); +INSERT INTO `yoshop_region` VALUES ('249', '244', '盂县', '盂县', '中国,山西省,阳泉市,盂县', '3', 'yuxian', '0353', '045100', 'Y', '113.41235', '38.08579'); +INSERT INTO `yoshop_region` VALUES ('250', '220', '长治', '长治市', '中国,山西省,长治市', '2', 'changzhi', '0355', '046000', 'C', '113.113556', '36.191112'); +INSERT INTO `yoshop_region` VALUES ('251', '250', '城区', '城区', '中国,山西省,长治市,城区', '3', 'chengqu', '0355', '046011', 'C', '113.12308', '36.20351'); +INSERT INTO `yoshop_region` VALUES ('252', '250', '郊区', '郊区', '中国,山西省,长治市,郊区', '3', 'jiaoqu', '0355', '046011', 'J', '113.12653', '36.19918'); +INSERT INTO `yoshop_region` VALUES ('253', '250', '长治', '长治县', '中国,山西省,长治市,长治县', '3', 'changzhi', '0355', '047100', 'C', '113.04791', '36.04722'); +INSERT INTO `yoshop_region` VALUES ('254', '250', '襄垣', '襄垣县', '中国,山西省,长治市,襄垣县', '3', 'xiangyuan', '0355', '046200', 'X', '113.05157', '36.53527'); +INSERT INTO `yoshop_region` VALUES ('255', '250', '屯留', '屯留县', '中国,山西省,长治市,屯留县', '3', 'tunliu', '0355', '046100', 'T', '112.89196', '36.31579'); +INSERT INTO `yoshop_region` VALUES ('256', '250', '平顺', '平顺县', '中国,山西省,长治市,平顺县', '3', 'pingshun', '0355', '047400', 'P', '113.43603', '36.20005'); +INSERT INTO `yoshop_region` VALUES ('257', '250', '黎城', '黎城县', '中国,山西省,长治市,黎城县', '3', 'licheng', '0355', '047600', 'L', '113.38766', '36.50301'); +INSERT INTO `yoshop_region` VALUES ('258', '250', '壶关', '壶关县', '中国,山西省,长治市,壶关县', '3', 'huguan', '0355', '047300', 'H', '113.207', '36.11301'); +INSERT INTO `yoshop_region` VALUES ('259', '250', '长子', '长子县', '中国,山西省,长治市,长子县', '3', 'zhangzi', '0355', '046600', 'C', '112.87731', '36.12125'); +INSERT INTO `yoshop_region` VALUES ('260', '250', '武乡', '武乡县', '中国,山西省,长治市,武乡县', '3', 'wuxiang', '0355', '046300', 'W', '112.86343', '36.83687'); +INSERT INTO `yoshop_region` VALUES ('261', '250', '沁县', '沁县', '中国,山西省,长治市,沁县', '3', 'qinxian', '0355', '046400', 'Q', '112.69863', '36.75628'); +INSERT INTO `yoshop_region` VALUES ('262', '250', '沁源', '沁源县', '中国,山西省,长治市,沁源县', '3', 'qinyuan', '0355', '046500', 'Q', '112.33758', '36.50008'); +INSERT INTO `yoshop_region` VALUES ('263', '250', '潞城', '潞城市', '中国,山西省,长治市,潞城市', '3', 'lucheng', '0355', '047500', 'L', '113.22888', '36.33414'); +INSERT INTO `yoshop_region` VALUES ('264', '220', '晋城', '晋城市', '中国,山西省,晋城市', '2', 'jincheng', '0356', '048000', 'J', '112.851274', '35.497553'); +INSERT INTO `yoshop_region` VALUES ('265', '264', '城区', '城区', '中国,山西省,晋城市,城区', '3', 'chengqu', '0356', '048000', 'C', '112.85319', '35.50175'); +INSERT INTO `yoshop_region` VALUES ('266', '264', '沁水', '沁水县', '中国,山西省,晋城市,沁水县', '3', 'qinshui', '0356', '048200', 'Q', '112.1871', '35.69102'); +INSERT INTO `yoshop_region` VALUES ('267', '264', '阳城', '阳城县', '中国,山西省,晋城市,阳城县', '3', 'yangcheng', '0356', '048100', 'Y', '112.41485', '35.48614'); +INSERT INTO `yoshop_region` VALUES ('268', '264', '陵川', '陵川县', '中国,山西省,晋城市,陵川县', '3', 'lingchuan', '0356', '048300', 'L', '113.2806', '35.77532'); +INSERT INTO `yoshop_region` VALUES ('269', '264', '泽州', '泽州县', '中国,山西省,晋城市,泽州县', '3', 'zezhou', '0356', '048012', 'Z', '112.83947', '35.50789'); +INSERT INTO `yoshop_region` VALUES ('270', '264', '高平', '高平市', '中国,山西省,晋城市,高平市', '3', 'gaoping', '0356', '048400', 'G', '112.92288', '35.79705'); +INSERT INTO `yoshop_region` VALUES ('271', '220', '朔州', '朔州市', '中国,山西省,朔州市', '2', 'shuozhou', '0349', '038500', 'S', '112.433387', '39.331261'); +INSERT INTO `yoshop_region` VALUES ('272', '271', '朔城', '朔城区', '中国,山西省,朔州市,朔城区', '3', 'shuocheng', '0349', '036000', 'S', '112.43189', '39.31982'); +INSERT INTO `yoshop_region` VALUES ('273', '271', '平鲁', '平鲁区', '中国,山西省,朔州市,平鲁区', '3', 'pinglu', '0349', '038600', 'P', '112.28833', '39.51155'); +INSERT INTO `yoshop_region` VALUES ('274', '271', '山阴', '山阴县', '中国,山西省,朔州市,山阴县', '3', 'shanyin', '0349', '036900', 'S', '112.81662', '39.52697'); +INSERT INTO `yoshop_region` VALUES ('275', '271', '应县', '应县', '中国,山西省,朔州市,应县', '3', 'yingxian', '0349', '037600', 'Y', '113.19052', '39.55279'); +INSERT INTO `yoshop_region` VALUES ('276', '271', '右玉', '右玉县', '中国,山西省,朔州市,右玉县', '3', 'youyu', '0349', '037200', 'Y', '112.46902', '39.99011'); +INSERT INTO `yoshop_region` VALUES ('277', '271', '怀仁', '怀仁县', '中国,山西省,朔州市,怀仁县', '3', 'huairen', '0349', '038300', 'H', '113.10009', '39.82806'); +INSERT INTO `yoshop_region` VALUES ('278', '220', '晋中', '晋中市', '中国,山西省,晋中市', '2', 'jinzhong', '0354', '030600', 'J', '112.736465', '37.696495'); +INSERT INTO `yoshop_region` VALUES ('279', '278', '榆次', '榆次区', '中国,山西省,晋中市,榆次区', '3', 'yuci', '0354', '030600', 'Y', '112.70788', '37.6978'); +INSERT INTO `yoshop_region` VALUES ('280', '278', '榆社', '榆社县', '中国,山西省,晋中市,榆社县', '3', 'yushe', '0354', '031800', 'Y', '112.97558', '37.0721'); +INSERT INTO `yoshop_region` VALUES ('281', '278', '左权', '左权县', '中国,山西省,晋中市,左权县', '3', 'zuoquan', '0354', '032600', 'Z', '113.37918', '37.08235'); +INSERT INTO `yoshop_region` VALUES ('282', '278', '和顺', '和顺县', '中国,山西省,晋中市,和顺县', '3', 'heshun', '0354', '032700', 'H', '113.56988', '37.32963'); +INSERT INTO `yoshop_region` VALUES ('283', '278', '昔阳', '昔阳县', '中国,山西省,晋中市,昔阳县', '3', 'xiyang', '0354', '045300', 'X', '113.70517', '37.61863'); +INSERT INTO `yoshop_region` VALUES ('284', '278', '寿阳', '寿阳县', '中国,山西省,晋中市,寿阳县', '3', 'shouyang', '0354', '045400', 'S', '113.17495', '37.88899'); +INSERT INTO `yoshop_region` VALUES ('285', '278', '太谷', '太谷县', '中国,山西省,晋中市,太谷县', '3', 'taigu', '0354', '030800', 'T', '112.55246', '37.42161'); +INSERT INTO `yoshop_region` VALUES ('286', '278', '祁县', '祁县', '中国,山西省,晋中市,祁县', '3', 'qixian', '0354', '030900', 'Q', '112.33358', '37.3579'); +INSERT INTO `yoshop_region` VALUES ('287', '278', '平遥', '平遥县', '中国,山西省,晋中市,平遥县', '3', 'pingyao', '0354', '031100', 'P', '112.17553', '37.1892'); +INSERT INTO `yoshop_region` VALUES ('288', '278', '灵石', '灵石县', '中国,山西省,晋中市,灵石县', '3', 'lingshi', '0354', '031300', 'L', '111.7774', '36.84814'); +INSERT INTO `yoshop_region` VALUES ('289', '278', '介休', '介休市', '中国,山西省,晋中市,介休市', '3', 'jiexiu', '0354', '032000', 'J', '111.91824', '37.02771'); +INSERT INTO `yoshop_region` VALUES ('290', '220', '运城', '运城市', '中国,山西省,运城市', '2', 'yuncheng', '0359', '044000', 'Y', '111.003957', '35.022778'); +INSERT INTO `yoshop_region` VALUES ('291', '290', '盐湖', '盐湖区', '中国,山西省,运城市,盐湖区', '3', 'yanhu', '0359', '044000', 'Y', '110.99827', '35.0151'); +INSERT INTO `yoshop_region` VALUES ('292', '290', '临猗', '临猗县', '中国,山西省,运城市,临猗县', '3', 'linyi', '0359', '044100', 'L', '110.77432', '35.14455'); +INSERT INTO `yoshop_region` VALUES ('293', '290', '万荣', '万荣县', '中国,山西省,运城市,万荣县', '3', 'wanrong', '0359', '044200', 'W', '110.83657', '35.41556'); +INSERT INTO `yoshop_region` VALUES ('294', '290', '闻喜', '闻喜县', '中国,山西省,运城市,闻喜县', '3', 'wenxi', '0359', '043800', 'W', '111.22265', '35.35553'); +INSERT INTO `yoshop_region` VALUES ('295', '290', '稷山', '稷山县', '中国,山西省,运城市,稷山县', '3', 'jishan', '0359', '043200', null, '110.97924', '35.59993'); +INSERT INTO `yoshop_region` VALUES ('296', '290', '新绛', '新绛县', '中国,山西省,运城市,新绛县', '3', 'xinjiang', '0359', '043100', 'X', '111.22509', '35.61566'); +INSERT INTO `yoshop_region` VALUES ('297', '290', '绛县', '绛县', '中国,山西省,运城市,绛县', '3', 'jiangxian', '0359', '043600', null, '111.56668', '35.49096'); +INSERT INTO `yoshop_region` VALUES ('298', '290', '垣曲', '垣曲县', '中国,山西省,运城市,垣曲县', '3', 'yuanqu', '0359', '043700', 'Y', '111.67166', '35.29923'); +INSERT INTO `yoshop_region` VALUES ('299', '290', '夏县', '夏县', '中国,山西省,运城市,夏县', '3', 'xiaxian', '0359', '044400', 'X', '111.21966', '35.14121'); +INSERT INTO `yoshop_region` VALUES ('300', '290', '平陆', '平陆县', '中国,山西省,运城市,平陆县', '3', 'pinglu', '0359', '044300', 'P', '111.21704', '34.83772'); +INSERT INTO `yoshop_region` VALUES ('301', '290', '芮城', '芮城县', '中国,山西省,运城市,芮城县', '3', 'ruicheng', '0359', '044600', null, '110.69455', '34.69384'); +INSERT INTO `yoshop_region` VALUES ('302', '290', '永济', '永济市', '中国,山西省,运城市,永济市', '3', 'yongji', '0359', '044500', 'Y', '110.44537', '34.86556'); +INSERT INTO `yoshop_region` VALUES ('303', '290', '河津', '河津市', '中国,山西省,运城市,河津市', '3', 'hejin', '0359', '043300', 'H', '110.7116', '35.59478'); +INSERT INTO `yoshop_region` VALUES ('304', '220', '忻州', '忻州市', '中国,山西省,忻州市', '2', 'xinzhou', '0350', '034000', 'X', '112.733538', '38.41769'); +INSERT INTO `yoshop_region` VALUES ('305', '304', '忻府', '忻府区', '中国,山西省,忻州市,忻府区', '3', 'xinfu', '0350', '034000', 'X', '112.74603', '38.40414'); +INSERT INTO `yoshop_region` VALUES ('306', '304', '定襄', '定襄县', '中国,山西省,忻州市,定襄县', '3', 'dingxiang', '0350', '035400', 'D', '112.95733', '38.47387'); +INSERT INTO `yoshop_region` VALUES ('307', '304', '五台', '五台县', '中国,山西省,忻州市,五台县', '3', 'wutai', '0350', '035500', 'W', '113.25256', '38.72774'); +INSERT INTO `yoshop_region` VALUES ('308', '304', '代县', '代县', '中国,山西省,忻州市,代县', '3', 'daixian', '0350', '034200', 'D', '112.95913', '39.06717'); +INSERT INTO `yoshop_region` VALUES ('309', '304', '繁峙', '繁峙县', '中国,山西省,忻州市,繁峙县', '3', 'fanshi', '0350', '034300', 'F', '113.26303', '39.18886'); +INSERT INTO `yoshop_region` VALUES ('310', '304', '宁武', '宁武县', '中国,山西省,忻州市,宁武县', '3', 'ningwu', '0350', '036700', 'N', '112.30423', '39.00211'); +INSERT INTO `yoshop_region` VALUES ('311', '304', '静乐', '静乐县', '中国,山西省,忻州市,静乐县', '3', 'jingle', '0350', '035100', 'J', '111.94158', '38.3602'); +INSERT INTO `yoshop_region` VALUES ('312', '304', '神池', '神池县', '中国,山西省,忻州市,神池县', '3', 'shenchi', '0350', '036100', 'S', '112.20541', '39.09'); +INSERT INTO `yoshop_region` VALUES ('313', '304', '五寨', '五寨县', '中国,山西省,忻州市,五寨县', '3', 'wuzhai', '0350', '036200', 'W', '111.8489', '38.90757'); +INSERT INTO `yoshop_region` VALUES ('314', '304', '岢岚', '岢岚县', '中国,山西省,忻州市,岢岚县', '3', 'kelan', '0350', '036300', null, '111.57388', '38.70452'); +INSERT INTO `yoshop_region` VALUES ('315', '304', '河曲', '河曲县', '中国,山西省,忻州市,河曲县', '3', 'hequ', '0350', '036500', 'H', '111.13821', '39.38439'); +INSERT INTO `yoshop_region` VALUES ('316', '304', '保德', '保德县', '中国,山西省,忻州市,保德县', '3', 'baode', '0350', '036600', 'B', '111.08656', '39.02248'); +INSERT INTO `yoshop_region` VALUES ('317', '304', '偏关', '偏关县', '中国,山西省,忻州市,偏关县', '3', 'pianguan', '0350', '036400', 'P', '111.50863', '39.43609'); +INSERT INTO `yoshop_region` VALUES ('318', '304', '原平', '原平市', '中国,山西省,忻州市,原平市', '3', 'yuanping', '0350', '034100', 'Y', '112.70584', '38.73181'); +INSERT INTO `yoshop_region` VALUES ('319', '220', '临汾', '临汾市', '中国,山西省,临汾市', '2', 'linfen', '0357', '041000', 'L', '111.517973', '36.08415'); +INSERT INTO `yoshop_region` VALUES ('320', '319', '尧都', '尧都区', '中国,山西省,临汾市,尧都区', '3', 'yaodu', '0357', '041000', 'Y', '111.5787', '36.08298'); +INSERT INTO `yoshop_region` VALUES ('321', '319', '曲沃', '曲沃县', '中国,山西省,临汾市,曲沃县', '3', 'quwo', '0357', '043400', 'Q', '111.47525', '35.64119'); +INSERT INTO `yoshop_region` VALUES ('322', '319', '翼城', '翼城县', '中国,山西省,临汾市,翼城县', '3', 'yicheng', '0357', '043500', 'Y', '111.7181', '35.73881'); +INSERT INTO `yoshop_region` VALUES ('323', '319', '襄汾', '襄汾县', '中国,山西省,临汾市,襄汾县', '3', 'xiangfen', '0357', '041500', 'X', '111.44204', '35.87711'); +INSERT INTO `yoshop_region` VALUES ('324', '319', '洪洞', '洪洞县', '中国,山西省,临汾市,洪洞县', '3', 'hongtong', '0357', '041600', 'H', '111.67501', '36.25425'); +INSERT INTO `yoshop_region` VALUES ('325', '319', '古县', '古县', '中国,山西省,临汾市,古县', '3', 'guxian', '0357', '042400', 'G', '111.92041', '36.26688'); +INSERT INTO `yoshop_region` VALUES ('326', '319', '安泽', '安泽县', '中国,山西省,临汾市,安泽县', '3', 'anze', '0357', '042500', 'A', '112.24981', '36.14803'); +INSERT INTO `yoshop_region` VALUES ('327', '319', '浮山', '浮山县', '中国,山西省,临汾市,浮山县', '3', 'fushan', '0357', '042600', 'F', '111.84744', '35.96854'); +INSERT INTO `yoshop_region` VALUES ('328', '319', '吉县', '吉县', '中国,山西省,临汾市,吉县', '3', 'jixian', '0357', '042200', 'J', '110.68148', '36.09873'); +INSERT INTO `yoshop_region` VALUES ('329', '319', '乡宁', '乡宁县', '中国,山西省,临汾市,乡宁县', '3', 'xiangning', '0357', '042100', 'X', '110.84652', '35.97072'); +INSERT INTO `yoshop_region` VALUES ('330', '319', '大宁', '大宁县', '中国,山西省,临汾市,大宁县', '3', 'daning', '0357', '042300', 'D', '110.75216', '36.46624'); +INSERT INTO `yoshop_region` VALUES ('331', '319', '隰县', '隰县', '中国,山西省,临汾市,隰县', '3', 'xixian', '0357', '041300', null, '110.93881', '36.69258'); +INSERT INTO `yoshop_region` VALUES ('332', '319', '永和', '永和县', '中国,山西省,临汾市,永和县', '3', 'yonghe', '0357', '041400', 'Y', '110.63168', '36.7584'); +INSERT INTO `yoshop_region` VALUES ('333', '319', '蒲县', '蒲县', '中国,山西省,临汾市,蒲县', '3', 'puxian', '0357', '041200', 'P', '111.09674', '36.41243'); +INSERT INTO `yoshop_region` VALUES ('334', '319', '汾西', '汾西县', '中国,山西省,临汾市,汾西县', '3', 'fenxi', '0357', '031500', 'F', '111.56811', '36.65063'); +INSERT INTO `yoshop_region` VALUES ('335', '319', '侯马', '侯马市', '中国,山西省,临汾市,侯马市', '3', 'houma', '0357', '043000', 'H', '111.37207', '35.61903'); +INSERT INTO `yoshop_region` VALUES ('336', '319', '霍州', '霍州市', '中国,山西省,临汾市,霍州市', '3', 'huozhou', '0357', '031400', 'H', '111.755', '36.5638'); +INSERT INTO `yoshop_region` VALUES ('337', '220', '吕梁', '吕梁市', '中国,山西省,吕梁市', '2', 'lvliang', '0358', '033000', 'L', '111.134335', '37.524366'); +INSERT INTO `yoshop_region` VALUES ('338', '337', '离石', '离石区', '中国,山西省,吕梁市,离石区', '3', 'lishi', '0358', '033000', 'L', '111.15059', '37.5177'); +INSERT INTO `yoshop_region` VALUES ('339', '337', '文水', '文水县', '中国,山西省,吕梁市,文水县', '3', 'wenshui', '0358', '032100', 'W', '112.02829', '37.43841'); +INSERT INTO `yoshop_region` VALUES ('340', '337', '交城', '交城县', '中国,山西省,吕梁市,交城县', '3', 'jiaocheng', '0358', '030500', 'J', '112.1585', '37.5512'); +INSERT INTO `yoshop_region` VALUES ('341', '337', '兴县', '兴县', '中国,山西省,吕梁市,兴县', '3', 'xingxian', '0358', '033600', 'X', '111.12692', '38.46321'); +INSERT INTO `yoshop_region` VALUES ('342', '337', '临县', '临县', '中国,山西省,吕梁市,临县', '3', 'linxian', '0358', '033200', 'L', '110.99282', '37.95271'); +INSERT INTO `yoshop_region` VALUES ('343', '337', '柳林', '柳林县', '中国,山西省,吕梁市,柳林县', '3', 'liulin', '0358', '033300', 'L', '110.88922', '37.42932'); +INSERT INTO `yoshop_region` VALUES ('344', '337', '石楼', '石楼县', '中国,山西省,吕梁市,石楼县', '3', 'shilou', '0358', '032500', 'S', '110.8352', '36.99731'); +INSERT INTO `yoshop_region` VALUES ('345', '337', '岚县', '岚县', '中国,山西省,吕梁市,岚县', '3', 'lanxian', '0358', '033500', null, '111.67627', '38.27874'); +INSERT INTO `yoshop_region` VALUES ('346', '337', '方山', '方山县', '中国,山西省,吕梁市,方山县', '3', 'fangshan', '0358', '033100', 'F', '111.24011', '37.88979'); +INSERT INTO `yoshop_region` VALUES ('347', '337', '中阳', '中阳县', '中国,山西省,吕梁市,中阳县', '3', 'zhongyang', '0358', '033400', 'Z', '111.1795', '37.35715'); +INSERT INTO `yoshop_region` VALUES ('348', '337', '交口', '交口县', '中国,山西省,吕梁市,交口县', '3', 'jiaokou', '0358', '032400', 'J', '111.18103', '36.98213'); +INSERT INTO `yoshop_region` VALUES ('349', '337', '孝义', '孝义市', '中国,山西省,吕梁市,孝义市', '3', 'xiaoyi', '0358', '032300', 'X', '111.77362', '37.14414'); +INSERT INTO `yoshop_region` VALUES ('350', '337', '汾阳', '汾阳市', '中国,山西省,吕梁市,汾阳市', '3', 'fenyang', '0358', '032200', 'F', '111.7882', '37.26605'); +INSERT INTO `yoshop_region` VALUES ('351', '0', '内蒙古', '内蒙古自治区', '中国,内蒙古自治区', '1', 'innermongolia', '', '', 'N', '111.670801', '40.818311'); +INSERT INTO `yoshop_region` VALUES ('352', '351', '呼和浩特', '呼和浩特市', '中国,内蒙古自治区,呼和浩特市', '2', 'hohhot', '0471', '010000', 'H', '111.670801', '40.818311'); +INSERT INTO `yoshop_region` VALUES ('353', '352', '新城', '新城区', '中国,内蒙古自治区,呼和浩特市,新城区', '3', 'xincheng', '0471', '010050', 'X', '111.66554', '40.85828'); +INSERT INTO `yoshop_region` VALUES ('354', '352', '回民', '回民区', '中国,内蒙古自治区,呼和浩特市,回民区', '3', 'huimin', '0471', '010030', 'H', '111.62402', '40.80827'); +INSERT INTO `yoshop_region` VALUES ('355', '352', '玉泉', '玉泉区', '中国,内蒙古自治区,呼和浩特市,玉泉区', '3', 'yuquan', '0471', '010020', 'Y', '111.67456', '40.75227'); +INSERT INTO `yoshop_region` VALUES ('356', '352', '赛罕', '赛罕区', '中国,内蒙古自治区,呼和浩特市,赛罕区', '3', 'saihan', '0471', '010020', 'S', '111.70224', '40.79207'); +INSERT INTO `yoshop_region` VALUES ('357', '352', '土默特左旗', '土默特左旗', '中国,内蒙古自治区,呼和浩特市,土默特左旗', '3', 'tumotezuoqi', '0471', '010100', 'T', '111.14898', '40.72229'); +INSERT INTO `yoshop_region` VALUES ('358', '352', '托克托', '托克托县', '中国,内蒙古自治区,呼和浩特市,托克托县', '3', 'tuoketuo', '0471', '010200', 'T', '111.19101', '40.27492'); +INSERT INTO `yoshop_region` VALUES ('359', '352', '和林格尔', '和林格尔县', '中国,内蒙古自治区,呼和浩特市,和林格尔县', '3', 'helingeer', '0471', '011500', 'H', '111.82205', '40.37892'); +INSERT INTO `yoshop_region` VALUES ('360', '352', '清水河', '清水河县', '中国,内蒙古自治区,呼和浩特市,清水河县', '3', 'qingshuihe', '0471', '011600', 'Q', '111.68316', '39.9097'); +INSERT INTO `yoshop_region` VALUES ('361', '352', '武川', '武川县', '中国,内蒙古自治区,呼和浩特市,武川县', '3', 'wuchuan', '0471', '011700', 'W', '111.45785', '41.09289'); +INSERT INTO `yoshop_region` VALUES ('362', '351', '包头', '包头市', '中国,内蒙古自治区,包头市', '2', 'baotou', '0472', '014025', 'B', '109.840405', '40.658168'); +INSERT INTO `yoshop_region` VALUES ('363', '362', '东河', '东河区', '中国,内蒙古自治区,包头市,东河区', '3', 'donghe', '0472', '014040', 'D', '110.0462', '40.58237'); +INSERT INTO `yoshop_region` VALUES ('364', '362', '昆都仑', '昆都仑区', '中国,内蒙古自治区,包头市,昆都仑区', '3', 'kundulun', '0472', '014010', 'K', '109.83862', '40.64175'); +INSERT INTO `yoshop_region` VALUES ('365', '362', '青山', '青山区', '中国,内蒙古自治区,包头市,青山区', '3', 'qingshan', '0472', '014030', 'Q', '109.90131', '40.64329'); +INSERT INTO `yoshop_region` VALUES ('366', '362', '石拐', '石拐区', '中国,内蒙古自治区,包头市,石拐区', '3', 'shiguai', '0472', '014070', 'S', '110.27322', '40.67297'); +INSERT INTO `yoshop_region` VALUES ('367', '362', '白云鄂博矿区', '白云鄂博矿区', '中国,内蒙古自治区,包头市,白云鄂博矿区', '3', 'baiyunebokuangqu', '0472', '014080', 'B', '109.97367', '41.76968'); +INSERT INTO `yoshop_region` VALUES ('368', '362', '九原', '九原区', '中国,内蒙古自治区,包头市,九原区', '3', 'jiuyuan', '0472', '014060', 'J', '109.96496', '40.60554'); +INSERT INTO `yoshop_region` VALUES ('369', '362', '土默特右旗', '土默特右旗', '中国,内蒙古自治区,包头市,土默特右旗', '3', 'tumoteyouqi', '0472', '014100', 'T', '110.52417', '40.5688'); +INSERT INTO `yoshop_region` VALUES ('370', '362', '固阳', '固阳县', '中国,内蒙古自治区,包头市,固阳县', '3', 'guyang', '0472', '014200', 'G', '110.06372', '41.01851'); +INSERT INTO `yoshop_region` VALUES ('371', '362', '达茂旗', '达尔罕茂明安联合旗', '中国,内蒙古自治区,包头市,达尔罕茂明安联合旗', '3', 'damaoqi', '0472', '014500', 'D', '110.43258', '41.69875'); +INSERT INTO `yoshop_region` VALUES ('372', '351', '乌海', '乌海市', '中国,内蒙古自治区,乌海市', '2', 'wuhai', '0473', '016000', 'W', '106.825563', '39.673734'); +INSERT INTO `yoshop_region` VALUES ('373', '372', '海勃湾', '海勃湾区', '中国,内蒙古自治区,乌海市,海勃湾区', '3', 'haibowan', '0473', '016000', 'H', '106.8222', '39.66955'); +INSERT INTO `yoshop_region` VALUES ('374', '372', '海南', '海南区', '中国,内蒙古自治区,乌海市,海南区', '3', 'hainan', '0473', '016030', 'H', '106.88656', '39.44128'); +INSERT INTO `yoshop_region` VALUES ('375', '372', '乌达', '乌达区', '中国,内蒙古自治区,乌海市,乌达区', '3', 'wuda', '0473', '016040', 'W', '106.72723', '39.505'); +INSERT INTO `yoshop_region` VALUES ('376', '351', '赤峰', '赤峰市', '中国,内蒙古自治区,赤峰市', '2', 'chifeng', '0476', '024000', 'C', '118.956806', '42.275317'); +INSERT INTO `yoshop_region` VALUES ('377', '376', '红山', '红山区', '中国,内蒙古自治区,赤峰市,红山区', '3', 'hongshan', '0476', '024020', 'H', '118.95755', '42.24312'); +INSERT INTO `yoshop_region` VALUES ('378', '376', '元宝山', '元宝山区', '中国,内蒙古自治区,赤峰市,元宝山区', '3', 'yuanbaoshan', '0476', '024076', 'Y', '119.28921', '42.04005'); +INSERT INTO `yoshop_region` VALUES ('379', '376', '松山', '松山区', '中国,内蒙古自治区,赤峰市,松山区', '3', 'songshan', '0476', '024005', 'S', '118.9328', '42.28613'); +INSERT INTO `yoshop_region` VALUES ('380', '376', '阿鲁科尔沁旗', '阿鲁科尔沁旗', '中国,内蒙古自治区,赤峰市,阿鲁科尔沁旗', '3', 'alukeerqinqi', '0476', '025550', 'A', '120.06527', '43.87988'); +INSERT INTO `yoshop_region` VALUES ('381', '376', '巴林左旗', '巴林左旗', '中国,内蒙古自治区,赤峰市,巴林左旗', '3', 'balinzuoqi', '0476', '025450', 'B', '119.38012', '43.97031'); +INSERT INTO `yoshop_region` VALUES ('382', '376', '巴林右旗', '巴林右旗', '中国,内蒙古自治区,赤峰市,巴林右旗', '3', 'balinyouqi', '0476', '025150', 'B', '118.66461', '43.53387'); +INSERT INTO `yoshop_region` VALUES ('383', '376', '林西', '林西县', '中国,内蒙古自治区,赤峰市,林西县', '3', 'linxi', '0476', '025250', 'L', '118.04733', '43.61165'); +INSERT INTO `yoshop_region` VALUES ('384', '376', '克什克腾旗', '克什克腾旗', '中国,内蒙古自治区,赤峰市,克什克腾旗', '3', 'keshiketengqi', '0476', '025350', 'K', '117.54562', '43.26501'); +INSERT INTO `yoshop_region` VALUES ('385', '376', '翁牛特旗', '翁牛特旗', '中国,内蒙古自治区,赤峰市,翁牛特旗', '3', 'wengniuteqi', '0476', '024500', 'W', '119.03042', '42.93147'); +INSERT INTO `yoshop_region` VALUES ('386', '376', '喀喇沁旗', '喀喇沁旗', '中国,内蒙古自治区,赤峰市,喀喇沁旗', '3', 'kalaqinqi', '0476', '024400', 'K', '118.70144', '41.92917'); +INSERT INTO `yoshop_region` VALUES ('387', '376', '宁城', '宁城县', '中国,内蒙古自治区,赤峰市,宁城县', '3', 'ningcheng', '0476', '024200', 'N', '119.34375', '41.59661'); +INSERT INTO `yoshop_region` VALUES ('388', '376', '敖汉旗', '敖汉旗', '中国,内蒙古自治区,赤峰市,敖汉旗', '3', 'aohanqi', '0476', '024300', 'A', '119.92163', '42.29071'); +INSERT INTO `yoshop_region` VALUES ('389', '351', '通辽', '通辽市', '中国,内蒙古自治区,通辽市', '2', 'tongliao', '0475', '028000', 'T', '122.263119', '43.617429'); +INSERT INTO `yoshop_region` VALUES ('390', '389', '科尔沁', '科尔沁区', '中国,内蒙古自治区,通辽市,科尔沁区', '3', 'keerqin', '0475', '028000', 'K', '122.25573', '43.62257'); +INSERT INTO `yoshop_region` VALUES ('391', '389', '科尔沁左翼中旗', '科尔沁左翼中旗', '中国,内蒙古自治区,通辽市,科尔沁左翼中旗', '3', 'keerqinzuoyizhongqi', '0475', '029300', 'K', '123.31912', '44.13014'); +INSERT INTO `yoshop_region` VALUES ('392', '389', '科尔沁左翼后旗', '科尔沁左翼后旗', '中国,内蒙古自治区,通辽市,科尔沁左翼后旗', '3', 'keerqinzuoyihouqi', '0475', '028100', 'K', '122.35745', '42.94897'); +INSERT INTO `yoshop_region` VALUES ('393', '389', '开鲁', '开鲁县', '中国,内蒙古自治区,通辽市,开鲁县', '3', 'kailu', '0475', '028400', 'K', '121.31884', '43.60003'); +INSERT INTO `yoshop_region` VALUES ('394', '389', '库伦旗', '库伦旗', '中国,内蒙古自治区,通辽市,库伦旗', '3', 'kulunqi', '0475', '028200', 'K', '121.776', '42.72998'); +INSERT INTO `yoshop_region` VALUES ('395', '389', '奈曼旗', '奈曼旗', '中国,内蒙古自治区,通辽市,奈曼旗', '3', 'naimanqi', '0475', '028300', 'N', '120.66348', '42.84527'); +INSERT INTO `yoshop_region` VALUES ('396', '389', '扎鲁特旗', '扎鲁特旗', '中国,内蒙古自治区,通辽市,扎鲁特旗', '3', 'zhaluteqi', '0475', '029100', 'Z', '120.91507', '44.55592'); +INSERT INTO `yoshop_region` VALUES ('397', '389', '霍林郭勒', '霍林郭勒市', '中国,内蒙古自治区,通辽市,霍林郭勒市', '3', 'huolinguole', '0475', '029200', 'H', '119.65429', '45.53454'); +INSERT INTO `yoshop_region` VALUES ('398', '351', '鄂尔多斯', '鄂尔多斯市', '中国,内蒙古自治区,鄂尔多斯市', '2', 'ordos', '0477', '017004', 'E', '109.99029', '39.817179'); +INSERT INTO `yoshop_region` VALUES ('399', '398', '东胜', '东胜区', '中国,内蒙古自治区,鄂尔多斯市,东胜区', '3', 'dongsheng', '0477', '017000', 'D', '109.96289', '39.82236'); +INSERT INTO `yoshop_region` VALUES ('400', '398', '达拉特旗', '达拉特旗', '中国,内蒙古自治区,鄂尔多斯市,达拉特旗', '3', 'dalateqi', '0477', '014300', 'D', '110.03317', '40.4001'); +INSERT INTO `yoshop_region` VALUES ('401', '398', '准格尔旗', '准格尔旗', '中国,内蒙古自治区,鄂尔多斯市,准格尔旗', '3', 'zhungeerqi', '0477', '017100', 'Z', '111.23645', '39.86783'); +INSERT INTO `yoshop_region` VALUES ('402', '398', '鄂托克前旗', '鄂托克前旗', '中国,内蒙古自治区,鄂尔多斯市,鄂托克前旗', '3', 'etuokeqianqi', '0477', '016200', 'E', '107.48403', '38.18396'); +INSERT INTO `yoshop_region` VALUES ('403', '398', '鄂托克旗', '鄂托克旗', '中国,内蒙古自治区,鄂尔多斯市,鄂托克旗', '3', 'etuokeqi', '0477', '016100', 'E', '107.98226', '39.09456'); +INSERT INTO `yoshop_region` VALUES ('404', '398', '杭锦旗', '杭锦旗', '中国,内蒙古自治区,鄂尔多斯市,杭锦旗', '3', 'hangjinqi', '0477', '017400', 'H', '108.72934', '39.84023'); +INSERT INTO `yoshop_region` VALUES ('405', '398', '乌审旗', '乌审旗', '中国,内蒙古自治区,鄂尔多斯市,乌审旗', '3', 'wushenqi', '0477', '017300', 'W', '108.8461', '38.59092'); +INSERT INTO `yoshop_region` VALUES ('406', '398', '伊金霍洛旗', '伊金霍洛旗', '中国,内蒙古自治区,鄂尔多斯市,伊金霍洛旗', '3', 'yijinhuoluoqi', '0477', '017200', 'Y', '109.74908', '39.57393'); +INSERT INTO `yoshop_region` VALUES ('407', '351', '呼伦贝尔', '呼伦贝尔市', '中国,内蒙古自治区,呼伦贝尔市', '2', 'hulunber', '0470', '021008', 'H', '119.758168', '49.215333'); +INSERT INTO `yoshop_region` VALUES ('408', '407', '海拉尔', '海拉尔区', '中国,内蒙古自治区,呼伦贝尔市,海拉尔区', '3', 'hailaer', '0470', '021000', 'H', '119.7364', '49.2122'); +INSERT INTO `yoshop_region` VALUES ('409', '407', '扎赉诺尔', '扎赉诺尔区', '中国,内蒙古自治区,呼伦贝尔市,扎赉诺尔区', '3', 'zhalainuoer', '0470', '021410', 'Z', '117.792702', '49.486943'); +INSERT INTO `yoshop_region` VALUES ('410', '407', '阿荣旗', '阿荣旗', '中国,内蒙古自治区,呼伦贝尔市,阿荣旗', '3', 'arongqi', '0470', '162750', 'A', '123.45941', '48.12581'); +INSERT INTO `yoshop_region` VALUES ('411', '407', '莫旗', '莫力达瓦达斡尔族自治旗', '中国,内蒙古自治区,呼伦贝尔市,莫力达瓦达斡尔族自治旗', '3', 'moqi', '0470', '162850', 'M', '124.51498', '48.48055'); +INSERT INTO `yoshop_region` VALUES ('412', '407', '鄂伦春', '鄂伦春自治旗', '中国,内蒙古自治区,呼伦贝尔市,鄂伦春自治旗', '3', 'elunchun', '0470', '165450', 'E', '123.72604', '50.59777'); +INSERT INTO `yoshop_region` VALUES ('413', '407', '鄂温', '鄂温克族自治旗', '中国,内蒙古自治区,呼伦贝尔市,鄂温克族自治旗', '3', 'ewen', '0470', '021100', 'E', '119.7565', '49.14284'); +INSERT INTO `yoshop_region` VALUES ('414', '407', '陈巴尔虎旗', '陈巴尔虎旗', '中国,内蒙古自治区,呼伦贝尔市,陈巴尔虎旗', '3', 'chenbaerhuqi', '0470', '021500', 'C', '119.42434', '49.32684'); +INSERT INTO `yoshop_region` VALUES ('415', '407', '新巴尔虎左旗', '新巴尔虎左旗', '中国,内蒙古自治区,呼伦贝尔市,新巴尔虎左旗', '3', 'xinbaerhuzuoqi', '0470', '021200', 'X', '118.26989', '48.21842'); +INSERT INTO `yoshop_region` VALUES ('416', '407', '新巴尔虎右旗', '新巴尔虎右旗', '中国,内蒙古自治区,呼伦贝尔市,新巴尔虎右旗', '3', 'xinbaerhuyouqi', '0470', '021300', 'X', '116.82366', '48.66473'); +INSERT INTO `yoshop_region` VALUES ('417', '407', '满洲里', '满洲里市', '中国,内蒙古自治区,呼伦贝尔市,满洲里市', '3', 'manzhouli', '0470', '021400', 'M', '117.47946', '49.58272'); +INSERT INTO `yoshop_region` VALUES ('418', '407', '牙克石', '牙克石市', '中国,内蒙古自治区,呼伦贝尔市,牙克石市', '3', 'yakeshi', '0470', '022150', 'Y', '120.7117', '49.2856'); +INSERT INTO `yoshop_region` VALUES ('419', '407', '扎兰屯', '扎兰屯市', '中国,内蒙古自治区,呼伦贝尔市,扎兰屯市', '3', 'zhalantun', '0470', '162650', 'Z', '122.73757', '48.01363'); +INSERT INTO `yoshop_region` VALUES ('420', '407', '额尔古纳', '额尔古纳市', '中国,内蒙古自治区,呼伦贝尔市,额尔古纳市', '3', 'eerguna', '0470', '022250', 'E', '120.19094', '50.24249'); +INSERT INTO `yoshop_region` VALUES ('421', '407', '根河', '根河市', '中国,内蒙古自治区,呼伦贝尔市,根河市', '3', 'genhe', '0470', '022350', 'G', '121.52197', '50.77996'); +INSERT INTO `yoshop_region` VALUES ('422', '351', '巴彦淖尔', '巴彦淖尔市', '中国,内蒙古自治区,巴彦淖尔市', '2', 'bayannur', '0478', '015001', 'B', '107.416959', '40.757402'); +INSERT INTO `yoshop_region` VALUES ('423', '422', '临河', '临河区', '中国,内蒙古自治区,巴彦淖尔市,临河区', '3', 'linhe', '0478', '015001', 'L', '107.42668', '40.75827'); +INSERT INTO `yoshop_region` VALUES ('424', '422', '五原', '五原县', '中国,内蒙古自治区,巴彦淖尔市,五原县', '3', 'wuyuan', '0478', '015100', 'W', '108.26916', '41.09631'); +INSERT INTO `yoshop_region` VALUES ('425', '422', '磴口', '磴口县', '中国,内蒙古自治区,巴彦淖尔市,磴口县', '3', 'dengkou', '0478', '015200', null, '107.00936', '40.33062'); +INSERT INTO `yoshop_region` VALUES ('426', '422', '乌拉特前旗', '乌拉特前旗', '中国,内蒙古自治区,巴彦淖尔市,乌拉特前旗', '3', 'wulateqianqi', '0478', '014400', 'W', '108.65219', '40.73649'); +INSERT INTO `yoshop_region` VALUES ('427', '422', '乌拉特中旗', '乌拉特中旗', '中国,内蒙古自治区,巴彦淖尔市,乌拉特中旗', '3', 'wulatezhongqi', '0478', '015300', 'W', '108.52587', '41.56789'); +INSERT INTO `yoshop_region` VALUES ('428', '422', '乌拉特后旗', '乌拉特后旗', '中国,内蒙古自治区,巴彦淖尔市,乌拉特后旗', '3', 'wulatehouqi', '0478', '015500', 'W', '106.98971', '41.43151'); +INSERT INTO `yoshop_region` VALUES ('429', '422', '杭锦后旗', '杭锦后旗', '中国,内蒙古自治区,巴彦淖尔市,杭锦后旗', '3', 'hangjinhouqi', '0478', '015400', 'H', '107.15133', '40.88627'); +INSERT INTO `yoshop_region` VALUES ('430', '351', '乌兰察布', '乌兰察布市', '中国,内蒙古自治区,乌兰察布市', '2', 'ulanqab', '0474', '012000', 'W', '113.114543', '41.034126'); +INSERT INTO `yoshop_region` VALUES ('431', '430', '集宁', '集宁区', '中国,内蒙古自治区,乌兰察布市,集宁区', '3', 'jining', '0474', '012000', 'J', '113.11452', '41.0353'); +INSERT INTO `yoshop_region` VALUES ('432', '430', '卓资', '卓资县', '中国,内蒙古自治区,乌兰察布市,卓资县', '3', 'zhuozi', '0474', '012300', 'Z', '112.57757', '40.89414'); +INSERT INTO `yoshop_region` VALUES ('433', '430', '化德', '化德县', '中国,内蒙古自治区,乌兰察布市,化德县', '3', 'huade', '0474', '013350', 'H', '114.01071', '41.90433'); +INSERT INTO `yoshop_region` VALUES ('434', '430', '商都', '商都县', '中国,内蒙古自治区,乌兰察布市,商都县', '3', 'shangdu', '0474', '013450', 'S', '113.57772', '41.56213'); +INSERT INTO `yoshop_region` VALUES ('435', '430', '兴和', '兴和县', '中国,内蒙古自治区,乌兰察布市,兴和县', '3', 'xinghe', '0474', '013650', 'X', '113.83395', '40.87186'); +INSERT INTO `yoshop_region` VALUES ('436', '430', '凉城', '凉城县', '中国,内蒙古自治区,乌兰察布市,凉城县', '3', 'liangcheng', '0474', '013750', 'L', '112.49569', '40.53346'); +INSERT INTO `yoshop_region` VALUES ('437', '430', '察右前旗', '察哈尔右翼前旗', '中国,内蒙古自治区,乌兰察布市,察哈尔右翼前旗', '3', 'chayouqianqi', '0474', '012200', 'C', '113.22131', '40.7788'); +INSERT INTO `yoshop_region` VALUES ('438', '430', '察右中旗', '察哈尔右翼中旗', '中国,内蒙古自治区,乌兰察布市,察哈尔右翼中旗', '3', 'chayouzhongqi', '0474', '013550', 'C', '112.63537', '41.27742'); +INSERT INTO `yoshop_region` VALUES ('439', '430', '察右后旗', '察哈尔右翼后旗', '中国,内蒙古自治区,乌兰察布市,察哈尔右翼后旗', '3', 'chayouhouqi', '0474', '012400', 'C', '113.19216', '41.43554'); +INSERT INTO `yoshop_region` VALUES ('440', '430', '四子王旗', '四子王旗', '中国,内蒙古自治区,乌兰察布市,四子王旗', '3', 'siziwangqi', '0474', '011800', 'S', '111.70654', '41.53312'); +INSERT INTO `yoshop_region` VALUES ('441', '430', '丰镇', '丰镇市', '中国,内蒙古自治区,乌兰察布市,丰镇市', '3', 'fengzhen', '0474', '012100', 'F', '113.10983', '40.4369'); +INSERT INTO `yoshop_region` VALUES ('442', '351', '兴安盟', '兴安盟', '中国,内蒙古自治区,兴安盟', '2', 'hinggan', '0482', '137401', 'X', '122.070317', '46.076268'); +INSERT INTO `yoshop_region` VALUES ('443', '442', '乌兰浩特', '乌兰浩特市', '中国,内蒙古自治区,兴安盟,乌兰浩特市', '3', 'wulanhaote', '0482', '137401', 'W', '122.06378', '46.06235'); +INSERT INTO `yoshop_region` VALUES ('444', '442', '阿尔山', '阿尔山市', '中国,内蒙古自治区,兴安盟,阿尔山市', '3', 'aershan', '0482', '137800', 'A', '119.94317', '47.17716'); +INSERT INTO `yoshop_region` VALUES ('445', '442', '科右前旗', '科尔沁右翼前旗', '中国,内蒙古自治区,兴安盟,科尔沁右翼前旗', '3', 'keyouqianqi', '0482', '137423', 'K', '121.95269', '46.0795'); +INSERT INTO `yoshop_region` VALUES ('446', '442', '科右中旗', '科尔沁右翼中旗', '中国,内蒙古自治区,兴安盟,科尔沁右翼中旗', '3', 'keyouzhongqi', '0482', '029400', 'K', '121.46807', '45.05605'); +INSERT INTO `yoshop_region` VALUES ('447', '442', '扎赉特旗', '扎赉特旗', '中国,内蒙古自治区,兴安盟,扎赉特旗', '3', 'zhalaiteqi', '0482', '137600', 'Z', '122.91229', '46.7267'); +INSERT INTO `yoshop_region` VALUES ('448', '442', '突泉', '突泉县', '中国,内蒙古自治区,兴安盟,突泉县', '3', 'tuquan', '0482', '137500', 'T', '121.59396', '45.38187'); +INSERT INTO `yoshop_region` VALUES ('449', '351', '锡林郭勒盟', '锡林郭勒盟', '中国,内蒙古自治区,锡林郭勒盟', '2', 'xilingol', '0479', '026000', 'X', '116.090996', '43.944018'); +INSERT INTO `yoshop_region` VALUES ('450', '449', '二连浩特', '二连浩特市', '中国,内蒙古自治区,锡林郭勒盟,二连浩特市', '3', 'erlianhaote', '0479', '011100', 'E', '111.98297', '43.65303'); +INSERT INTO `yoshop_region` VALUES ('451', '449', '锡林浩特', '锡林浩特市', '中国,内蒙古自治区,锡林郭勒盟,锡林浩特市', '3', 'xilinhaote', '0479', '026021', 'X', '116.08603', '43.93341'); +INSERT INTO `yoshop_region` VALUES ('452', '449', '阿巴嘎旗', '阿巴嘎旗', '中国,内蒙古自治区,锡林郭勒盟,阿巴嘎旗', '3', 'abagaqi', '0479', '011400', 'A', '114.96826', '44.02174'); +INSERT INTO `yoshop_region` VALUES ('453', '449', '苏尼特左旗', '苏尼特左旗', '中国,内蒙古自治区,锡林郭勒盟,苏尼特左旗', '3', 'sunitezuoqi', '0479', '011300', 'S', '113.6506', '43.85687'); +INSERT INTO `yoshop_region` VALUES ('454', '449', '苏尼特右旗', '苏尼特右旗', '中国,内蒙古自治区,锡林郭勒盟,苏尼特右旗', '3', 'suniteyouqi', '0479', '011200', 'S', '112.65741', '42.7469'); +INSERT INTO `yoshop_region` VALUES ('455', '449', '东乌旗', '东乌珠穆沁旗', '中国,内蒙古自治区,锡林郭勒盟,东乌珠穆沁旗', '3', 'dongwuqi', '0479', '026300', 'D', '116.97293', '45.51108'); +INSERT INTO `yoshop_region` VALUES ('456', '449', '西乌旗', '西乌珠穆沁旗', '中国,内蒙古自治区,锡林郭勒盟,西乌珠穆沁旗', '3', 'xiwuqi', '0479', '026200', 'X', '117.60983', '44.59623'); +INSERT INTO `yoshop_region` VALUES ('457', '449', '太仆寺旗', '太仆寺旗', '中国,内蒙古自治区,锡林郭勒盟,太仆寺旗', '3', 'taipusiqi', '0479', '027000', 'T', '115.28302', '41.87727'); +INSERT INTO `yoshop_region` VALUES ('458', '449', '镶黄旗', '镶黄旗', '中国,内蒙古自治区,锡林郭勒盟,镶黄旗', '3', 'xianghuangqi', '0479', '013250', 'X', '113.84472', '42.23927'); +INSERT INTO `yoshop_region` VALUES ('459', '449', '正镶白旗', '正镶白旗', '中国,内蒙古自治区,锡林郭勒盟,正镶白旗', '3', 'zhengxiangbaiqi', '0479', '013800', 'Z', '115.00067', '42.30712'); +INSERT INTO `yoshop_region` VALUES ('460', '449', '正蓝旗', '正蓝旗', '中国,内蒙古自治区,锡林郭勒盟,正蓝旗', '3', 'zhenglanqi', '0479', '027200', 'Z', '116.00363', '42.25229'); +INSERT INTO `yoshop_region` VALUES ('461', '449', '多伦', '多伦县', '中国,内蒙古自治区,锡林郭勒盟,多伦县', '3', 'duolun', '0479', '027300', 'D', '116.48565', '42.203'); +INSERT INTO `yoshop_region` VALUES ('462', '351', '阿拉善盟', '阿拉善盟', '中国,内蒙古自治区,阿拉善盟', '2', 'alxa', '0483', '750306', 'A', '105.706422', '38.844814'); +INSERT INTO `yoshop_region` VALUES ('463', '462', '阿拉善左旗', '阿拉善左旗', '中国,内蒙古自治区,阿拉善盟,阿拉善左旗', '3', 'alashanzuoqi', '0483', '750306', 'A', '105.67532', '38.8293'); +INSERT INTO `yoshop_region` VALUES ('464', '462', '阿拉善右旗', '阿拉善右旗', '中国,内蒙古自治区,阿拉善盟,阿拉善右旗', '3', 'alashanyouqi', '0483', '737300', 'A', '101.66705', '39.21533'); +INSERT INTO `yoshop_region` VALUES ('465', '462', '额济纳旗', '额济纳旗', '中国,内蒙古自治区,阿拉善盟,额济纳旗', '3', 'ejinaqi', '0483', '735400', 'E', '101.06887', '41.96755'); +INSERT INTO `yoshop_region` VALUES ('466', '0', '辽宁', '辽宁省', '中国,辽宁省', '1', 'liaoning', '', '', 'L', '123.429096', '41.796767'); +INSERT INTO `yoshop_region` VALUES ('467', '466', '沈阳', '沈阳市', '中国,辽宁省,沈阳市', '2', 'shenyang', '024', '110013', 'S', '123.429096', '41.796767'); +INSERT INTO `yoshop_region` VALUES ('468', '467', '和平', '和平区', '中国,辽宁省,沈阳市,和平区', '3', 'heping', '024', '110001', 'H', '123.4204', '41.78997'); +INSERT INTO `yoshop_region` VALUES ('469', '467', '沈河', '沈河区', '中国,辽宁省,沈阳市,沈河区', '3', 'shenhe', '024', '110011', 'S', '123.45871', '41.79625'); +INSERT INTO `yoshop_region` VALUES ('470', '467', '大东', '大东区', '中国,辽宁省,沈阳市,大东区', '3', 'dadong', '024', '110041', 'D', '123.46997', '41.80539'); +INSERT INTO `yoshop_region` VALUES ('471', '467', '皇姑', '皇姑区', '中国,辽宁省,沈阳市,皇姑区', '3', 'huanggu', '024', '110031', 'H', '123.42527', '41.82035'); +INSERT INTO `yoshop_region` VALUES ('472', '467', '铁西', '铁西区', '中国,辽宁省,沈阳市,铁西区', '3', 'tiexi', '024', '110021', 'T', '123.37675', '41.80269'); +INSERT INTO `yoshop_region` VALUES ('473', '467', '苏家屯', '苏家屯区', '中国,辽宁省,沈阳市,苏家屯区', '3', 'sujiatun', '024', '110101', 'S', '123.34405', '41.66475'); +INSERT INTO `yoshop_region` VALUES ('474', '467', '浑南', '浑南区', '中国,辽宁省,沈阳市,浑南区', '3', 'hunnan', '024', '110015', 'H', '123.457707', '41.719450'); +INSERT INTO `yoshop_region` VALUES ('475', '467', '沈北新区', '沈北新区', '中国,辽宁省,沈阳市,沈北新区', '3', 'shenbeixinqu', '024', '110121', 'S', '123.52658', '42.05297'); +INSERT INTO `yoshop_region` VALUES ('476', '467', '于洪', '于洪区', '中国,辽宁省,沈阳市,于洪区', '3', 'yuhong', '024', '110141', 'Y', '123.30807', '41.794'); +INSERT INTO `yoshop_region` VALUES ('477', '467', '辽中', '辽中县', '中国,辽宁省,沈阳市,辽中县', '3', 'liaozhong', '024', '110200', 'L', '122.72659', '41.51302'); +INSERT INTO `yoshop_region` VALUES ('478', '467', '康平', '康平县', '中国,辽宁省,沈阳市,康平县', '3', 'kangping', '024', '110500', 'K', '123.35446', '42.75081'); +INSERT INTO `yoshop_region` VALUES ('479', '467', '法库', '法库县', '中国,辽宁省,沈阳市,法库县', '3', 'faku', '024', '110400', 'F', '123.41214', '42.50608'); +INSERT INTO `yoshop_region` VALUES ('480', '467', '新民', '新民市', '中国,辽宁省,沈阳市,新民市', '3', 'xinmin', '024', '110300', 'X', '122.82867', '41.99847'); +INSERT INTO `yoshop_region` VALUES ('481', '466', '大连', '大连市', '中国,辽宁省,大连市', '2', 'dalian', '0411', '116011', 'D', '121.618622', '38.91459'); +INSERT INTO `yoshop_region` VALUES ('482', '481', '中山', '中山区', '中国,辽宁省,大连市,中山区', '3', 'zhongshan', '0411', '116001', 'Z', '121.64465', '38.91859'); +INSERT INTO `yoshop_region` VALUES ('483', '481', '西岗', '西岗区', '中国,辽宁省,大连市,西岗区', '3', 'xigang', '0411', '116011', 'X', '121.61238', '38.91469'); +INSERT INTO `yoshop_region` VALUES ('484', '481', '沙河口', '沙河口区', '中国,辽宁省,大连市,沙河口区', '3', 'shahekou', '0411', '116021', 'S', '121.58017', '38.90536'); +INSERT INTO `yoshop_region` VALUES ('485', '481', '甘井子', '甘井子区', '中国,辽宁省,大连市,甘井子区', '3', 'ganjingzi', '0411', '116033', 'G', '121.56567', '38.95017'); +INSERT INTO `yoshop_region` VALUES ('486', '481', '旅顺口', '旅顺口区', '中国,辽宁省,大连市,旅顺口区', '3', 'lvshunkou', '0411', '116041', 'L', '121.26202', '38.85125'); +INSERT INTO `yoshop_region` VALUES ('487', '481', '金州', '金州区', '中国,辽宁省,大连市,金州区', '3', 'jinzhou', '0411', '116100', 'J', '121.71893', '39.1004'); +INSERT INTO `yoshop_region` VALUES ('488', '481', '长海', '长海县', '中国,辽宁省,大连市,长海县', '3', 'changhai', '0411', '116500', 'C', '122.58859', '39.27274'); +INSERT INTO `yoshop_region` VALUES ('489', '481', '瓦房店', '瓦房店市', '中国,辽宁省,大连市,瓦房店市', '3', 'wafangdian', '0411', '116300', 'W', '121.98104', '39.62843'); +INSERT INTO `yoshop_region` VALUES ('490', '481', '普兰店', '普兰店市', '中国,辽宁省,大连市,普兰店市', '3', 'pulandian', '0411', '116200', 'P', '121.96316', '39.39465'); +INSERT INTO `yoshop_region` VALUES ('491', '481', '庄河', '庄河市', '中国,辽宁省,大连市,庄河市', '3', 'zhuanghe', '0411', '116400', 'Z', '122.96725', '39.68815'); +INSERT INTO `yoshop_region` VALUES ('492', '466', '鞍山', '鞍山市', '中国,辽宁省,鞍山市', '2', 'anshan', '0412', '114001', 'A', '122.995632', '41.110626'); +INSERT INTO `yoshop_region` VALUES ('493', '492', '铁东', '铁东区', '中国,辽宁省,鞍山市,铁东区', '3', 'tiedong', '0412', '114001', 'T', '122.99085', '41.08975'); +INSERT INTO `yoshop_region` VALUES ('494', '492', '铁西', '铁西区', '中国,辽宁省,鞍山市,铁西区', '3', 'tiexi', '0413', '114013', 'T', '122.96967', '41.11977'); +INSERT INTO `yoshop_region` VALUES ('495', '492', '立山', '立山区', '中国,辽宁省,鞍山市,立山区', '3', 'lishan', '0414', '114031', 'L', '123.02948', '41.15008'); +INSERT INTO `yoshop_region` VALUES ('496', '492', '千山', '千山区', '中国,辽宁省,鞍山市,千山区', '3', 'qianshan', '0415', '114041', 'Q', '122.96048', '41.07507'); +INSERT INTO `yoshop_region` VALUES ('497', '492', '台安', '台安县', '中国,辽宁省,鞍山市,台安县', '3', 'tai\'an', '0417', '114100', 'T', '122.43585', '41.41265'); +INSERT INTO `yoshop_region` VALUES ('498', '492', '岫岩', '岫岩满族自治县', '中国,辽宁省,鞍山市,岫岩满族自治县', '3', 'xiuyan', '0418', '114300', null, '123.28875', '40.27996'); +INSERT INTO `yoshop_region` VALUES ('499', '492', '海城', '海城市', '中国,辽宁省,鞍山市,海城市', '3', 'haicheng', '0416', '114200', 'H', '122.68457', '40.88142'); +INSERT INTO `yoshop_region` VALUES ('500', '466', '抚顺', '抚顺市', '中国,辽宁省,抚顺市', '2', 'fushun', '024', '113008', 'F', '123.921109', '41.875956'); +INSERT INTO `yoshop_region` VALUES ('501', '500', '新抚', '新抚区', '中国,辽宁省,抚顺市,新抚区', '3', 'xinfu', '024', '113008', 'X', '123.91264', '41.86205'); +INSERT INTO `yoshop_region` VALUES ('502', '500', '东洲', '东洲区', '中国,辽宁省,抚顺市,东洲区', '3', 'dongzhou', '024', '113003', 'D', '124.03759', '41.8519'); +INSERT INTO `yoshop_region` VALUES ('503', '500', '望花', '望花区', '中国,辽宁省,抚顺市,望花区', '3', 'wanghua', '024', '113001', 'W', '123.78283', '41.85532'); +INSERT INTO `yoshop_region` VALUES ('504', '500', '顺城', '顺城区', '中国,辽宁省,抚顺市,顺城区', '3', 'shuncheng', '024', '113006', 'S', '123.94506', '41.88321'); +INSERT INTO `yoshop_region` VALUES ('505', '500', '抚顺', '抚顺县', '中国,辽宁省,抚顺市,抚顺县', '3', 'fushun', '024', '113006', 'F', '124.17755', '41.71217'); +INSERT INTO `yoshop_region` VALUES ('506', '500', '新宾', '新宾满族自治县', '中国,辽宁省,抚顺市,新宾满族自治县', '3', 'xinbin', '024', '113200', 'X', '125.04049', '41.73409'); +INSERT INTO `yoshop_region` VALUES ('507', '500', '清原', '清原满族自治县', '中国,辽宁省,抚顺市,清原满族自治县', '3', 'qingyuan', '024', '113300', 'Q', '124.92807', '42.10221'); +INSERT INTO `yoshop_region` VALUES ('508', '466', '本溪', '本溪市', '中国,辽宁省,本溪市', '2', 'benxi', '0414', '117000', 'B', '123.770519', '41.297909'); +INSERT INTO `yoshop_region` VALUES ('509', '508', '平山', '平山区', '中国,辽宁省,本溪市,平山区', '3', 'pingshan', '0414', '117000', 'P', '123.76892', '41.2997'); +INSERT INTO `yoshop_region` VALUES ('510', '508', '溪湖', '溪湖区', '中国,辽宁省,本溪市,溪湖区', '3', 'xihu', '0414', '117002', 'X', '123.76764', '41.32921'); +INSERT INTO `yoshop_region` VALUES ('511', '508', '明山', '明山区', '中国,辽宁省,本溪市,明山区', '3', 'mingshan', '0414', '117021', 'M', '123.81746', '41.30827'); +INSERT INTO `yoshop_region` VALUES ('512', '508', '南芬', '南芬区', '中国,辽宁省,本溪市,南芬区', '3', 'nanfen', '0414', '117014', 'N', '123.74523', '41.1006'); +INSERT INTO `yoshop_region` VALUES ('513', '508', '本溪', '本溪满族自治县', '中国,辽宁省,本溪市,本溪满族自治县', '3', 'benxi', '0414', '117100', 'B', '124.12741', '41.30059'); +INSERT INTO `yoshop_region` VALUES ('514', '508', '桓仁', '桓仁满族自治县', '中国,辽宁省,本溪市,桓仁满族自治县', '3', 'huanren', '0414', '117200', 'H', '125.36062', '41.26798'); +INSERT INTO `yoshop_region` VALUES ('515', '466', '丹东', '丹东市', '中国,辽宁省,丹东市', '2', 'dandong', '0415', '118000', 'D', '124.383044', '40.124296'); +INSERT INTO `yoshop_region` VALUES ('516', '515', '元宝', '元宝区', '中国,辽宁省,丹东市,元宝区', '3', 'yuanbao', '0415', '118000', 'Y', '124.39575', '40.13651'); +INSERT INTO `yoshop_region` VALUES ('517', '515', '振兴', '振兴区', '中国,辽宁省,丹东市,振兴区', '3', 'zhenxing', '0415', '118002', 'Z', '124.36035', '40.10489'); +INSERT INTO `yoshop_region` VALUES ('518', '515', '振安', '振安区', '中国,辽宁省,丹东市,振安区', '3', 'zhen\'an', '0415', '118001', 'Z', '124.42816', '40.15826'); +INSERT INTO `yoshop_region` VALUES ('519', '515', '宽甸', '宽甸满族自治县', '中国,辽宁省,丹东市,宽甸满族自治县', '3', 'kuandian', '0415', '118200', 'K', '124.78247', '40.73187'); +INSERT INTO `yoshop_region` VALUES ('520', '515', '东港', '东港市', '中国,辽宁省,丹东市,东港市', '3', 'donggang', '0415', '118300', 'D', '124.16287', '39.86256'); +INSERT INTO `yoshop_region` VALUES ('521', '515', '凤城', '凤城市', '中国,辽宁省,丹东市,凤城市', '3', 'fengcheng', '0415', '118100', 'F', '124.06671', '40.45302'); +INSERT INTO `yoshop_region` VALUES ('522', '466', '锦州', '锦州市', '中国,辽宁省,锦州市', '2', 'jinzhou', '0416', '121000', 'J', '121.135742', '41.119269'); +INSERT INTO `yoshop_region` VALUES ('523', '522', '古塔', '古塔区', '中国,辽宁省,锦州市,古塔区', '3', 'guta', '0416', '121001', 'G', '121.12832', '41.11725'); +INSERT INTO `yoshop_region` VALUES ('524', '522', '凌河', '凌河区', '中国,辽宁省,锦州市,凌河区', '3', 'linghe', '0416', '121000', 'L', '121.15089', '41.11496'); +INSERT INTO `yoshop_region` VALUES ('525', '522', '太和', '太和区', '中国,辽宁省,锦州市,太和区', '3', 'taihe', '0416', '121011', 'T', '121.10354', '41.10929'); +INSERT INTO `yoshop_region` VALUES ('526', '522', '黑山', '黑山县', '中国,辽宁省,锦州市,黑山县', '3', 'heishan', '0416', '121400', 'H', '122.12081', '41.69417'); +INSERT INTO `yoshop_region` VALUES ('527', '522', '义县', '义县', '中国,辽宁省,锦州市,义县', '3', 'yixian', '0416', '121100', 'Y', '121.24035', '41.53458'); +INSERT INTO `yoshop_region` VALUES ('528', '522', '凌海', '凌海市', '中国,辽宁省,锦州市,凌海市', '3', 'linghai', '0416', '121200', 'L', '121.35705', '41.1737'); +INSERT INTO `yoshop_region` VALUES ('529', '522', '北镇', '北镇市', '中国,辽宁省,锦州市,北镇市', '3', 'beizhen', '0416', '121300', 'B', '121.79858', '41.59537'); +INSERT INTO `yoshop_region` VALUES ('530', '466', '营口', '营口市', '中国,辽宁省,营口市', '2', 'yingkou', '0417', '115003', 'Y', '122.235151', '40.667432'); +INSERT INTO `yoshop_region` VALUES ('531', '530', '站前', '站前区', '中国,辽宁省,营口市,站前区', '3', 'zhanqian', '0417', '115002', 'Z', '122.25896', '40.67266'); +INSERT INTO `yoshop_region` VALUES ('532', '530', '西市', '西市区', '中国,辽宁省,营口市,西市区', '3', 'xishi', '0417', '115004', 'X', '122.20641', '40.6664'); +INSERT INTO `yoshop_region` VALUES ('533', '530', '鲅鱼圈', '鲅鱼圈区', '中国,辽宁省,营口市,鲅鱼圈区', '3', 'bayuquan', '0417', '115007', null, '122.13266', '40.26865'); +INSERT INTO `yoshop_region` VALUES ('534', '530', '老边', '老边区', '中国,辽宁省,营口市,老边区', '3', 'laobian', '0417', '115005', 'L', '122.37996', '40.6803'); +INSERT INTO `yoshop_region` VALUES ('535', '530', '盖州', '盖州市', '中国,辽宁省,营口市,盖州市', '3', 'gaizhou', '0417', '115200', 'G', '122.35464', '40.40446'); +INSERT INTO `yoshop_region` VALUES ('536', '530', '大石桥', '大石桥市', '中国,辽宁省,营口市,大石桥市', '3', 'dashiqiao', '0417', '115100', 'D', '122.50927', '40.64567'); +INSERT INTO `yoshop_region` VALUES ('537', '466', '阜新', '阜新市', '中国,辽宁省,阜新市', '2', 'fuxin', '0418', '123000', 'F', '121.648962', '42.011796'); +INSERT INTO `yoshop_region` VALUES ('538', '537', '海州', '海州区', '中国,辽宁省,阜新市,海州区', '3', 'haizhou', '0418', '123000', 'H', '121.65626', '42.01336'); +INSERT INTO `yoshop_region` VALUES ('539', '537', '新邱', '新邱区', '中国,辽宁省,阜新市,新邱区', '3', 'xinqiu', '0418', '123005', 'X', '121.79251', '42.09181'); +INSERT INTO `yoshop_region` VALUES ('540', '537', '太平', '太平区', '中国,辽宁省,阜新市,太平区', '3', 'taiping', '0418', '123003', 'T', '121.67865', '42.01065'); +INSERT INTO `yoshop_region` VALUES ('541', '537', '清河门', '清河门区', '中国,辽宁省,阜新市,清河门区', '3', 'qinghemen', '0418', '123006', 'Q', '121.4161', '41.78309'); +INSERT INTO `yoshop_region` VALUES ('542', '537', '细河', '细河区', '中国,辽宁省,阜新市,细河区', '3', 'xihe', '0418', '123000', 'X', '121.68013', '42.02533'); +INSERT INTO `yoshop_region` VALUES ('543', '537', '阜新', '阜新蒙古族自治县', '中国,辽宁省,阜新市,阜新蒙古族自治县', '3', 'fuxin', '0418', '123100', 'F', '121.75787', '42.0651'); +INSERT INTO `yoshop_region` VALUES ('544', '537', '彰武', '彰武县', '中国,辽宁省,阜新市,彰武县', '3', 'zhangwu', '0418', '123200', 'Z', '122.54022', '42.38625'); +INSERT INTO `yoshop_region` VALUES ('545', '466', '辽阳', '辽阳市', '中国,辽宁省,辽阳市', '2', 'liaoyang', '0419', '111000', 'L', '123.18152', '41.269402'); +INSERT INTO `yoshop_region` VALUES ('546', '545', '白塔', '白塔区', '中国,辽宁省,辽阳市,白塔区', '3', 'baita', '0419', '111000', 'B', '123.1747', '41.27025'); +INSERT INTO `yoshop_region` VALUES ('547', '545', '文圣', '文圣区', '中国,辽宁省,辽阳市,文圣区', '3', 'wensheng', '0419', '111000', 'W', '123.18521', '41.26267'); +INSERT INTO `yoshop_region` VALUES ('548', '545', '宏伟', '宏伟区', '中国,辽宁省,辽阳市,宏伟区', '3', 'hongwei', '0419', '111003', 'H', '123.1929', '41.21852'); +INSERT INTO `yoshop_region` VALUES ('549', '545', '弓长岭', '弓长岭区', '中国,辽宁省,辽阳市,弓长岭区', '3', 'gongchangling', '0419', '111008', 'G', '123.41963', '41.15181'); +INSERT INTO `yoshop_region` VALUES ('550', '545', '太子河', '太子河区', '中国,辽宁省,辽阳市,太子河区', '3', 'taizihe', '0419', '111000', 'T', '123.18182', '41.25337'); +INSERT INTO `yoshop_region` VALUES ('551', '545', '辽阳', '辽阳县', '中国,辽宁省,辽阳市,辽阳县', '3', 'liaoyang', '0419', '111200', 'L', '123.10574', '41.20542'); +INSERT INTO `yoshop_region` VALUES ('552', '545', '灯塔', '灯塔市', '中国,辽宁省,辽阳市,灯塔市', '3', 'dengta', '0419', '111300', 'D', '123.33926', '41.42612'); +INSERT INTO `yoshop_region` VALUES ('553', '466', '盘锦', '盘锦市', '中国,辽宁省,盘锦市', '2', 'panjin', '0427', '124010', 'P', '122.06957', '41.124484'); +INSERT INTO `yoshop_region` VALUES ('554', '553', '双台子', '双台子区', '中国,辽宁省,盘锦市,双台子区', '3', 'shuangtaizi', '0427', '124000', 'S', '122.06011', '41.1906'); +INSERT INTO `yoshop_region` VALUES ('555', '553', '兴隆台', '兴隆台区', '中国,辽宁省,盘锦市,兴隆台区', '3', 'xinglongtai', '0427', '124010', 'X', '122.07529', '41.12402'); +INSERT INTO `yoshop_region` VALUES ('556', '553', '大洼', '大洼县', '中国,辽宁省,盘锦市,大洼县', '3', 'dawa', '0427', '124200', 'D', '122.08239', '41.00244'); +INSERT INTO `yoshop_region` VALUES ('557', '553', '盘山', '盘山县', '中国,辽宁省,盘锦市,盘山县', '3', 'panshan', '0427', '124000', 'P', '121.99777', '41.23805'); +INSERT INTO `yoshop_region` VALUES ('558', '466', '铁岭', '铁岭市', '中国,辽宁省,铁岭市', '2', 'tieling', '024', '112000', 'T', '123.844279', '42.290585'); +INSERT INTO `yoshop_region` VALUES ('559', '558', '银州', '银州区', '中国,辽宁省,铁岭市,银州区', '3', 'yinzhou', '024', '112000', 'Y', '123.8573', '42.29507'); +INSERT INTO `yoshop_region` VALUES ('560', '558', '清河', '清河区', '中国,辽宁省,铁岭市,清河区', '3', 'qinghe', '024', '112003', 'Q', '124.15911', '42.54679'); +INSERT INTO `yoshop_region` VALUES ('561', '558', '铁岭', '铁岭县', '中国,辽宁省,铁岭市,铁岭县', '3', 'tieling', '024', '112000', 'T', '123.77325', '42.22498'); +INSERT INTO `yoshop_region` VALUES ('562', '558', '西丰', '西丰县', '中国,辽宁省,铁岭市,西丰县', '3', 'xifeng', '024', '112400', 'X', '124.7304', '42.73756'); +INSERT INTO `yoshop_region` VALUES ('563', '558', '昌图', '昌图县', '中国,辽宁省,铁岭市,昌图县', '3', 'changtu', '024', '112500', 'C', '124.11206', '42.78428'); +INSERT INTO `yoshop_region` VALUES ('564', '558', '调兵山', '调兵山市', '中国,辽宁省,铁岭市,调兵山市', '3', 'diaobingshan', '024', '112700', 'D', '123.56689', '42.4675'); +INSERT INTO `yoshop_region` VALUES ('565', '558', '开原', '开原市', '中国,辽宁省,铁岭市,开原市', '3', 'kaiyuan', '024', '112300', 'K', '124.03945', '42.54585'); +INSERT INTO `yoshop_region` VALUES ('566', '466', '朝阳', '朝阳市', '中国,辽宁省,朝阳市', '2', 'chaoyang', '0421', '122000', 'C', '120.451176', '41.576758'); +INSERT INTO `yoshop_region` VALUES ('567', '566', '双塔', '双塔区', '中国,辽宁省,朝阳市,双塔区', '3', 'shuangta', '0421', '122000', 'S', '120.45385', '41.566'); +INSERT INTO `yoshop_region` VALUES ('568', '566', '龙城', '龙城区', '中国,辽宁省,朝阳市,龙城区', '3', 'longcheng', '0421', '122000', 'L', '120.43719', '41.59264'); +INSERT INTO `yoshop_region` VALUES ('569', '566', '朝阳', '朝阳县', '中国,辽宁省,朝阳市,朝阳县', '3', 'chaoyang', '0421', '122000', 'C', '120.17401', '41.4324'); +INSERT INTO `yoshop_region` VALUES ('570', '566', '建平', '建平县', '中国,辽宁省,朝阳市,建平县', '3', 'jianping', '0421', '122400', 'J', '119.64392', '41.40315'); +INSERT INTO `yoshop_region` VALUES ('571', '566', '喀喇沁左翼', '喀喇沁左翼蒙古族自治县', '中国,辽宁省,朝阳市,喀喇沁左翼蒙古族自治县', '3', 'kalaqinzuoyi', '0421', '122300', 'K', '119.74185', '41.12801'); +INSERT INTO `yoshop_region` VALUES ('572', '566', '北票', '北票市', '中国,辽宁省,朝阳市,北票市', '3', 'beipiao', '0421', '122100', 'B', '120.76977', '41.80196'); +INSERT INTO `yoshop_region` VALUES ('573', '566', '凌源', '凌源市', '中国,辽宁省,朝阳市,凌源市', '3', 'lingyuan', '0421', '122500', 'L', '119.40148', '41.24558'); +INSERT INTO `yoshop_region` VALUES ('574', '466', '葫芦岛', '葫芦岛市', '中国,辽宁省,葫芦岛市', '2', 'huludao', '0429', '125000', 'H', '120.856394', '40.755572'); +INSERT INTO `yoshop_region` VALUES ('575', '574', '连山', '连山区', '中国,辽宁省,葫芦岛市,连山区', '3', 'lianshan', '0429', '125001', 'L', '120.86393', '40.75554'); +INSERT INTO `yoshop_region` VALUES ('576', '574', '龙港', '龙港区', '中国,辽宁省,葫芦岛市,龙港区', '3', 'longgang', '0429', '125003', 'L', '120.94866', '40.71919'); +INSERT INTO `yoshop_region` VALUES ('577', '574', '南票', '南票区', '中国,辽宁省,葫芦岛市,南票区', '3', 'nanpiao', '0429', '125027', 'N', '120.74978', '41.10707'); +INSERT INTO `yoshop_region` VALUES ('578', '574', '绥中', '绥中县', '中国,辽宁省,葫芦岛市,绥中县', '3', 'suizhong', '0429', '125200', 'S', '120.34451', '40.32552'); +INSERT INTO `yoshop_region` VALUES ('579', '574', '建昌', '建昌县', '中国,辽宁省,葫芦岛市,建昌县', '3', 'jianchang', '0429', '125300', 'J', '119.8377', '40.82448'); +INSERT INTO `yoshop_region` VALUES ('580', '574', '兴城', '兴城市', '中国,辽宁省,葫芦岛市,兴城市', '3', 'xingcheng', '0429', '125100', 'X', '120.72537', '40.61492'); +INSERT INTO `yoshop_region` VALUES ('581', '466', '金普新区', '金普新区', '中国,辽宁省,金普新区', '2', 'jinpuxinqu', '0411', '116100', 'J', '121.789627', '39.055451'); +INSERT INTO `yoshop_region` VALUES ('582', '581', '金州新区', '金州新区', '中国,辽宁省,金普新区,金州新区', '3', 'jinzhouxinqu', '0411', '116100', 'J', '121.784821', '39.052252'); +INSERT INTO `yoshop_region` VALUES ('583', '581', '普湾新区', '普湾新区', '中国,辽宁省,金普新区,普湾新区', '3', 'puwanxinqu', '0411', '116200', 'P', '121.812812', '39.330093'); +INSERT INTO `yoshop_region` VALUES ('584', '581', '保税区', '保税区', '中国,辽宁省,金普新区,保税区', '3', 'baoshuiqu', '0411', '116100', 'B', '121.94289', '39.224614'); +INSERT INTO `yoshop_region` VALUES ('585', '0', '吉林', '吉林省', '中国,吉林省', '1', 'jilin', '', '', 'J', '125.3245', '43.886841'); +INSERT INTO `yoshop_region` VALUES ('586', '585', '长春', '长春市', '中国,吉林省,长春市', '2', 'changchun', '0431', '130022', 'C', '125.3245', '43.886841'); +INSERT INTO `yoshop_region` VALUES ('587', '586', '南关', '南关区', '中国,吉林省,长春市,南关区', '3', 'nanguan', '0431', '130022', 'N', '125.35035', '43.86401'); +INSERT INTO `yoshop_region` VALUES ('588', '586', '宽城', '宽城区', '中国,吉林省,长春市,宽城区', '3', 'kuancheng', '0431', '130051', 'K', '125.32635', '43.90182'); +INSERT INTO `yoshop_region` VALUES ('589', '586', '朝阳', '朝阳区', '中国,吉林省,长春市,朝阳区', '3', 'chaoyang', '0431', '130012', 'C', '125.2883', '43.83339'); +INSERT INTO `yoshop_region` VALUES ('590', '586', '二道', '二道区', '中国,吉林省,长春市,二道区', '3', 'erdao', '0431', '130031', 'E', '125.37429', '43.86501'); +INSERT INTO `yoshop_region` VALUES ('591', '586', '绿园', '绿园区', '中国,吉林省,长春市,绿园区', '3', 'lvyuan', '0431', '130062', 'L', '125.25582', '43.88045'); +INSERT INTO `yoshop_region` VALUES ('592', '586', '双阳', '双阳区', '中国,吉林省,长春市,双阳区', '3', 'shuangyang', '0431', '130600', 'S', '125.65631', '43.52803'); +INSERT INTO `yoshop_region` VALUES ('593', '586', '九台', '九台区', '中国,吉林省,长春市,九台区', '3', 'jiutai', '0431', '130500', 'J', '125.8395', '44.15163'); +INSERT INTO `yoshop_region` VALUES ('594', '586', '农安', '农安县', '中国,吉林省,长春市,农安县', '3', 'nong\'an', '0431', '130200', 'N', '125.18481', '44.43265'); +INSERT INTO `yoshop_region` VALUES ('595', '586', '榆树', '榆树市', '中国,吉林省,长春市,榆树市', '3', 'yushu', '0431', '130400', 'Y', '126.55688', '44.82523'); +INSERT INTO `yoshop_region` VALUES ('596', '586', '德惠', '德惠市', '中国,吉林省,长春市,德惠市', '3', 'dehui', '0431', '130300', 'D', '125.70538', '44.53719'); +INSERT INTO `yoshop_region` VALUES ('597', '585', '吉林', '吉林市', '中国,吉林省,吉林市', '2', 'jilin', '0432', '132011', 'J', '126.55302', '43.843577'); +INSERT INTO `yoshop_region` VALUES ('598', '597', '昌邑', '昌邑区', '中国,吉林省,吉林市,昌邑区', '3', 'changyi', '0432', '132002', 'C', '126.57424', '43.88183'); +INSERT INTO `yoshop_region` VALUES ('599', '597', '龙潭', '龙潭区', '中国,吉林省,吉林市,龙潭区', '3', 'longtan', '0432', '132021', 'L', '126.56213', '43.91054'); +INSERT INTO `yoshop_region` VALUES ('600', '597', '船营', '船营区', '中国,吉林省,吉林市,船营区', '3', 'chuanying', '0432', '132011', 'C', '126.54096', '43.83344'); +INSERT INTO `yoshop_region` VALUES ('601', '597', '丰满', '丰满区', '中国,吉林省,吉林市,丰满区', '3', 'fengman', '0432', '132013', 'F', '126.56237', '43.82236'); +INSERT INTO `yoshop_region` VALUES ('602', '597', '永吉', '永吉县', '中国,吉林省,吉林市,永吉县', '3', 'yongji', '0432', '132200', 'Y', '126.4963', '43.67197'); +INSERT INTO `yoshop_region` VALUES ('603', '597', '蛟河', '蛟河市', '中国,吉林省,吉林市,蛟河市', '3', 'jiaohe', '0432', '132500', null, '127.34426', '43.72696'); +INSERT INTO `yoshop_region` VALUES ('604', '597', '桦甸', '桦甸市', '中国,吉林省,吉林市,桦甸市', '3', 'huadian', '0432', '132400', null, '126.74624', '42.97206'); +INSERT INTO `yoshop_region` VALUES ('605', '597', '舒兰', '舒兰市', '中国,吉林省,吉林市,舒兰市', '3', 'shulan', '0432', '132600', 'S', '126.9653', '44.40582'); +INSERT INTO `yoshop_region` VALUES ('606', '597', '磐石', '磐石市', '中国,吉林省,吉林市,磐石市', '3', 'panshi', '0432', '132300', 'P', '126.0625', '42.94628'); +INSERT INTO `yoshop_region` VALUES ('607', '585', '四平', '四平市', '中国,吉林省,四平市', '2', 'siping', '0434', '136000', 'S', '124.370785', '43.170344'); +INSERT INTO `yoshop_region` VALUES ('608', '607', '铁西', '铁西区', '中国,吉林省,四平市,铁西区', '3', 'tiexi', '0434', '136000', 'T', '124.37369', '43.17456'); +INSERT INTO `yoshop_region` VALUES ('609', '607', '铁东', '铁东区', '中国,吉林省,四平市,铁东区', '3', 'tiedong', '0434', '136001', 'T', '124.40976', '43.16241'); +INSERT INTO `yoshop_region` VALUES ('610', '607', '梨树', '梨树县', '中国,吉林省,四平市,梨树县', '3', 'lishu', '0434', '136500', 'L', '124.33563', '43.30717'); +INSERT INTO `yoshop_region` VALUES ('611', '607', '伊通', '伊通满族自治县', '中国,吉林省,四平市,伊通满族自治县', '3', 'yitong', '0434', '130700', 'Y', '125.30596', '43.34434'); +INSERT INTO `yoshop_region` VALUES ('612', '607', '公主岭', '公主岭市', '中国,吉林省,四平市,公主岭市', '3', 'gongzhuling', '0434', '136100', 'G', '124.82266', '43.50453'); +INSERT INTO `yoshop_region` VALUES ('613', '607', '双辽', '双辽市', '中国,吉林省,四平市,双辽市', '3', 'shuangliao', '0434', '136400', 'S', '123.50106', '43.52099'); +INSERT INTO `yoshop_region` VALUES ('614', '585', '辽源', '辽源市', '中国,吉林省,辽源市', '2', 'liaoyuan', '0437', '136200', 'L', '125.145349', '42.902692'); +INSERT INTO `yoshop_region` VALUES ('615', '614', '龙山', '龙山区', '中国,吉林省,辽源市,龙山区', '3', 'longshan', '0437', '136200', 'L', '125.13641', '42.89714'); +INSERT INTO `yoshop_region` VALUES ('616', '614', '西安', '西安区', '中国,吉林省,辽源市,西安区', '3', 'xi\'an', '0437', '136201', 'X', '125.14904', '42.927'); +INSERT INTO `yoshop_region` VALUES ('617', '614', '东丰', '东丰县', '中国,吉林省,辽源市,东丰县', '3', 'dongfeng', '0437', '136300', 'D', '125.53244', '42.6783'); +INSERT INTO `yoshop_region` VALUES ('618', '614', '东辽', '东辽县', '中国,吉林省,辽源市,东辽县', '3', 'dongliao', '0437', '136600', 'D', '124.98596', '42.92492'); +INSERT INTO `yoshop_region` VALUES ('619', '585', '通化', '通化市', '中国,吉林省,通化市', '2', 'tonghua', '0435', '134001', 'T', '125.936501', '41.721177'); +INSERT INTO `yoshop_region` VALUES ('620', '619', '东昌', '东昌区', '中国,吉林省,通化市,东昌区', '3', 'dongchang', '0435', '134001', 'D', '125.9551', '41.72849'); +INSERT INTO `yoshop_region` VALUES ('621', '619', '二道江', '二道江区', '中国,吉林省,通化市,二道江区', '3', 'erdaojiang', '0435', '134003', 'E', '126.04257', '41.7741'); +INSERT INTO `yoshop_region` VALUES ('622', '619', '通化', '通化县', '中国,吉林省,通化市,通化县', '3', 'tonghua', '0435', '134100', 'T', '125.75936', '41.67928'); +INSERT INTO `yoshop_region` VALUES ('623', '619', '辉南', '辉南县', '中国,吉林省,通化市,辉南县', '3', 'huinan', '0435', '135100', 'H', '126.04684', '42.68497'); +INSERT INTO `yoshop_region` VALUES ('624', '619', '柳河', '柳河县', '中国,吉林省,通化市,柳河县', '3', 'liuhe', '0435', '135300', 'L', '125.74475', '42.28468'); +INSERT INTO `yoshop_region` VALUES ('625', '619', '梅河口', '梅河口市', '中国,吉林省,通化市,梅河口市', '3', 'meihekou', '0435', '135000', 'M', '125.71041', '42.53828'); +INSERT INTO `yoshop_region` VALUES ('626', '619', '集安', '集安市', '中国,吉林省,通化市,集安市', '3', 'ji\'an', '0435', '134200', 'J', '126.18829', '41.12268'); +INSERT INTO `yoshop_region` VALUES ('627', '585', '白山', '白山市', '中国,吉林省,白山市', '2', 'baishan', '0439', '134300', 'B', '126.427839', '41.942505'); +INSERT INTO `yoshop_region` VALUES ('628', '627', '浑江', '浑江区', '中国,吉林省,白山市,浑江区', '3', 'hunjiang', '0439', '134300', 'H', '126.422342', '41.945656'); +INSERT INTO `yoshop_region` VALUES ('629', '627', '江源', '江源区', '中国,吉林省,白山市,江源区', '3', 'jiangyuan', '0439', '134700', 'J', '126.59079', '42.05664'); +INSERT INTO `yoshop_region` VALUES ('630', '627', '抚松', '抚松县', '中国,吉林省,白山市,抚松县', '3', 'fusong', '0439', '134500', 'F', '127.2803', '42.34198'); +INSERT INTO `yoshop_region` VALUES ('631', '627', '靖宇', '靖宇县', '中国,吉林省,白山市,靖宇县', '3', 'jingyu', '0439', '135200', 'J', '126.81308', '42.38863'); +INSERT INTO `yoshop_region` VALUES ('632', '627', '长白', '长白朝鲜族自治县', '中国,吉林省,白山市,长白朝鲜族自治县', '3', 'changbai', '0439', '134400', 'C', '128.20047', '41.41996'); +INSERT INTO `yoshop_region` VALUES ('633', '627', '临江', '临江市', '中国,吉林省,白山市,临江市', '3', 'linjiang', '0439', '134600', 'L', '126.91751', '41.81142'); +INSERT INTO `yoshop_region` VALUES ('634', '585', '松原', '松原市', '中国,吉林省,松原市', '2', 'songyuan', '0438', '138000', 'S', '124.823608', '45.118243'); +INSERT INTO `yoshop_region` VALUES ('635', '634', '宁江', '宁江区', '中国,吉林省,松原市,宁江区', '3', 'ningjiang', '0438', '138000', 'N', '124.81689', '45.17175'); +INSERT INTO `yoshop_region` VALUES ('636', '634', '前郭尔罗斯', '前郭尔罗斯蒙古族自治县', '中国,吉林省,松原市,前郭尔罗斯蒙古族自治县', '3', 'qianguoerluosi', '0438', '138000', 'Q', '124.82351', '45.11726'); +INSERT INTO `yoshop_region` VALUES ('637', '634', '长岭', '长岭县', '中国,吉林省,松原市,长岭县', '3', 'changling', '0438', '131500', 'C', '123.96725', '44.27581'); +INSERT INTO `yoshop_region` VALUES ('638', '634', '乾安', '乾安县', '中国,吉林省,松原市,乾安县', '3', 'qian\'an', '0438', '131400', 'Q', '124.02737', '45.01068'); +INSERT INTO `yoshop_region` VALUES ('639', '634', '扶余', '扶余市', '中国,吉林省,松原市,扶余市', '3', 'fuyu', '0438', '131200', 'F', '126.042758', '44.986199'); +INSERT INTO `yoshop_region` VALUES ('640', '585', '白城', '白城市', '中国,吉林省,白城市', '2', 'baicheng', '0436', '137000', 'B', '122.841114', '45.619026'); +INSERT INTO `yoshop_region` VALUES ('641', '640', '洮北', '洮北区', '中国,吉林省,白城市,洮北区', '3', 'taobei', '0436', '137000', null, '122.85104', '45.62167'); +INSERT INTO `yoshop_region` VALUES ('642', '640', '镇赉', '镇赉县', '中国,吉林省,白城市,镇赉县', '3', 'zhenlai', '0436', '137300', 'Z', '123.19924', '45.84779'); +INSERT INTO `yoshop_region` VALUES ('643', '640', '通榆', '通榆县', '中国,吉林省,白城市,通榆县', '3', 'tongyu', '0436', '137200', 'T', '123.08761', '44.81388'); +INSERT INTO `yoshop_region` VALUES ('644', '640', '洮南', '洮南市', '中国,吉林省,白城市,洮南市', '3', 'taonan', '0436', '137100', null, '122.78772', '45.33502'); +INSERT INTO `yoshop_region` VALUES ('645', '640', '大安', '大安市', '中国,吉林省,白城市,大安市', '3', 'da\'an', '0436', '131300', 'D', '124.29519', '45.50846'); +INSERT INTO `yoshop_region` VALUES ('646', '585', '延边', '延边朝鲜族自治州', '中国,吉林省,延边朝鲜族自治州', '2', 'yanbian', '0433', '133000', 'Y', '129.513228', '42.904823'); +INSERT INTO `yoshop_region` VALUES ('647', '646', '延吉', '延吉市', '中国,吉林省,延边朝鲜族自治州,延吉市', '3', 'yanji', '0433', '133000', 'Y', '129.51357', '42.90682'); +INSERT INTO `yoshop_region` VALUES ('648', '646', '图们', '图们市', '中国,吉林省,延边朝鲜族自治州,图们市', '3', 'tumen', '0433', '133100', 'T', '129.84381', '42.96801'); +INSERT INTO `yoshop_region` VALUES ('649', '646', '敦化', '敦化市', '中国,吉林省,延边朝鲜族自治州,敦化市', '3', 'dunhua', '0433', '133700', 'D', '128.23242', '43.37304'); +INSERT INTO `yoshop_region` VALUES ('650', '646', '珲春', '珲春市', '中国,吉林省,延边朝鲜族自治州,珲春市', '3', 'hunchun', '0433', '133300', null, '130.36572', '42.86242'); +INSERT INTO `yoshop_region` VALUES ('651', '646', '龙井', '龙井市', '中国,吉林省,延边朝鲜族自治州,龙井市', '3', 'longjing', '0433', '133400', 'L', '129.42584', '42.76804'); +INSERT INTO `yoshop_region` VALUES ('652', '646', '和龙', '和龙市', '中国,吉林省,延边朝鲜族自治州,和龙市', '3', 'helong', '0433', '133500', 'H', '129.01077', '42.5464'); +INSERT INTO `yoshop_region` VALUES ('653', '646', '汪清', '汪清县', '中国,吉林省,延边朝鲜族自治州,汪清县', '3', 'wangqing', '0433', '133200', 'W', '129.77121', '43.31278'); +INSERT INTO `yoshop_region` VALUES ('654', '646', '安图', '安图县', '中国,吉林省,延边朝鲜族自治州,安图县', '3', 'antu', '0433', '133600', 'A', '128.90625', '43.11533'); +INSERT INTO `yoshop_region` VALUES ('655', '0', '黑龙江', '黑龙江省', '中国,黑龙江省', '1', 'heilongjiang', '', '', 'H', '126.642464', '45.756967'); +INSERT INTO `yoshop_region` VALUES ('656', '655', '哈尔滨', '哈尔滨市', '中国,黑龙江省,哈尔滨市', '2', 'harbin', '0451', '150010', 'H', '126.642464', '45.756967'); +INSERT INTO `yoshop_region` VALUES ('657', '656', '道里', '道里区', '中国,黑龙江省,哈尔滨市,道里区', '3', 'daoli', '0451', '150010', 'D', '126.61705', '45.75586'); +INSERT INTO `yoshop_region` VALUES ('658', '656', '南岗', '南岗区', '中国,黑龙江省,哈尔滨市,南岗区', '3', 'nangang', '0451', '150006', 'N', '126.66854', '45.75996'); +INSERT INTO `yoshop_region` VALUES ('659', '656', '道外', '道外区', '中国,黑龙江省,哈尔滨市,道外区', '3', 'daowai', '0451', '150020', 'D', '126.64938', '45.79187'); +INSERT INTO `yoshop_region` VALUES ('660', '656', '平房', '平房区', '中国,黑龙江省,哈尔滨市,平房区', '3', 'pingfang', '0451', '150060', 'P', '126.63729', '45.59777'); +INSERT INTO `yoshop_region` VALUES ('661', '656', '松北', '松北区', '中国,黑龙江省,哈尔滨市,松北区', '3', 'songbei', '0451', '150028', 'S', '126.56276', '45.80831'); +INSERT INTO `yoshop_region` VALUES ('662', '656', '香坊', '香坊区', '中国,黑龙江省,哈尔滨市,香坊区', '3', 'xiangfang', '0451', '150036', 'X', '126.67968', '45.72383'); +INSERT INTO `yoshop_region` VALUES ('663', '656', '呼兰', '呼兰区', '中国,黑龙江省,哈尔滨市,呼兰区', '3', 'hulan', '0451', '150500', 'H', '126.58792', '45.88895'); +INSERT INTO `yoshop_region` VALUES ('664', '656', '阿城', '阿城区', '中国,黑龙江省,哈尔滨市,阿城区', '3', 'a\'cheng', '0451', '150300', 'A', '126.97525', '45.54144'); +INSERT INTO `yoshop_region` VALUES ('665', '656', '双城', '双城区', '中国,黑龙江省,哈尔滨市,双城区', '3', 'shuangcheng', '0451', '150100', 'S', '126.308784', '45.377942'); +INSERT INTO `yoshop_region` VALUES ('666', '656', '依兰', '依兰县', '中国,黑龙江省,哈尔滨市,依兰县', '3', 'yilan', '0451', '154800', 'Y', '129.56817', '46.3247'); +INSERT INTO `yoshop_region` VALUES ('667', '656', '方正', '方正县', '中国,黑龙江省,哈尔滨市,方正县', '3', 'fangzheng', '0451', '150800', 'F', '128.82952', '45.85162'); +INSERT INTO `yoshop_region` VALUES ('668', '656', '宾县', '宾县', '中国,黑龙江省,哈尔滨市,宾县', '3', 'binxian', '0451', '150400', 'B', '127.48675', '45.75504'); +INSERT INTO `yoshop_region` VALUES ('669', '656', '巴彦', '巴彦县', '中国,黑龙江省,哈尔滨市,巴彦县', '3', 'bayan', '0451', '151800', 'B', '127.40799', '46.08148'); +INSERT INTO `yoshop_region` VALUES ('670', '656', '木兰', '木兰县', '中国,黑龙江省,哈尔滨市,木兰县', '3', 'mulan', '0451', '151900', 'M', '128.0448', '45.94944'); +INSERT INTO `yoshop_region` VALUES ('671', '656', '通河', '通河县', '中国,黑龙江省,哈尔滨市,通河县', '3', 'tonghe', '0451', '150900', 'T', '128.74603', '45.99007'); +INSERT INTO `yoshop_region` VALUES ('672', '656', '延寿', '延寿县', '中国,黑龙江省,哈尔滨市,延寿县', '3', 'yanshou', '0451', '150700', 'Y', '128.33419', '45.4554'); +INSERT INTO `yoshop_region` VALUES ('673', '656', '尚志', '尚志市', '中国,黑龙江省,哈尔滨市,尚志市', '3', 'shangzhi', '0451', '150600', 'S', '127.96191', '45.21736'); +INSERT INTO `yoshop_region` VALUES ('674', '656', '五常', '五常市', '中国,黑龙江省,哈尔滨市,五常市', '3', 'wuchang', '0451', '150200', 'W', '127.16751', '44.93184'); +INSERT INTO `yoshop_region` VALUES ('675', '655', '齐齐哈尔', '齐齐哈尔市', '中国,黑龙江省,齐齐哈尔市', '2', 'qiqihar', '0452', '161005', 'Q', '123.953486', '47.348079'); +INSERT INTO `yoshop_region` VALUES ('676', '675', '龙沙', '龙沙区', '中国,黑龙江省,齐齐哈尔市,龙沙区', '3', 'longsha', '0452', '161000', 'L', '123.95752', '47.31776'); +INSERT INTO `yoshop_region` VALUES ('677', '675', '建华', '建华区', '中国,黑龙江省,齐齐哈尔市,建华区', '3', 'jianhua', '0452', '161006', 'J', '124.0133', '47.36718'); +INSERT INTO `yoshop_region` VALUES ('678', '675', '铁锋', '铁锋区', '中国,黑龙江省,齐齐哈尔市,铁锋区', '3', 'tiefeng', '0452', '161000', 'T', '123.97821', '47.34075'); +INSERT INTO `yoshop_region` VALUES ('679', '675', '昂昂溪', '昂昂溪区', '中国,黑龙江省,齐齐哈尔市,昂昂溪区', '3', 'angangxi', '0452', '161031', 'A', '123.82229', '47.15513'); +INSERT INTO `yoshop_region` VALUES ('680', '675', '富拉尔基', '富拉尔基区', '中国,黑龙江省,齐齐哈尔市,富拉尔基区', '3', 'fulaerji', '0452', '161041', 'F', '123.62918', '47.20884'); +INSERT INTO `yoshop_region` VALUES ('681', '675', '碾子山', '碾子山区', '中国,黑龙江省,齐齐哈尔市,碾子山区', '3', 'nianzishan', '0452', '161046', 'N', '122.88183', '47.51662'); +INSERT INTO `yoshop_region` VALUES ('682', '675', '梅里斯', '梅里斯达斡尔族区', '中国,黑龙江省,齐齐哈尔市,梅里斯达斡尔族区', '3', 'meilisi', '0452', '161021', 'M', '123.75274', '47.30946'); +INSERT INTO `yoshop_region` VALUES ('683', '675', '龙江', '龙江县', '中国,黑龙江省,齐齐哈尔市,龙江县', '3', 'longjiang', '0452', '161100', 'L', '123.20532', '47.33868'); +INSERT INTO `yoshop_region` VALUES ('684', '675', '依安', '依安县', '中国,黑龙江省,齐齐哈尔市,依安县', '3', 'yi\'an', '0452', '161500', 'Y', '125.30896', '47.8931'); +INSERT INTO `yoshop_region` VALUES ('685', '675', '泰来', '泰来县', '中国,黑龙江省,齐齐哈尔市,泰来县', '3', 'tailai', '0452', '162400', 'T', '123.42285', '46.39386'); +INSERT INTO `yoshop_region` VALUES ('686', '675', '甘南', '甘南县', '中国,黑龙江省,齐齐哈尔市,甘南县', '3', 'gannan', '0452', '162100', 'G', '123.50317', '47.92437'); +INSERT INTO `yoshop_region` VALUES ('687', '675', '富裕', '富裕县', '中国,黑龙江省,齐齐哈尔市,富裕县', '3', 'fuyu', '0452', '161200', 'F', '124.47457', '47.77431'); +INSERT INTO `yoshop_region` VALUES ('688', '675', '克山', '克山县', '中国,黑龙江省,齐齐哈尔市,克山县', '3', 'keshan', '0452', '161600', 'K', '125.87396', '48.03265'); +INSERT INTO `yoshop_region` VALUES ('689', '675', '克东', '克东县', '中国,黑龙江省,齐齐哈尔市,克东县', '3', 'kedong', '0452', '164800', 'K', '126.24917', '48.03828'); +INSERT INTO `yoshop_region` VALUES ('690', '675', '拜泉', '拜泉县', '中国,黑龙江省,齐齐哈尔市,拜泉县', '3', 'baiquan', '0452', '164700', 'B', '126.09167', '47.60817'); +INSERT INTO `yoshop_region` VALUES ('691', '675', '讷河', '讷河市', '中国,黑龙江省,齐齐哈尔市,讷河市', '3', 'nehe', '0452', '161300', null, '124.87713', '48.48388'); +INSERT INTO `yoshop_region` VALUES ('692', '655', '鸡西', '鸡西市', '中国,黑龙江省,鸡西市', '2', 'jixi', '0467', '158100', 'J', '130.975966', '45.300046'); +INSERT INTO `yoshop_region` VALUES ('693', '692', '鸡冠', '鸡冠区', '中国,黑龙江省,鸡西市,鸡冠区', '3', 'jiguan', '0467', '158100', 'J', '130.98139', '45.30396'); +INSERT INTO `yoshop_region` VALUES ('694', '692', '恒山', '恒山区', '中国,黑龙江省,鸡西市,恒山区', '3', 'hengshan', '0467', '158130', 'H', '130.90493', '45.21071'); +INSERT INTO `yoshop_region` VALUES ('695', '692', '滴道', '滴道区', '中国,黑龙江省,鸡西市,滴道区', '3', 'didao', '0467', '158150', 'D', '130.84841', '45.35109'); +INSERT INTO `yoshop_region` VALUES ('696', '692', '梨树', '梨树区', '中国,黑龙江省,鸡西市,梨树区', '3', 'lishu', '0467', '158160', 'L', '130.69848', '45.09037'); +INSERT INTO `yoshop_region` VALUES ('697', '692', '城子河', '城子河区', '中国,黑龙江省,鸡西市,城子河区', '3', 'chengzihe', '0467', '158170', 'C', '131.01132', '45.33689'); +INSERT INTO `yoshop_region` VALUES ('698', '692', '麻山', '麻山区', '中国,黑龙江省,鸡西市,麻山区', '3', 'mashan', '0467', '158180', 'M', '130.47811', '45.21209'); +INSERT INTO `yoshop_region` VALUES ('699', '692', '鸡东', '鸡东县', '中国,黑龙江省,鸡西市,鸡东县', '3', 'jidong', '0467', '158200', 'J', '131.12423', '45.26025'); +INSERT INTO `yoshop_region` VALUES ('700', '692', '虎林', '虎林市', '中国,黑龙江省,鸡西市,虎林市', '3', 'hulin', '0467', '158400', 'H', '132.93679', '45.76291'); +INSERT INTO `yoshop_region` VALUES ('701', '692', '密山', '密山市', '中国,黑龙江省,鸡西市,密山市', '3', 'mishan', '0467', '158300', 'M', '131.84625', '45.5297'); +INSERT INTO `yoshop_region` VALUES ('702', '655', '鹤岗', '鹤岗市', '中国,黑龙江省,鹤岗市', '2', 'hegang', '0468', '154100', 'H', '130.277487', '47.332085'); +INSERT INTO `yoshop_region` VALUES ('703', '702', '向阳', '向阳区', '中国,黑龙江省,鹤岗市,向阳区', '3', 'xiangyang', '0468', '154100', 'X', '130.2943', '47.34247'); +INSERT INTO `yoshop_region` VALUES ('704', '702', '工农', '工农区', '中国,黑龙江省,鹤岗市,工农区', '3', 'gongnong', '0468', '154101', 'G', '130.27468', '47.31869'); +INSERT INTO `yoshop_region` VALUES ('705', '702', '南山', '南山区', '中国,黑龙江省,鹤岗市,南山区', '3', 'nanshan', '0468', '154104', 'N', '130.27676', '47.31404'); +INSERT INTO `yoshop_region` VALUES ('706', '702', '兴安', '兴安区', '中国,黑龙江省,鹤岗市,兴安区', '3', 'xing\'an', '0468', '154102', 'X', '130.23965', '47.2526'); +INSERT INTO `yoshop_region` VALUES ('707', '702', '东山', '东山区', '中国,黑龙江省,鹤岗市,东山区', '3', 'dongshan', '0468', '154106', 'D', '130.31706', '47.33853'); +INSERT INTO `yoshop_region` VALUES ('708', '702', '兴山', '兴山区', '中国,黑龙江省,鹤岗市,兴山区', '3', 'xingshan', '0468', '154105', 'X', '130.29271', '47.35776'); +INSERT INTO `yoshop_region` VALUES ('709', '702', '萝北', '萝北县', '中国,黑龙江省,鹤岗市,萝北县', '3', 'luobei', '0468', '154200', 'L', '130.83346', '47.57959'); +INSERT INTO `yoshop_region` VALUES ('710', '702', '绥滨', '绥滨县', '中国,黑龙江省,鹤岗市,绥滨县', '3', 'suibin', '0468', '156200', 'S', '131.86029', '47.2903'); +INSERT INTO `yoshop_region` VALUES ('711', '655', '双鸭山', '双鸭山市', '中国,黑龙江省,双鸭山市', '2', 'shuangyashan', '0469', '155100', 'S', '131.157304', '46.643442'); +INSERT INTO `yoshop_region` VALUES ('712', '711', '尖山', '尖山区', '中国,黑龙江省,双鸭山市,尖山区', '3', 'jianshan', '0469', '155100', 'J', '131.15841', '46.64635'); +INSERT INTO `yoshop_region` VALUES ('713', '711', '岭东', '岭东区', '中国,黑龙江省,双鸭山市,岭东区', '3', 'lingdong', '0469', '155120', 'L', '131.16473', '46.59043'); +INSERT INTO `yoshop_region` VALUES ('714', '711', '四方台', '四方台区', '中国,黑龙江省,双鸭山市,四方台区', '3', 'sifangtai', '0469', '155130', 'S', '131.33593', '46.59499'); +INSERT INTO `yoshop_region` VALUES ('715', '711', '宝山', '宝山区', '中国,黑龙江省,双鸭山市,宝山区', '3', 'baoshan', '0469', '155131', 'B', '131.4016', '46.57718'); +INSERT INTO `yoshop_region` VALUES ('716', '711', '集贤', '集贤县', '中国,黑龙江省,双鸭山市,集贤县', '3', 'jixian', '0469', '155900', 'J', '131.14053', '46.72678'); +INSERT INTO `yoshop_region` VALUES ('717', '711', '友谊', '友谊县', '中国,黑龙江省,双鸭山市,友谊县', '3', 'youyi', '0469', '155800', 'Y', '131.80789', '46.76739'); +INSERT INTO `yoshop_region` VALUES ('718', '711', '宝清', '宝清县', '中国,黑龙江省,双鸭山市,宝清县', '3', 'baoqing', '0469', '155600', 'B', '132.19695', '46.32716'); +INSERT INTO `yoshop_region` VALUES ('719', '711', '饶河', '饶河县', '中国,黑龙江省,双鸭山市,饶河县', '3', 'raohe', '0469', '155700', 'R', '134.01986', '46.79899'); +INSERT INTO `yoshop_region` VALUES ('720', '655', '大庆', '大庆市', '中国,黑龙江省,大庆市', '2', 'daqing', '0459', '163000', 'D', '125.11272', '46.590734'); +INSERT INTO `yoshop_region` VALUES ('721', '720', '萨尔图', '萨尔图区', '中国,黑龙江省,大庆市,萨尔图区', '3', 'saertu', '0459', '163001', 'S', '125.08792', '46.59359'); +INSERT INTO `yoshop_region` VALUES ('722', '720', '龙凤', '龙凤区', '中国,黑龙江省,大庆市,龙凤区', '3', 'longfeng', '0459', '163711', 'L', '125.11657', '46.53273'); +INSERT INTO `yoshop_region` VALUES ('723', '720', '让胡路', '让胡路区', '中国,黑龙江省,大庆市,让胡路区', '3', 'ranghulu', '0459', '163712', 'R', '124.87075', '46.6522'); +INSERT INTO `yoshop_region` VALUES ('724', '720', '红岗', '红岗区', '中国,黑龙江省,大庆市,红岗区', '3', 'honggang', '0459', '163511', 'H', '124.89248', '46.40128'); +INSERT INTO `yoshop_region` VALUES ('725', '720', '大同', '大同区', '中国,黑龙江省,大庆市,大同区', '3', 'datong', '0459', '163515', 'D', '124.81591', '46.03295'); +INSERT INTO `yoshop_region` VALUES ('726', '720', '肇州', '肇州县', '中国,黑龙江省,大庆市,肇州县', '3', 'zhaozhou', '0459', '166400', 'Z', '125.27059', '45.70414'); +INSERT INTO `yoshop_region` VALUES ('727', '720', '肇源', '肇源县', '中国,黑龙江省,大庆市,肇源县', '3', 'zhaoyuan', '0459', '166500', 'Z', '125.08456', '45.52032'); +INSERT INTO `yoshop_region` VALUES ('728', '720', '林甸', '林甸县', '中国,黑龙江省,大庆市,林甸县', '3', 'lindian', '0459', '166300', 'L', '124.87564', '47.18601'); +INSERT INTO `yoshop_region` VALUES ('729', '720', '杜尔伯特', '杜尔伯特蒙古族自治县', '中国,黑龙江省,大庆市,杜尔伯特蒙古族自治县', '3', 'duerbote', '0459', '166200', 'D', '124.44937', '46.86507'); +INSERT INTO `yoshop_region` VALUES ('730', '655', '伊春', '伊春市', '中国,黑龙江省,伊春市', '2', 'yichun', '0458', '153000', 'Y', '128.899396', '47.724775'); +INSERT INTO `yoshop_region` VALUES ('731', '730', '伊春', '伊春区', '中国,黑龙江省,伊春市,伊春区', '3', 'yichun', '0458', '153000', 'Y', '128.90752', '47.728'); +INSERT INTO `yoshop_region` VALUES ('732', '730', '南岔', '南岔区', '中国,黑龙江省,伊春市,南岔区', '3', 'nancha', '0458', '153100', 'N', '129.28362', '47.13897'); +INSERT INTO `yoshop_region` VALUES ('733', '730', '友好', '友好区', '中国,黑龙江省,伊春市,友好区', '3', 'youhao', '0458', '153031', 'Y', '128.84039', '47.85371'); +INSERT INTO `yoshop_region` VALUES ('734', '730', '西林', '西林区', '中国,黑龙江省,伊春市,西林区', '3', 'xilin', '0458', '153025', 'X', '129.31201', '47.48103'); +INSERT INTO `yoshop_region` VALUES ('735', '730', '翠峦', '翠峦区', '中国,黑龙江省,伊春市,翠峦区', '3', 'cuiluan', '0458', '153013', 'C', '128.66729', '47.72503'); +INSERT INTO `yoshop_region` VALUES ('736', '730', '新青', '新青区', '中国,黑龙江省,伊春市,新青区', '3', 'xinqing', '0458', '153036', 'X', '129.53653', '48.29067'); +INSERT INTO `yoshop_region` VALUES ('737', '730', '美溪', '美溪区', '中国,黑龙江省,伊春市,美溪区', '3', 'meixi', '0458', '153021', 'M', '129.13708', '47.63513'); +INSERT INTO `yoshop_region` VALUES ('738', '730', '金山屯', '金山屯区', '中国,黑龙江省,伊春市,金山屯区', '3', 'jinshantun', '0458', '153026', 'J', '129.43768', '47.41349'); +INSERT INTO `yoshop_region` VALUES ('739', '730', '五营', '五营区', '中国,黑龙江省,伊春市,五营区', '3', 'wuying', '0458', '153033', 'W', '129.24545', '48.10791'); +INSERT INTO `yoshop_region` VALUES ('740', '730', '乌马河', '乌马河区', '中国,黑龙江省,伊春市,乌马河区', '3', 'wumahe', '0458', '153011', 'W', '128.79672', '47.728'); +INSERT INTO `yoshop_region` VALUES ('741', '730', '汤旺河', '汤旺河区', '中国,黑龙江省,伊春市,汤旺河区', '3', 'tangwanghe', '0458', '153037', 'T', '129.57226', '48.45182'); +INSERT INTO `yoshop_region` VALUES ('742', '730', '带岭', '带岭区', '中国,黑龙江省,伊春市,带岭区', '3', 'dailing', '0458', '153106', 'D', '129.02352', '47.02553'); +INSERT INTO `yoshop_region` VALUES ('743', '730', '乌伊岭', '乌伊岭区', '中国,黑龙江省,伊春市,乌伊岭区', '3', 'wuyiling', '0458', '153038', 'W', '129.43981', '48.59602'); +INSERT INTO `yoshop_region` VALUES ('744', '730', '红星', '红星区', '中国,黑龙江省,伊春市,红星区', '3', 'hongxing', '0458', '153035', 'H', '129.3887', '48.23944'); +INSERT INTO `yoshop_region` VALUES ('745', '730', '上甘岭', '上甘岭区', '中国,黑龙江省,伊春市,上甘岭区', '3', 'shangganling', '0458', '153032', 'S', '129.02447', '47.97522'); +INSERT INTO `yoshop_region` VALUES ('746', '730', '嘉荫', '嘉荫县', '中国,黑龙江省,伊春市,嘉荫县', '3', 'jiayin', '0458', '153200', 'J', '130.39825', '48.8917'); +INSERT INTO `yoshop_region` VALUES ('747', '730', '铁力', '铁力市', '中国,黑龙江省,伊春市,铁力市', '3', 'tieli', '0458', '152500', 'T', '128.0317', '46.98571'); +INSERT INTO `yoshop_region` VALUES ('748', '655', '佳木斯', '佳木斯市', '中国,黑龙江省,佳木斯市', '2', 'jiamusi', '0454', '154002', 'J', '130.361634', '46.809606'); +INSERT INTO `yoshop_region` VALUES ('749', '748', '向阳', '向阳区', '中国,黑龙江省,佳木斯市,向阳区', '3', 'xiangyang', '0454', '154002', 'X', '130.36519', '46.80778'); +INSERT INTO `yoshop_region` VALUES ('750', '748', '前进', '前进区', '中国,黑龙江省,佳木斯市,前进区', '3', 'qianjin', '0454', '154002', 'Q', '130.37497', '46.81401'); +INSERT INTO `yoshop_region` VALUES ('751', '748', '东风', '东风区', '中国,黑龙江省,佳木斯市,东风区', '3', 'dongfeng', '0454', '154005', 'D', '130.40366', '46.82257'); +INSERT INTO `yoshop_region` VALUES ('752', '748', '郊区', '郊区', '中国,黑龙江省,佳木斯市,郊区', '3', 'jiaoqu', '0454', '154004', 'J', '130.32731', '46.80958'); +INSERT INTO `yoshop_region` VALUES ('753', '748', '桦南', '桦南县', '中国,黑龙江省,佳木斯市,桦南县', '3', 'huanan', '0454', '154400', null, '130.55361', '46.23921'); +INSERT INTO `yoshop_region` VALUES ('754', '748', '桦川', '桦川县', '中国,黑龙江省,佳木斯市,桦川县', '3', 'huachuan', '0454', '154300', null, '130.71893', '47.02297'); +INSERT INTO `yoshop_region` VALUES ('755', '748', '汤原', '汤原县', '中国,黑龙江省,佳木斯市,汤原县', '3', 'tangyuan', '0454', '154700', 'T', '129.90966', '46.72755'); +INSERT INTO `yoshop_region` VALUES ('756', '748', '抚远', '抚远县', '中国,黑龙江省,佳木斯市,抚远县', '3', 'fuyuan', '0454', '156500', 'F', '134.29595', '48.36794'); +INSERT INTO `yoshop_region` VALUES ('757', '748', '同江', '同江市', '中国,黑龙江省,佳木斯市,同江市', '3', 'tongjiang', '0454', '156400', 'T', '132.51095', '47.64211'); +INSERT INTO `yoshop_region` VALUES ('758', '748', '富锦', '富锦市', '中国,黑龙江省,佳木斯市,富锦市', '3', 'fujin', '0454', '156100', 'F', '132.03707', '47.25132'); +INSERT INTO `yoshop_region` VALUES ('759', '655', '七台河', '七台河市', '中国,黑龙江省,七台河市', '2', 'qitaihe', '0464', '154600', 'Q', '131.015584', '45.771266'); +INSERT INTO `yoshop_region` VALUES ('760', '759', '新兴', '新兴区', '中国,黑龙江省,七台河市,新兴区', '3', 'xinxing', '0464', '154604', 'X', '130.93212', '45.81624'); +INSERT INTO `yoshop_region` VALUES ('761', '759', '桃山', '桃山区', '中国,黑龙江省,七台河市,桃山区', '3', 'taoshan', '0464', '154600', 'T', '131.01786', '45.76782'); +INSERT INTO `yoshop_region` VALUES ('762', '759', '茄子河', '茄子河区', '中国,黑龙江省,七台河市,茄子河区', '3', 'qiezihe', '0464', '154622', 'Q', '131.06807', '45.78519'); +INSERT INTO `yoshop_region` VALUES ('763', '759', '勃利', '勃利县', '中国,黑龙江省,七台河市,勃利县', '3', 'boli', '0464', '154500', 'B', '130.59179', '45.755'); +INSERT INTO `yoshop_region` VALUES ('764', '655', '牡丹江', '牡丹江市', '中国,黑龙江省,牡丹江市', '2', 'mudanjiang', '0453', '157000', 'M', '129.618602', '44.582962'); +INSERT INTO `yoshop_region` VALUES ('765', '764', '东安', '东安区', '中国,黑龙江省,牡丹江市,东安区', '3', 'dong\'an', '0453', '157000', 'D', '129.62665', '44.58133'); +INSERT INTO `yoshop_region` VALUES ('766', '764', '阳明', '阳明区', '中国,黑龙江省,牡丹江市,阳明区', '3', 'yangming', '0453', '157013', 'Y', '129.63547', '44.59603'); +INSERT INTO `yoshop_region` VALUES ('767', '764', '爱民', '爱民区', '中国,黑龙江省,牡丹江市,爱民区', '3', 'aimin', '0453', '157009', 'A', '129.59077', '44.59648'); +INSERT INTO `yoshop_region` VALUES ('768', '764', '西安', '西安区', '中国,黑龙江省,牡丹江市,西安区', '3', 'xi\'an', '0453', '157000', 'X', '129.61616', '44.57766'); +INSERT INTO `yoshop_region` VALUES ('769', '764', '东宁', '东宁县', '中国,黑龙江省,牡丹江市,东宁县', '3', 'dongning', '0453', '157200', 'D', '131.12793', '44.0661'); +INSERT INTO `yoshop_region` VALUES ('770', '764', '林口', '林口县', '中国,黑龙江省,牡丹江市,林口县', '3', 'linkou', '0453', '157600', 'L', '130.28393', '45.27809'); +INSERT INTO `yoshop_region` VALUES ('771', '764', '绥芬河', '绥芬河市', '中国,黑龙江省,牡丹江市,绥芬河市', '3', 'suifenhe', '0453', '157300', 'S', '131.15139', '44.41249'); +INSERT INTO `yoshop_region` VALUES ('772', '764', '海林', '海林市', '中国,黑龙江省,牡丹江市,海林市', '3', 'hailin', '0453', '157100', 'H', '129.38156', '44.59'); +INSERT INTO `yoshop_region` VALUES ('773', '764', '宁安', '宁安市', '中国,黑龙江省,牡丹江市,宁安市', '3', 'ning\'an', '0453', '157400', 'N', '129.48303', '44.34016'); +INSERT INTO `yoshop_region` VALUES ('774', '764', '穆棱', '穆棱市', '中国,黑龙江省,牡丹江市,穆棱市', '3', 'muling', '0453', '157500', 'M', '130.52465', '44.919'); +INSERT INTO `yoshop_region` VALUES ('775', '655', '黑河', '黑河市', '中国,黑龙江省,黑河市', '2', 'heihe', '0456', '164300', 'H', '127.499023', '50.249585'); +INSERT INTO `yoshop_region` VALUES ('776', '775', '爱辉', '爱辉区', '中国,黑龙江省,黑河市,爱辉区', '3', 'aihui', '0456', '164300', 'A', '127.50074', '50.25202'); +INSERT INTO `yoshop_region` VALUES ('777', '775', '嫩江', '嫩江县', '中国,黑龙江省,黑河市,嫩江县', '3', 'nenjiang', '0456', '161400', 'N', '125.22607', '49.17844'); +INSERT INTO `yoshop_region` VALUES ('778', '775', '逊克', '逊克县', '中国,黑龙江省,黑河市,逊克县', '3', 'xunke', '0456', '164400', 'X', '128.47882', '49.57983'); +INSERT INTO `yoshop_region` VALUES ('779', '775', '孙吴', '孙吴县', '中国,黑龙江省,黑河市,孙吴县', '3', 'sunwu', '0456', '164200', 'S', '127.33599', '49.42539'); +INSERT INTO `yoshop_region` VALUES ('780', '775', '北安', '北安市', '中国,黑龙江省,黑河市,北安市', '3', 'bei\'an', '0456', '164000', 'B', '126.48193', '48.23872'); +INSERT INTO `yoshop_region` VALUES ('781', '775', '五大连池', '五大连池市', '中国,黑龙江省,黑河市,五大连池市', '3', 'wudalianchi', '0456', '164100', 'W', '126.20294', '48.51507'); +INSERT INTO `yoshop_region` VALUES ('782', '655', '绥化', '绥化市', '中国,黑龙江省,绥化市', '2', 'suihua', '0455', '152000', 'S', '126.99293', '46.637393'); +INSERT INTO `yoshop_region` VALUES ('783', '782', '北林', '北林区', '中国,黑龙江省,绥化市,北林区', '3', 'beilin', '0455', '152000', 'B', '126.98564', '46.63735'); +INSERT INTO `yoshop_region` VALUES ('784', '782', '望奎', '望奎县', '中国,黑龙江省,绥化市,望奎县', '3', 'wangkui', '0455', '152100', 'W', '126.48187', '46.83079'); +INSERT INTO `yoshop_region` VALUES ('785', '782', '兰西', '兰西县', '中国,黑龙江省,绥化市,兰西县', '3', 'lanxi', '0455', '151500', 'L', '126.28994', '46.2525'); +INSERT INTO `yoshop_region` VALUES ('786', '782', '青冈', '青冈县', '中国,黑龙江省,绥化市,青冈县', '3', 'qinggang', '0455', '151600', 'Q', '126.11325', '46.68534'); +INSERT INTO `yoshop_region` VALUES ('787', '782', '庆安', '庆安县', '中国,黑龙江省,绥化市,庆安县', '3', 'qing\'an', '0455', '152400', 'Q', '127.50753', '46.88016'); +INSERT INTO `yoshop_region` VALUES ('788', '782', '明水', '明水县', '中国,黑龙江省,绥化市,明水县', '3', 'mingshui', '0455', '151700', 'M', '125.90594', '47.17327'); +INSERT INTO `yoshop_region` VALUES ('789', '782', '绥棱', '绥棱县', '中国,黑龙江省,绥化市,绥棱县', '3', 'suileng', '0455', '152200', 'S', '127.11584', '47.24267'); +INSERT INTO `yoshop_region` VALUES ('790', '782', '安达', '安达市', '中国,黑龙江省,绥化市,安达市', '3', 'anda', '0455', '151400', 'A', '125.34375', '46.4177'); +INSERT INTO `yoshop_region` VALUES ('791', '782', '肇东', '肇东市', '中国,黑龙江省,绥化市,肇东市', '3', 'zhaodong', '0455', '151100', 'Z', '125.96243', '46.05131'); +INSERT INTO `yoshop_region` VALUES ('792', '782', '海伦', '海伦市', '中国,黑龙江省,绥化市,海伦市', '3', 'hailun', '0455', '152300', 'H', '126.9682', '47.46093'); +INSERT INTO `yoshop_region` VALUES ('793', '655', '大兴安岭', '大兴安岭地区', '中国,黑龙江省,大兴安岭地区', '2', 'daxinganling', '0457', '165000', 'D', '124.711526', '52.335262'); +INSERT INTO `yoshop_region` VALUES ('794', '793', '加格达奇', '加格达奇区', '中国,黑龙江省,大兴安岭地区,加格达奇区', '3', 'jiagedaqi', '0457', '165000', 'J', '124.30954', '51.98144'); +INSERT INTO `yoshop_region` VALUES ('795', '793', '新林', '新林区', '中国,黑龙江省,大兴安岭地区,新林区', '3', 'xinlin', '0457', '165000', 'X', '124.397983', '51.67341'); +INSERT INTO `yoshop_region` VALUES ('796', '793', '松岭', '松岭区', '中国,黑龙江省,大兴安岭地区,松岭区', '3', 'songling', '0457', '165000', 'S', '124.189713', '51.985453'); +INSERT INTO `yoshop_region` VALUES ('797', '793', '呼中', '呼中区', '中国,黑龙江省,大兴安岭地区,呼中区', '3', 'huzhong', '0457', '165000', 'H', '123.60009', '52.03346'); +INSERT INTO `yoshop_region` VALUES ('798', '793', '呼玛', '呼玛县', '中国,黑龙江省,大兴安岭地区,呼玛县', '3', 'huma', '0457', '165100', 'H', '126.66174', '51.73112'); +INSERT INTO `yoshop_region` VALUES ('799', '793', '塔河', '塔河县', '中国,黑龙江省,大兴安岭地区,塔河县', '3', 'tahe', '0457', '165200', 'T', '124.70999', '52.33431'); +INSERT INTO `yoshop_region` VALUES ('800', '793', '漠河', '漠河县', '中国,黑龙江省,大兴安岭地区,漠河县', '3', 'mohe', '0457', '165300', 'M', '122.53759', '52.97003'); +INSERT INTO `yoshop_region` VALUES ('801', '0', '上海', '上海市', '中国,上海', '1', 'shanghai', '', '', 'S', '121.472644', '31.231706'); +INSERT INTO `yoshop_region` VALUES ('802', '801', '上海', '上海市', '中国,上海,上海市', '2', 'shanghai', '021', '200000', 'S', '121.472644', '31.231706'); +INSERT INTO `yoshop_region` VALUES ('803', '802', '黄浦', '黄浦区', '中国,上海,上海市,黄浦区', '3', 'huangpu', '021', '200001', 'H', '121.49295', '31.22337'); +INSERT INTO `yoshop_region` VALUES ('804', '802', '徐汇', '徐汇区', '中国,上海,上海市,徐汇区', '3', 'xuhui', '021', '200030', 'X', '121.43676', '31.18831'); +INSERT INTO `yoshop_region` VALUES ('805', '802', '长宁', '长宁区', '中国,上海,上海市,长宁区', '3', 'changning', '021', '200050', 'C', '121.42462', '31.22036'); +INSERT INTO `yoshop_region` VALUES ('806', '802', '静安', '静安区', '中国,上海,上海市,静安区', '3', 'jing\'an', '021', '200040', 'J', '121.4444', '31.22884'); +INSERT INTO `yoshop_region` VALUES ('807', '802', '普陀', '普陀区', '中国,上海,上海市,普陀区', '3', 'putuo', '021', '200333', 'P', '121.39703', '31.24951'); +INSERT INTO `yoshop_region` VALUES ('808', '802', '闸北', '闸北区', '中国,上海,上海市,闸北区', '3', 'zhabei', '021', '200070', 'Z', '121.44636', '31.28075'); +INSERT INTO `yoshop_region` VALUES ('809', '802', '虹口', '虹口区', '中国,上海,上海市,虹口区', '3', 'hongkou', '021', '200086', 'H', '121.48162', '31.27788'); +INSERT INTO `yoshop_region` VALUES ('810', '802', '杨浦', '杨浦区', '中国,上海,上海市,杨浦区', '3', 'yangpu', '021', '200082', 'Y', '121.526', '31.2595'); +INSERT INTO `yoshop_region` VALUES ('811', '802', '闵行', '闵行区', '中国,上海,上海市,闵行区', '3', 'minhang', '021', '201100', null, '121.38162', '31.11246'); +INSERT INTO `yoshop_region` VALUES ('812', '802', '宝山', '宝山区', '中国,上海,上海市,宝山区', '3', 'baoshan', '021', '201900', 'B', '121.4891', '31.4045'); +INSERT INTO `yoshop_region` VALUES ('813', '802', '嘉定', '嘉定区', '中国,上海,上海市,嘉定区', '3', 'jiading', '021', '201800', 'J', '121.2655', '31.37473'); +INSERT INTO `yoshop_region` VALUES ('814', '802', '浦东', '浦东新区', '中国,上海,上海市,浦东新区', '3', 'pudong', '021', '200135', 'P', '121.5447', '31.22249'); +INSERT INTO `yoshop_region` VALUES ('815', '802', '金山', '金山区', '中国,上海,上海市,金山区', '3', 'jinshan', '021', '200540', 'J', '121.34164', '30.74163'); +INSERT INTO `yoshop_region` VALUES ('816', '802', '松江', '松江区', '中国,上海,上海市,松江区', '3', 'songjiang', '021', '201600', 'S', '121.22879', '31.03222'); +INSERT INTO `yoshop_region` VALUES ('817', '802', '青浦', '青浦区', '中国,上海,上海市,青浦区', '3', 'qingpu', '021', '201700', 'Q', '121.12417', '31.14974'); +INSERT INTO `yoshop_region` VALUES ('818', '802', '奉贤', '奉贤区', '中国,上海,上海市,奉贤区', '3', 'fengxian', '021', '201400', 'F', '121.47412', '30.9179'); +INSERT INTO `yoshop_region` VALUES ('819', '802', '崇明', '崇明县', '中国,上海,上海市,崇明县', '3', 'chongming', '021', '202150', 'C', '121.39758', '31.62278'); +INSERT INTO `yoshop_region` VALUES ('820', '0', '江苏', '江苏省', '中国,江苏省', '1', 'jiangsu', '', '', 'J', '118.767413', '32.041544'); +INSERT INTO `yoshop_region` VALUES ('821', '820', '南京', '南京市', '中国,江苏省,南京市', '2', 'nanjing', '025', '210008', 'N', '118.767413', '32.041544'); +INSERT INTO `yoshop_region` VALUES ('822', '821', '玄武', '玄武区', '中国,江苏省,南京市,玄武区', '3', 'xuanwu', '025', '210018', 'X', '118.79772', '32.04856'); +INSERT INTO `yoshop_region` VALUES ('823', '821', '秦淮', '秦淮区', '中国,江苏省,南京市,秦淮区', '3', 'qinhuai', '025', '210001', 'Q', '118.79815', '32.01112'); +INSERT INTO `yoshop_region` VALUES ('824', '821', '建邺', '建邺区', '中国,江苏省,南京市,建邺区', '3', 'jianye', '025', '210004', 'J', '118.76641', '32.03096'); +INSERT INTO `yoshop_region` VALUES ('825', '821', '鼓楼', '鼓楼区', '中国,江苏省,南京市,鼓楼区', '3', 'gulou', '025', '210009', 'G', '118.76974', '32.06632'); +INSERT INTO `yoshop_region` VALUES ('826', '821', '浦口', '浦口区', '中国,江苏省,南京市,浦口区', '3', 'pukou', '025', '211800', 'P', '118.62802', '32.05881'); +INSERT INTO `yoshop_region` VALUES ('827', '821', '栖霞', '栖霞区', '中国,江苏省,南京市,栖霞区', '3', 'qixia', '025', '210046', 'Q', '118.88064', '32.11352'); +INSERT INTO `yoshop_region` VALUES ('828', '821', '雨花台', '雨花台区', '中国,江苏省,南京市,雨花台区', '3', 'yuhuatai', '025', '210012', 'Y', '118.7799', '31.99202'); +INSERT INTO `yoshop_region` VALUES ('829', '821', '江宁', '江宁区', '中国,江苏省,南京市,江宁区', '3', 'jiangning', '025', '211100', 'J', '118.8399', '31.95263'); +INSERT INTO `yoshop_region` VALUES ('830', '821', '六合', '六合区', '中国,江苏省,南京市,六合区', '3', 'luhe', '025', '211500', 'L', '118.8413', '32.34222'); +INSERT INTO `yoshop_region` VALUES ('831', '821', '溧水', '溧水区', '中国,江苏省,南京市,溧水区', '3', 'lishui', '025', '211200', null, '119.028732', '31.653061'); +INSERT INTO `yoshop_region` VALUES ('832', '821', '高淳', '高淳区', '中国,江苏省,南京市,高淳区', '3', 'gaochun', '025', '211300', 'G', '118.87589', '31.327132'); +INSERT INTO `yoshop_region` VALUES ('833', '820', '无锡', '无锡市', '中国,江苏省,无锡市', '2', 'wuxi', '0510', '214000', 'W', '120.301663', '31.574729'); +INSERT INTO `yoshop_region` VALUES ('834', '833', '崇安', '崇安区', '中国,江苏省,无锡市,崇安区', '3', 'chong\'an', '0510', '214001', 'C', '120.29975', '31.58002'); +INSERT INTO `yoshop_region` VALUES ('835', '833', '南长', '南长区', '中国,江苏省,无锡市,南长区', '3', 'nanchang', '0510', '214021', 'N', '120.30873', '31.56359'); +INSERT INTO `yoshop_region` VALUES ('836', '833', '北塘', '北塘区', '中国,江苏省,无锡市,北塘区', '3', 'beitang', '0510', '214044', 'B', '120.29405', '31.60592'); +INSERT INTO `yoshop_region` VALUES ('837', '833', '锡山', '锡山区', '中国,江苏省,无锡市,锡山区', '3', 'xishan', '0510', '214101', 'X', '120.35699', '31.5886'); +INSERT INTO `yoshop_region` VALUES ('838', '833', '惠山', '惠山区', '中国,江苏省,无锡市,惠山区', '3', 'huishan', '0510', '214174', 'H', '120.29849', '31.68088'); +INSERT INTO `yoshop_region` VALUES ('839', '833', '滨湖', '滨湖区', '中国,江苏省,无锡市,滨湖区', '3', 'binhu', '0510', '214123', 'B', '120.29461', '31.52162'); +INSERT INTO `yoshop_region` VALUES ('840', '833', '江阴', '江阴市', '中国,江苏省,无锡市,江阴市', '3', 'jiangyin', '0510', '214431', 'J', '120.2853', '31.91996'); +INSERT INTO `yoshop_region` VALUES ('841', '833', '宜兴', '宜兴市', '中国,江苏省,无锡市,宜兴市', '3', 'yixing', '0510', '214200', 'Y', '119.82357', '31.33978'); +INSERT INTO `yoshop_region` VALUES ('842', '820', '徐州', '徐州市', '中国,江苏省,徐州市', '2', 'xuzhou', '0516', '221003', 'X', '117.184811', '34.261792'); +INSERT INTO `yoshop_region` VALUES ('843', '842', '鼓楼', '鼓楼区', '中国,江苏省,徐州市,鼓楼区', '3', 'gulou', '0516', '221005', 'G', '117.18559', '34.28851'); +INSERT INTO `yoshop_region` VALUES ('844', '842', '云龙', '云龙区', '中国,江苏省,徐州市,云龙区', '3', 'yunlong', '0516', '221007', 'Y', '117.23053', '34.24895'); +INSERT INTO `yoshop_region` VALUES ('845', '842', '贾汪', '贾汪区', '中国,江苏省,徐州市,贾汪区', '3', 'jiawang', '0516', '221003', 'J', '117.45346', '34.44264'); +INSERT INTO `yoshop_region` VALUES ('846', '842', '泉山', '泉山区', '中国,江苏省,徐州市,泉山区', '3', 'quanshan', '0516', '221006', 'Q', '117.19378', '34.24418'); +INSERT INTO `yoshop_region` VALUES ('847', '842', '铜山', '铜山区', '中国,江苏省,徐州市,铜山区', '3', 'tongshan', '0516', '221106', 'T', '117.183894', '34.19288'); +INSERT INTO `yoshop_region` VALUES ('848', '842', '丰县', '丰县', '中国,江苏省,徐州市,丰县', '3', 'fengxian', '0516', '221700', 'F', '116.59957', '34.69972'); +INSERT INTO `yoshop_region` VALUES ('849', '842', '沛县', '沛县', '中国,江苏省,徐州市,沛县', '3', 'peixian', '0516', '221600', 'P', '116.93743', '34.72163'); +INSERT INTO `yoshop_region` VALUES ('850', '842', '睢宁', '睢宁县', '中国,江苏省,徐州市,睢宁县', '3', 'suining', '0516', '221200', null, '117.94104', '33.91269'); +INSERT INTO `yoshop_region` VALUES ('851', '842', '新沂', '新沂市', '中国,江苏省,徐州市,新沂市', '3', 'xinyi', '0516', '221400', 'X', '118.35452', '34.36942'); +INSERT INTO `yoshop_region` VALUES ('852', '842', '邳州', '邳州市', '中国,江苏省,徐州市,邳州市', '3', 'pizhou', '0516', '221300', null, '117.95858', '34.33329'); +INSERT INTO `yoshop_region` VALUES ('853', '820', '常州', '常州市', '中国,江苏省,常州市', '2', 'changzhou', '0519', '213000', 'C', '119.946973', '31.772752'); +INSERT INTO `yoshop_region` VALUES ('854', '853', '天宁', '天宁区', '中国,江苏省,常州市,天宁区', '3', 'tianning', '0519', '213000', 'T', '119.95132', '31.75211'); +INSERT INTO `yoshop_region` VALUES ('855', '853', '钟楼', '钟楼区', '中国,江苏省,常州市,钟楼区', '3', 'zhonglou', '0519', '213023', 'Z', '119.90178', '31.80221'); +INSERT INTO `yoshop_region` VALUES ('856', '853', '戚墅堰', '戚墅堰区', '中国,江苏省,常州市,戚墅堰区', '3', 'qishuyan', '0519', '213025', 'Q', '120.06106', '31.71956'); +INSERT INTO `yoshop_region` VALUES ('857', '853', '新北', '新北区', '中国,江苏省,常州市,新北区', '3', 'xinbei', '0519', '213022', 'X', '119.97131', '31.83046'); +INSERT INTO `yoshop_region` VALUES ('858', '853', '武进', '武进区', '中国,江苏省,常州市,武进区', '3', 'wujin', '0519', '213100', 'W', '119.94244', '31.70086'); +INSERT INTO `yoshop_region` VALUES ('859', '853', '溧阳', '溧阳市', '中国,江苏省,常州市,溧阳市', '3', 'liyang', '0519', '213300', null, '119.4837', '31.41538'); +INSERT INTO `yoshop_region` VALUES ('860', '853', '金坛', '金坛市', '中国,江苏省,常州市,金坛市', '3', 'jintan', '0519', '213200', 'J', '119.57757', '31.74043'); +INSERT INTO `yoshop_region` VALUES ('861', '820', '苏州', '苏州市', '中国,江苏省,苏州市', '2', 'suzhou', '0512', '215002', 'S', '120.619585', '31.299379'); +INSERT INTO `yoshop_region` VALUES ('862', '861', '虎丘', '虎丘区', '中国,江苏省,苏州市,虎丘区', '3', 'huqiu', '0512', '215004', 'H', '120.57345', '31.2953'); +INSERT INTO `yoshop_region` VALUES ('863', '861', '吴中', '吴中区', '中国,江苏省,苏州市,吴中区', '3', 'wuzhong', '0512', '215128', 'W', '120.63211', '31.26226'); +INSERT INTO `yoshop_region` VALUES ('864', '861', '相城', '相城区', '中国,江苏省,苏州市,相城区', '3', 'xiangcheng', '0512', '215131', 'X', '120.64239', '31.36889'); +INSERT INTO `yoshop_region` VALUES ('865', '861', '姑苏', '姑苏区', '中国,江苏省,苏州市,姑苏区', '3', 'gusu', '0512', '215031', 'G', '120.619585', '31.299379'); +INSERT INTO `yoshop_region` VALUES ('866', '861', '吴江', '吴江区', '中国,江苏省,苏州市,吴江区', '3', 'wujiang', '0512', '215200', 'W', '120.638317', '31.159815'); +INSERT INTO `yoshop_region` VALUES ('867', '861', '常熟', '常熟市', '中国,江苏省,苏州市,常熟市', '3', 'changshu', '0512', '215500', 'C', '120.75225', '31.65374'); +INSERT INTO `yoshop_region` VALUES ('868', '861', '张家港', '张家港市', '中国,江苏省,苏州市,张家港市', '3', 'zhangjiagang', '0512', '215600', 'Z', '120.55538', '31.87532'); +INSERT INTO `yoshop_region` VALUES ('869', '861', '昆山', '昆山市', '中国,江苏省,苏州市,昆山市', '3', 'kunshan', '0512', '215300', 'K', '120.98074', '31.38464'); +INSERT INTO `yoshop_region` VALUES ('870', '861', '太仓', '太仓市', '中国,江苏省,苏州市,太仓市', '3', 'taicang', '0512', '215400', 'T', '121.10891', '31.4497'); +INSERT INTO `yoshop_region` VALUES ('871', '820', '南通', '南通市', '中国,江苏省,南通市', '2', 'nantong', '0513', '226001', 'N', '120.864608', '32.016212'); +INSERT INTO `yoshop_region` VALUES ('872', '871', '崇川', '崇川区', '中国,江苏省,南通市,崇川区', '3', 'chongchuan', '0513', '226001', 'C', '120.8573', '32.0098'); +INSERT INTO `yoshop_region` VALUES ('873', '871', '港闸', '港闸区', '中国,江苏省,南通市,港闸区', '3', 'gangzha', '0513', '226001', 'G', '120.81778', '32.03163'); +INSERT INTO `yoshop_region` VALUES ('874', '871', '通州', '通州区', '中国,江苏省,南通市,通州区', '3', 'tongzhou', '0513', '226300', 'T', '121.07293', '32.0676'); +INSERT INTO `yoshop_region` VALUES ('875', '871', '海安', '海安县', '中国,江苏省,南通市,海安县', '3', 'hai\'an', '0513', '226600', 'H', '120.45852', '32.54514'); +INSERT INTO `yoshop_region` VALUES ('876', '871', '如东', '如东县', '中国,江苏省,南通市,如东县', '3', 'rudong', '0513', '226400', 'R', '121.18942', '32.31439'); +INSERT INTO `yoshop_region` VALUES ('877', '871', '启东', '启东市', '中国,江苏省,南通市,启东市', '3', 'qidong', '0513', '226200', 'Q', '121.65985', '31.81083'); +INSERT INTO `yoshop_region` VALUES ('878', '871', '如皋', '如皋市', '中国,江苏省,南通市,如皋市', '3', 'rugao', '0513', '226500', 'R', '120.55969', '32.37597'); +INSERT INTO `yoshop_region` VALUES ('879', '871', '海门', '海门市', '中国,江苏省,南通市,海门市', '3', 'haimen', '0513', '226100', 'H', '121.16995', '31.89422'); +INSERT INTO `yoshop_region` VALUES ('880', '820', '连云港', '连云港市', '中国,江苏省,连云港市', '2', 'lianyungang', '0518', '222002', 'L', '119.178821', '34.600018'); +INSERT INTO `yoshop_region` VALUES ('881', '880', '连云', '连云区', '中国,江苏省,连云港市,连云区', '3', 'lianyun', '0518', '222042', 'L', '119.37304', '34.75293'); +INSERT INTO `yoshop_region` VALUES ('882', '880', '海州', '海州区', '中国,江苏省,连云港市,海州区', '3', 'haizhou', '0518', '222003', 'H', '119.13128', '34.56986'); +INSERT INTO `yoshop_region` VALUES ('883', '880', '赣榆', '赣榆区', '中国,江苏省,连云港市,赣榆区', '3', 'ganyu', '0518', '222100', 'G', '119.128774', '34.839154'); +INSERT INTO `yoshop_region` VALUES ('884', '880', '东海', '东海县', '中国,江苏省,连云港市,东海县', '3', 'donghai', '0518', '222300', 'D', '118.77145', '34.54215'); +INSERT INTO `yoshop_region` VALUES ('885', '880', '灌云', '灌云县', '中国,江苏省,连云港市,灌云县', '3', 'guanyun', '0518', '222200', 'G', '119.23925', '34.28391'); +INSERT INTO `yoshop_region` VALUES ('886', '880', '灌南', '灌南县', '中国,江苏省,连云港市,灌南县', '3', 'guannan', '0518', '222500', 'G', '119.35632', '34.09'); +INSERT INTO `yoshop_region` VALUES ('887', '820', '淮安', '淮安市', '中国,江苏省,淮安市', '2', 'huai\'an', '0517', '223001', 'H', '119.021265', '33.597506'); +INSERT INTO `yoshop_region` VALUES ('888', '887', '清河', '清河区', '中国,江苏省,淮安市,清河区', '3', 'qinghe', '0517', '223001', 'Q', '119.00778', '33.59949'); +INSERT INTO `yoshop_region` VALUES ('889', '887', '淮安', '淮安区', '中国,江苏省,淮安市,淮安区', '3', 'huai\'an', '0517', '223200', 'H', '119.021265', '33.597506'); +INSERT INTO `yoshop_region` VALUES ('890', '887', '淮阴', '淮阴区', '中国,江苏省,淮安市,淮阴区', '3', 'huaiyin', '0517', '223300', 'H', '119.03485', '33.63171'); +INSERT INTO `yoshop_region` VALUES ('891', '887', '清浦', '清浦区', '中国,江苏省,淮安市,清浦区', '3', 'qingpu', '0517', '223002', 'Q', '119.02648', '33.55232'); +INSERT INTO `yoshop_region` VALUES ('892', '887', '涟水', '涟水县', '中国,江苏省,淮安市,涟水县', '3', 'lianshui', '0517', '223400', 'L', '119.26083', '33.78094'); +INSERT INTO `yoshop_region` VALUES ('893', '887', '洪泽', '洪泽县', '中国,江苏省,淮安市,洪泽县', '3', 'hongze', '0517', '223100', 'H', '118.87344', '33.29429'); +INSERT INTO `yoshop_region` VALUES ('894', '887', '盱眙', '盱眙县', '中国,江苏省,淮安市,盱眙县', '3', 'xuyi', '0517', '211700', null, '118.54495', '33.01086'); +INSERT INTO `yoshop_region` VALUES ('895', '887', '金湖', '金湖县', '中国,江苏省,淮安市,金湖县', '3', 'jinhu', '0517', '211600', 'J', '119.02307', '33.02219'); +INSERT INTO `yoshop_region` VALUES ('896', '820', '盐城', '盐城市', '中国,江苏省,盐城市', '2', 'yancheng', '0515', '224005', 'Y', '120.139998', '33.377631'); +INSERT INTO `yoshop_region` VALUES ('897', '896', '亭湖', '亭湖区', '中国,江苏省,盐城市,亭湖区', '3', 'tinghu', '0515', '224005', 'T', '120.16583', '33.37825'); +INSERT INTO `yoshop_region` VALUES ('898', '896', '盐都', '盐都区', '中国,江苏省,盐城市,盐都区', '3', 'yandu', '0515', '224055', 'Y', '120.15441', '33.3373'); +INSERT INTO `yoshop_region` VALUES ('899', '896', '响水', '响水县', '中国,江苏省,盐城市,响水县', '3', 'xiangshui', '0515', '224600', 'X', '119.56985', '34.20513'); +INSERT INTO `yoshop_region` VALUES ('900', '896', '滨海', '滨海县', '中国,江苏省,盐城市,滨海县', '3', 'binhai', '0515', '224500', 'B', '119.82058', '33.98972'); +INSERT INTO `yoshop_region` VALUES ('901', '896', '阜宁', '阜宁县', '中国,江苏省,盐城市,阜宁县', '3', 'funing', '0515', '224400', 'F', '119.80175', '33.78228'); +INSERT INTO `yoshop_region` VALUES ('902', '896', '射阳', '射阳县', '中国,江苏省,盐城市,射阳县', '3', 'sheyang', '0515', '224300', 'S', '120.26043', '33.77636'); +INSERT INTO `yoshop_region` VALUES ('903', '896', '建湖', '建湖县', '中国,江苏省,盐城市,建湖县', '3', 'jianhu', '0515', '224700', 'J', '119.79852', '33.47241'); +INSERT INTO `yoshop_region` VALUES ('904', '896', '东台', '东台市', '中国,江苏省,盐城市,东台市', '3', 'dongtai', '0515', '224200', 'D', '120.32376', '32.85078'); +INSERT INTO `yoshop_region` VALUES ('905', '896', '大丰', '大丰市', '中国,江苏省,盐城市,大丰市', '3', 'dafeng', '0515', '224100', 'D', '120.46594', '33.19893'); +INSERT INTO `yoshop_region` VALUES ('906', '820', '扬州', '扬州市', '中国,江苏省,扬州市', '2', 'yangzhou', '0514', '225002', 'Y', '119.421003', '32.393159'); +INSERT INTO `yoshop_region` VALUES ('907', '906', '广陵', '广陵区', '中国,江苏省,扬州市,广陵区', '3', 'guangling', '0514', '225002', 'G', '119.43186', '32.39472'); +INSERT INTO `yoshop_region` VALUES ('908', '906', '邗江', '邗江区', '中国,江苏省,扬州市,邗江区', '3', 'hanjiang', '0514', '225002', null, '119.39816', '32.3765'); +INSERT INTO `yoshop_region` VALUES ('909', '906', '江都', '江都区', '中国,江苏省,扬州市,江都区', '3', 'jiangdu', '0514', '225200', 'J', '119.567481', '32.426564'); +INSERT INTO `yoshop_region` VALUES ('910', '906', '宝应', '宝应县', '中国,江苏省,扬州市,宝应县', '3', 'baoying', '0514', '225800', 'B', '119.31213', '33.23549'); +INSERT INTO `yoshop_region` VALUES ('911', '906', '仪征', '仪征市', '中国,江苏省,扬州市,仪征市', '3', 'yizheng', '0514', '211400', 'Y', '119.18432', '32.27197'); +INSERT INTO `yoshop_region` VALUES ('912', '906', '高邮', '高邮市', '中国,江苏省,扬州市,高邮市', '3', 'gaoyou', '0514', '225600', 'G', '119.45965', '32.78135'); +INSERT INTO `yoshop_region` VALUES ('913', '820', '镇江', '镇江市', '中国,江苏省,镇江市', '2', 'zhenjiang', '0511', '212004', 'Z', '119.452753', '32.204402'); +INSERT INTO `yoshop_region` VALUES ('914', '913', '京口', '京口区', '中国,江苏省,镇江市,京口区', '3', 'jingkou', '0511', '212003', 'J', '119.46947', '32.19809'); +INSERT INTO `yoshop_region` VALUES ('915', '913', '润州', '润州区', '中国,江苏省,镇江市,润州区', '3', 'runzhou', '0511', '212005', 'R', '119.41134', '32.19523'); +INSERT INTO `yoshop_region` VALUES ('916', '913', '丹徒', '丹徒区', '中国,江苏省,镇江市,丹徒区', '3', 'dantu', '0511', '212028', 'D', '119.43383', '32.13183'); +INSERT INTO `yoshop_region` VALUES ('917', '913', '丹阳', '丹阳市', '中国,江苏省,镇江市,丹阳市', '3', 'danyang', '0511', '212300', 'D', '119.57525', '31.99121'); +INSERT INTO `yoshop_region` VALUES ('918', '913', '扬中', '扬中市', '中国,江苏省,镇江市,扬中市', '3', 'yangzhong', '0511', '212200', 'Y', '119.79718', '32.2363'); +INSERT INTO `yoshop_region` VALUES ('919', '913', '句容', '句容市', '中国,江苏省,镇江市,句容市', '3', 'jurong', '0511', '212400', 'J', '119.16482', '31.95591'); +INSERT INTO `yoshop_region` VALUES ('920', '820', '泰州', '泰州市', '中国,江苏省,泰州市', '2', 'taizhou', '0523', '225300', 'T', '119.915176', '32.484882'); +INSERT INTO `yoshop_region` VALUES ('921', '920', '海陵', '海陵区', '中国,江苏省,泰州市,海陵区', '3', 'hailing', '0523', '225300', 'H', '119.91942', '32.49101'); +INSERT INTO `yoshop_region` VALUES ('922', '920', '高港', '高港区', '中国,江苏省,泰州市,高港区', '3', 'gaogang', '0523', '225321', 'G', '119.88089', '32.31833'); +INSERT INTO `yoshop_region` VALUES ('923', '920', '姜堰', '姜堰区', '中国,江苏省,泰州市,姜堰区', '3', 'jiangyan', '0523', '225500', 'J', '120.148208', '32.508483'); +INSERT INTO `yoshop_region` VALUES ('924', '920', '兴化', '兴化市', '中国,江苏省,泰州市,兴化市', '3', 'xinghua', '0523', '225700', 'X', '119.85238', '32.90944'); +INSERT INTO `yoshop_region` VALUES ('925', '920', '靖江', '靖江市', '中国,江苏省,泰州市,靖江市', '3', 'jingjiang', '0523', '214500', 'J', '120.27291', '32.01595'); +INSERT INTO `yoshop_region` VALUES ('926', '920', '泰兴', '泰兴市', '中国,江苏省,泰州市,泰兴市', '3', 'taixing', '0523', '225400', 'T', '120.05194', '32.17187'); +INSERT INTO `yoshop_region` VALUES ('927', '820', '宿迁', '宿迁市', '中国,江苏省,宿迁市', '2', 'suqian', '0527', '223800', 'S', '118.293328', '33.945154'); +INSERT INTO `yoshop_region` VALUES ('928', '927', '宿城', '宿城区', '中国,江苏省,宿迁市,宿城区', '3', 'sucheng', '0527', '223800', 'S', '118.29141', '33.94219'); +INSERT INTO `yoshop_region` VALUES ('929', '927', '宿豫', '宿豫区', '中国,江苏省,宿迁市,宿豫区', '3', 'suyu', '0527', '223800', 'S', '118.32922', '33.94673'); +INSERT INTO `yoshop_region` VALUES ('930', '927', '沭阳', '沭阳县', '中国,江苏省,宿迁市,沭阳县', '3', 'shuyang', '0527', '223600', null, '118.76873', '34.11446'); +INSERT INTO `yoshop_region` VALUES ('931', '927', '泗阳', '泗阳县', '中国,江苏省,宿迁市,泗阳县', '3', 'siyang', '0527', '223700', null, '118.7033', '33.72096'); +INSERT INTO `yoshop_region` VALUES ('932', '927', '泗洪', '泗洪县', '中国,江苏省,宿迁市,泗洪县', '3', 'sihong', '0527', '223900', null, '118.21716', '33.45996'); +INSERT INTO `yoshop_region` VALUES ('933', '0', '浙江', '浙江省', '中国,浙江省', '1', 'zhejiang', '', '', 'Z', '120.153576', '30.287459'); +INSERT INTO `yoshop_region` VALUES ('934', '933', '杭州', '杭州市', '中国,浙江省,杭州市', '2', 'hangzhou', '0571', '310026', 'H', '120.153576', '30.287459'); +INSERT INTO `yoshop_region` VALUES ('935', '934', '上城', '上城区', '中国,浙江省,杭州市,上城区', '3', 'shangcheng', '0571', '310002', 'S', '120.16922', '30.24255'); +INSERT INTO `yoshop_region` VALUES ('936', '934', '下城', '下城区', '中国,浙江省,杭州市,下城区', '3', 'xiacheng', '0571', '310006', 'X', '120.18096', '30.28153'); +INSERT INTO `yoshop_region` VALUES ('937', '934', '江干', '江干区', '中国,浙江省,杭州市,江干区', '3', 'jianggan', '0571', '310016', 'J', '120.20517', '30.2572'); +INSERT INTO `yoshop_region` VALUES ('938', '934', '拱墅', '拱墅区', '中国,浙江省,杭州市,拱墅区', '3', 'gongshu', '0571', '310011', 'G', '120.14209', '30.31968'); +INSERT INTO `yoshop_region` VALUES ('939', '934', '西湖', '西湖区', '中国,浙江省,杭州市,西湖区', '3', 'xihu', '0571', '310013', 'X', '120.12979', '30.25949'); +INSERT INTO `yoshop_region` VALUES ('940', '934', '滨江', '滨江区', '中国,浙江省,杭州市,滨江区', '3', 'binjiang', '0571', '310051', 'B', '120.21194', '30.20835'); +INSERT INTO `yoshop_region` VALUES ('941', '934', '萧山', '萧山区', '中国,浙江省,杭州市,萧山区', '3', 'xiaoshan', '0571', '311200', 'X', '120.26452', '30.18505'); +INSERT INTO `yoshop_region` VALUES ('942', '934', '余杭', '余杭区', '中国,浙江省,杭州市,余杭区', '3', 'yuhang', '0571', '311100', 'Y', '120.29986', '30.41829'); +INSERT INTO `yoshop_region` VALUES ('943', '934', '桐庐', '桐庐县', '中国,浙江省,杭州市,桐庐县', '3', 'tonglu', '0571', '311500', 'T', '119.68853', '29.79779'); +INSERT INTO `yoshop_region` VALUES ('944', '934', '淳安', '淳安县', '中国,浙江省,杭州市,淳安县', '3', 'chun\'an', '0571', '311700', 'C', '119.04257', '29.60988'); +INSERT INTO `yoshop_region` VALUES ('945', '934', '建德', '建德市', '中国,浙江省,杭州市,建德市', '3', 'jiande', '0571', '311600', 'J', '119.28158', '29.47603'); +INSERT INTO `yoshop_region` VALUES ('946', '934', '富阳', '富阳区', '中国,浙江省,杭州市,富阳区', '3', 'fuyang', '0571', '311400', 'F', '119.96041', '30.04878'); +INSERT INTO `yoshop_region` VALUES ('947', '934', '临安', '临安市', '中国,浙江省,杭州市,临安市', '3', 'lin\'an', '0571', '311300', 'L', '119.72473', '30.23447'); +INSERT INTO `yoshop_region` VALUES ('948', '933', '宁波', '宁波市', '中国,浙江省,宁波市', '2', 'ningbo', '0574', '315000', 'N', '121.549792', '29.868388'); +INSERT INTO `yoshop_region` VALUES ('949', '948', '海曙', '海曙区', '中国,浙江省,宁波市,海曙区', '3', 'haishu', '0574', '315000', 'H', '121.55106', '29.85977'); +INSERT INTO `yoshop_region` VALUES ('950', '948', '江东', '江东区', '中国,浙江省,宁波市,江东区', '3', 'jiangdong', '0574', '315040', 'J', '121.57028', '29.86701'); +INSERT INTO `yoshop_region` VALUES ('951', '948', '江北', '江北区', '中国,浙江省,宁波市,江北区', '3', 'jiangbei', '0574', '315020', 'J', '121.55681', '29.88776'); +INSERT INTO `yoshop_region` VALUES ('952', '948', '北仑', '北仑区', '中国,浙江省,宁波市,北仑区', '3', 'beilun', '0574', '315800', 'B', '121.84408', '29.90069'); +INSERT INTO `yoshop_region` VALUES ('953', '948', '镇海', '镇海区', '中国,浙江省,宁波市,镇海区', '3', 'zhenhai', '0574', '315200', 'Z', '121.71615', '29.94893'); +INSERT INTO `yoshop_region` VALUES ('954', '948', '鄞州', '鄞州区', '中国,浙江省,宁波市,鄞州区', '3', 'yinzhou', '0574', '315100', null, '121.54754', '29.81614'); +INSERT INTO `yoshop_region` VALUES ('955', '948', '象山', '象山县', '中国,浙江省,宁波市,象山县', '3', 'xiangshan', '0574', '315700', 'X', '121.86917', '29.47758'); +INSERT INTO `yoshop_region` VALUES ('956', '948', '宁海', '宁海县', '中国,浙江省,宁波市,宁海县', '3', 'ninghai', '0574', '315600', 'N', '121.43072', '29.2889'); +INSERT INTO `yoshop_region` VALUES ('957', '948', '余姚', '余姚市', '中国,浙江省,宁波市,余姚市', '3', 'yuyao', '0574', '315400', 'Y', '121.15341', '30.03867'); +INSERT INTO `yoshop_region` VALUES ('958', '948', '慈溪', '慈溪市', '中国,浙江省,宁波市,慈溪市', '3', 'cixi', '0574', '315300', 'C', '121.26641', '30.16959'); +INSERT INTO `yoshop_region` VALUES ('959', '948', '奉化', '奉化市', '中国,浙江省,宁波市,奉化市', '3', 'fenghua', '0574', '315500', 'F', '121.41003', '29.65537'); +INSERT INTO `yoshop_region` VALUES ('960', '933', '温州', '温州市', '中国,浙江省,温州市', '2', 'wenzhou', '0577', '325000', 'W', '120.672111', '28.000575'); +INSERT INTO `yoshop_region` VALUES ('961', '960', '鹿城', '鹿城区', '中国,浙江省,温州市,鹿城区', '3', 'lucheng', '0577', '325000', 'L', '120.65505', '28.01489'); +INSERT INTO `yoshop_region` VALUES ('962', '960', '龙湾', '龙湾区', '中国,浙江省,温州市,龙湾区', '3', 'longwan', '0577', '325013', 'L', '120.83053', '27.91284'); +INSERT INTO `yoshop_region` VALUES ('963', '960', '瓯海', '瓯海区', '中国,浙江省,温州市,瓯海区', '3', 'ouhai', '0577', '325005', null, '120.63751', '28.00714'); +INSERT INTO `yoshop_region` VALUES ('964', '960', '洞头', '洞头县', '中国,浙江省,温州市,洞头县', '3', 'dongtou', '0577', '325700', 'D', '121.15606', '27.83634'); +INSERT INTO `yoshop_region` VALUES ('965', '960', '永嘉', '永嘉县', '中国,浙江省,温州市,永嘉县', '3', 'yongjia', '0577', '325100', 'Y', '120.69317', '28.15456'); +INSERT INTO `yoshop_region` VALUES ('966', '960', '平阳', '平阳县', '中国,浙江省,温州市,平阳县', '3', 'pingyang', '0577', '325400', 'P', '120.56506', '27.66245'); +INSERT INTO `yoshop_region` VALUES ('967', '960', '苍南', '苍南县', '中国,浙江省,温州市,苍南县', '3', 'cangnan', '0577', '325800', 'C', '120.42608', '27.51739'); +INSERT INTO `yoshop_region` VALUES ('968', '960', '文成', '文成县', '中国,浙江省,温州市,文成县', '3', 'wencheng', '0577', '325300', 'W', '120.09063', '27.78678'); +INSERT INTO `yoshop_region` VALUES ('969', '960', '泰顺', '泰顺县', '中国,浙江省,温州市,泰顺县', '3', 'taishun', '0577', '325500', 'T', '119.7182', '27.55694'); +INSERT INTO `yoshop_region` VALUES ('970', '960', '瑞安', '瑞安市', '中国,浙江省,温州市,瑞安市', '3', 'rui\'an', '0577', '325200', 'R', '120.65466', '27.78041'); +INSERT INTO `yoshop_region` VALUES ('971', '960', '乐清', '乐清市', '中国,浙江省,温州市,乐清市', '3', 'yueqing', '0577', '325600', 'L', '120.9617', '28.12404'); +INSERT INTO `yoshop_region` VALUES ('972', '933', '嘉兴', '嘉兴市', '中国,浙江省,嘉兴市', '2', 'jiaxing', '0573', '314000', 'J', '120.750865', '30.762653'); +INSERT INTO `yoshop_region` VALUES ('973', '972', '南湖', '南湖区', '中国,浙江省,嘉兴市,南湖区', '3', 'nanhu', '0573', '314051', 'N', '120.78524', '30.74865'); +INSERT INTO `yoshop_region` VALUES ('974', '972', '秀洲', '秀洲区', '中国,浙江省,嘉兴市,秀洲区', '3', 'xiuzhou', '0573', '314031', 'X', '120.70867', '30.76454'); +INSERT INTO `yoshop_region` VALUES ('975', '972', '嘉善', '嘉善县', '中国,浙江省,嘉兴市,嘉善县', '3', 'jiashan', '0573', '314100', 'J', '120.92559', '30.82993'); +INSERT INTO `yoshop_region` VALUES ('976', '972', '海盐', '海盐县', '中国,浙江省,嘉兴市,海盐县', '3', 'haiyan', '0573', '314300', 'H', '120.9457', '30.52547'); +INSERT INTO `yoshop_region` VALUES ('977', '972', '海宁', '海宁市', '中国,浙江省,嘉兴市,海宁市', '3', 'haining', '0573', '314400', 'H', '120.6813', '30.5097'); +INSERT INTO `yoshop_region` VALUES ('978', '972', '平湖', '平湖市', '中国,浙江省,嘉兴市,平湖市', '3', 'pinghu', '0573', '314200', 'P', '121.02166', '30.69618'); +INSERT INTO `yoshop_region` VALUES ('979', '972', '桐乡', '桐乡市', '中国,浙江省,嘉兴市,桐乡市', '3', 'tongxiang', '0573', '314500', 'T', '120.56485', '30.6302'); +INSERT INTO `yoshop_region` VALUES ('980', '933', '湖州', '湖州市', '中国,浙江省,湖州市', '2', 'huzhou', '0572', '313000', 'H', '120.102398', '30.867198'); +INSERT INTO `yoshop_region` VALUES ('981', '980', '吴兴', '吴兴区', '中国,浙江省,湖州市,吴兴区', '3', 'wuxing', '0572', '313000', 'W', '120.12548', '30.85752'); +INSERT INTO `yoshop_region` VALUES ('982', '980', '南浔', '南浔区', '中国,浙江省,湖州市,南浔区', '3', 'nanxun', '0572', '313009', 'N', '120.42038', '30.86686'); +INSERT INTO `yoshop_region` VALUES ('983', '980', '德清', '德清县', '中国,浙江省,湖州市,德清县', '3', 'deqing', '0572', '313200', 'D', '119.97836', '30.53369'); +INSERT INTO `yoshop_region` VALUES ('984', '980', '长兴', '长兴县', '中国,浙江省,湖州市,长兴县', '3', 'changxing', '0572', '313100', 'C', '119.90783', '31.00606'); +INSERT INTO `yoshop_region` VALUES ('985', '980', '安吉', '安吉县', '中国,浙江省,湖州市,安吉县', '3', 'anji', '0572', '313300', 'A', '119.68158', '30.63798'); +INSERT INTO `yoshop_region` VALUES ('986', '933', '绍兴', '绍兴市', '中国,浙江省,绍兴市', '2', 'shaoxing', '0575', '312000', 'S', '120.582112', '29.997117'); +INSERT INTO `yoshop_region` VALUES ('987', '986', '越城', '越城区', '中国,浙江省,绍兴市,越城区', '3', 'yuecheng', '0575', '312000', 'Y', '120.5819', '29.98895'); +INSERT INTO `yoshop_region` VALUES ('988', '986', '柯桥', '柯桥区', '中国,浙江省,绍兴市,柯桥区', '3', 'keqiao', '0575', '312030', 'K', '120.492736', '30.08763'); +INSERT INTO `yoshop_region` VALUES ('989', '986', '上虞', '上虞区', '中国,浙江省,绍兴市,上虞区', '3', 'shangyu', '0575', '312300', 'S', '120.476075', '30.078038'); +INSERT INTO `yoshop_region` VALUES ('990', '986', '新昌', '新昌县', '中国,浙江省,绍兴市,新昌县', '3', 'xinchang', '0575', '312500', 'X', '120.90435', '29.49991'); +INSERT INTO `yoshop_region` VALUES ('991', '986', '诸暨', '诸暨市', '中国,浙江省,绍兴市,诸暨市', '3', 'zhuji', '0575', '311800', 'Z', '120.23629', '29.71358'); +INSERT INTO `yoshop_region` VALUES ('992', '986', '嵊州', '嵊州市', '中国,浙江省,绍兴市,嵊州市', '3', 'shengzhou', '0575', '312400', null, '120.82174', '29.58854'); +INSERT INTO `yoshop_region` VALUES ('993', '933', '金华', '金华市', '中国,浙江省,金华市', '2', 'jinhua', '0579', '321000', 'J', '119.649506', '29.089524'); +INSERT INTO `yoshop_region` VALUES ('994', '993', '婺城', '婺城区', '中国,浙江省,金华市,婺城区', '3', 'wucheng', '0579', '321000', null, '119.57135', '29.09521'); +INSERT INTO `yoshop_region` VALUES ('995', '993', '金东', '金东区', '中国,浙江省,金华市,金东区', '3', 'jindong', '0579', '321000', 'J', '119.69302', '29.0991'); +INSERT INTO `yoshop_region` VALUES ('996', '993', '武义', '武义县', '中国,浙江省,金华市,武义县', '3', 'wuyi', '0579', '321200', 'W', '119.8164', '28.89331'); +INSERT INTO `yoshop_region` VALUES ('997', '993', '浦江', '浦江县', '中国,浙江省,金华市,浦江县', '3', 'pujiang', '0579', '322200', 'P', '119.89181', '29.45353'); +INSERT INTO `yoshop_region` VALUES ('998', '993', '磐安', '磐安县', '中国,浙江省,金华市,磐安县', '3', 'pan\'an', '0579', '322300', 'P', '120.45022', '29.05733'); +INSERT INTO `yoshop_region` VALUES ('999', '993', '兰溪', '兰溪市', '中国,浙江省,金华市,兰溪市', '3', 'lanxi', '0579', '321100', 'L', '119.45965', '29.20841'); +INSERT INTO `yoshop_region` VALUES ('1000', '993', '义乌', '义乌市', '中国,浙江省,金华市,义乌市', '3', 'yiwu', '0579', '322000', 'Y', '120.0744', '29.30558'); +INSERT INTO `yoshop_region` VALUES ('1001', '993', '东阳', '东阳市', '中国,浙江省,金华市,东阳市', '3', 'dongyang', '0579', '322100', 'D', '120.24185', '29.28942'); +INSERT INTO `yoshop_region` VALUES ('1002', '993', '永康', '永康市', '中国,浙江省,金华市,永康市', '3', 'yongkang', '0579', '321300', 'Y', '120.04727', '28.88844'); +INSERT INTO `yoshop_region` VALUES ('1003', '933', '衢州', '衢州市', '中国,浙江省,衢州市', '2', 'quzhou', '0570', '324002', null, '118.87263', '28.941708'); +INSERT INTO `yoshop_region` VALUES ('1004', '1003', '柯城', '柯城区', '中国,浙江省,衢州市,柯城区', '3', 'kecheng', '0570', '324100', 'K', '118.87109', '28.96858'); +INSERT INTO `yoshop_region` VALUES ('1005', '1003', '衢江', '衢江区', '中国,浙江省,衢州市,衢江区', '3', 'qujiang', '0570', '324022', null, '118.9598', '28.97977'); +INSERT INTO `yoshop_region` VALUES ('1006', '1003', '常山', '常山县', '中国,浙江省,衢州市,常山县', '3', 'changshan', '0570', '324200', 'C', '118.51025', '28.90191'); +INSERT INTO `yoshop_region` VALUES ('1007', '1003', '开化', '开化县', '中国,浙江省,衢州市,开化县', '3', 'kaihua', '0570', '324300', 'K', '118.41616', '29.13785'); +INSERT INTO `yoshop_region` VALUES ('1008', '1003', '龙游', '龙游县', '中国,浙江省,衢州市,龙游县', '3', 'longyou', '0570', '324400', 'L', '119.17221', '29.02823'); +INSERT INTO `yoshop_region` VALUES ('1009', '1003', '江山', '江山市', '中国,浙江省,衢州市,江山市', '3', 'jiangshan', '0570', '324100', 'J', '118.62674', '28.7386'); +INSERT INTO `yoshop_region` VALUES ('1010', '933', '舟山', '舟山市', '中国,浙江省,舟山市', '2', 'zhoushan', '0580', '316000', 'Z', '122.106863', '30.016028'); +INSERT INTO `yoshop_region` VALUES ('1011', '1010', '定海', '定海区', '中国,浙江省,舟山市,定海区', '3', 'dinghai', '0580', '316000', 'D', '122.10677', '30.01985'); +INSERT INTO `yoshop_region` VALUES ('1012', '1010', '普陀', '普陀区', '中国,浙江省,舟山市,普陀区', '3', 'putuo', '0580', '316100', 'P', '122.30278', '29.94908'); +INSERT INTO `yoshop_region` VALUES ('1013', '1010', '岱山', '岱山县', '中国,浙江省,舟山市,岱山县', '3', 'daishan', '0580', '316200', null, '122.20486', '30.24385'); +INSERT INTO `yoshop_region` VALUES ('1014', '1010', '嵊泗', '嵊泗县', '中国,浙江省,舟山市,嵊泗县', '3', 'shengsi', '0580', '202450', null, '122.45129', '30.72678'); +INSERT INTO `yoshop_region` VALUES ('1015', '933', '台州', '台州市', '中国,浙江省,台州市', '2', 'taizhou', '0576', '318000', 'T', '121.428599', '28.661378'); +INSERT INTO `yoshop_region` VALUES ('1016', '1015', '椒江', '椒江区', '中国,浙江省,台州市,椒江区', '3', 'jiaojiang', '0576', '318000', 'J', '121.44287', '28.67301'); +INSERT INTO `yoshop_region` VALUES ('1017', '1015', '黄岩', '黄岩区', '中国,浙江省,台州市,黄岩区', '3', 'huangyan', '0576', '318020', 'H', '121.25891', '28.65077'); +INSERT INTO `yoshop_region` VALUES ('1018', '1015', '路桥', '路桥区', '中国,浙江省,台州市,路桥区', '3', 'luqiao', '0576', '318050', 'L', '121.37381', '28.58016'); +INSERT INTO `yoshop_region` VALUES ('1019', '1015', '玉环', '玉环县', '中国,浙江省,台州市,玉环县', '3', 'yuhuan', '0576', '317600', 'Y', '121.23242', '28.13637'); +INSERT INTO `yoshop_region` VALUES ('1020', '1015', '三门', '三门县', '中国,浙江省,台州市,三门县', '3', 'sanmen', '0576', '317100', 'S', '121.3937', '29.1051'); +INSERT INTO `yoshop_region` VALUES ('1021', '1015', '天台', '天台县', '中国,浙江省,台州市,天台县', '3', 'tiantai', '0576', '317200', 'T', '121.00848', '29.1429'); +INSERT INTO `yoshop_region` VALUES ('1022', '1015', '仙居', '仙居县', '中国,浙江省,台州市,仙居县', '3', 'xianju', '0576', '317300', 'X', '120.72872', '28.84672'); +INSERT INTO `yoshop_region` VALUES ('1023', '1015', '温岭', '温岭市', '中国,浙江省,台州市,温岭市', '3', 'wenling', '0576', '317500', 'W', '121.38595', '28.37176'); +INSERT INTO `yoshop_region` VALUES ('1024', '1015', '临海', '临海市', '中国,浙江省,台州市,临海市', '3', 'linhai', '0576', '317000', 'L', '121.13885', '28.85603'); +INSERT INTO `yoshop_region` VALUES ('1025', '933', '丽水', '丽水市', '中国,浙江省,丽水市', '2', 'lishui', '0578', '323000', 'L', '119.921786', '28.451993'); +INSERT INTO `yoshop_region` VALUES ('1026', '1025', '莲都', '莲都区', '中国,浙江省,丽水市,莲都区', '3', 'liandu', '0578', '323000', 'L', '119.9127', '28.44583'); +INSERT INTO `yoshop_region` VALUES ('1027', '1025', '青田', '青田县', '中国,浙江省,丽水市,青田县', '3', 'qingtian', '0578', '323900', 'Q', '120.29028', '28.13897'); +INSERT INTO `yoshop_region` VALUES ('1028', '1025', '缙云', '缙云县', '中国,浙江省,丽水市,缙云县', '3', 'jinyun', '0578', '321400', null, '120.09036', '28.65944'); +INSERT INTO `yoshop_region` VALUES ('1029', '1025', '遂昌', '遂昌县', '中国,浙江省,丽水市,遂昌县', '3', 'suichang', '0578', '323300', 'S', '119.27606', '28.59291'); +INSERT INTO `yoshop_region` VALUES ('1030', '1025', '松阳', '松阳县', '中国,浙江省,丽水市,松阳县', '3', 'songyang', '0578', '323400', 'S', '119.48199', '28.4494'); +INSERT INTO `yoshop_region` VALUES ('1031', '1025', '云和', '云和县', '中国,浙江省,丽水市,云和县', '3', 'yunhe', '0578', '323600', 'Y', '119.57287', '28.11643'); +INSERT INTO `yoshop_region` VALUES ('1032', '1025', '庆元', '庆元县', '中国,浙江省,丽水市,庆元县', '3', 'qingyuan', '0578', '323800', 'Q', '119.06256', '27.61842'); +INSERT INTO `yoshop_region` VALUES ('1033', '1025', '景宁', '景宁畲族自治县', '中国,浙江省,丽水市,景宁畲族自治县', '3', 'jingning', '0578', '323500', 'J', '119.63839', '27.97393'); +INSERT INTO `yoshop_region` VALUES ('1034', '1025', '龙泉', '龙泉市', '中国,浙江省,丽水市,龙泉市', '3', 'longquan', '0578', '323700', 'L', '119.14163', '28.0743'); +INSERT INTO `yoshop_region` VALUES ('1035', '933', '舟山新区', '舟山群岛新区', '中国,浙江省,舟山群岛新区', '2', 'zhoushan', '0580', '316000', 'Z', '122.317657', '29.813242'); +INSERT INTO `yoshop_region` VALUES ('1036', '1035', '金塘', '金塘岛', '中国,浙江省,舟山群岛新区,金塘岛', '3', 'jintang', '0580', '316000', 'J', '121.893373', '30.040641'); +INSERT INTO `yoshop_region` VALUES ('1037', '1035', '六横', '六横岛', '中国,浙江省,舟山群岛新区,六横岛', '3', 'liuheng', '0580', '316000', 'L', '122.14265', '29.662938'); +INSERT INTO `yoshop_region` VALUES ('1038', '1035', '衢山', '衢山岛', '中国,浙江省,舟山群岛新区,衢山岛', '3', 'qushan', '0580', '316000', null, '122.358425', '30.442642'); +INSERT INTO `yoshop_region` VALUES ('1039', '1035', '舟山', '舟山本岛西北部', '中国,浙江省,舟山群岛新区,舟山本岛西北部', '3', 'zhoushan', '0580', '316000', 'Z', '122.03064', '30.140377'); +INSERT INTO `yoshop_region` VALUES ('1040', '1035', '岱山', '岱山岛西南部', '中国,浙江省,舟山群岛新区,岱山岛西南部', '3', 'daishan', '0580', '316000', null, '122.180123', '30.277269'); +INSERT INTO `yoshop_region` VALUES ('1041', '1035', '泗礁', '泗礁岛', '中国,浙江省,舟山群岛新区,泗礁岛', '3', 'sijiao', '0580', '316000', null, '122.45803', '30.725112'); +INSERT INTO `yoshop_region` VALUES ('1042', '1035', '朱家尖', '朱家尖岛', '中国,浙江省,舟山群岛新区,朱家尖岛', '3', 'zhujiajian', '0580', '316000', 'Z', '122.390636', '29.916303'); +INSERT INTO `yoshop_region` VALUES ('1043', '1035', '洋山', '洋山岛', '中国,浙江省,舟山群岛新区,洋山岛', '3', 'yangshan', '0580', '316000', 'Y', '121.995891', '30.094637'); +INSERT INTO `yoshop_region` VALUES ('1044', '1035', '长涂', '长涂岛', '中国,浙江省,舟山群岛新区,长涂岛', '3', 'changtu', '0580', '316000', 'C', '122.284681', '30.24888'); +INSERT INTO `yoshop_region` VALUES ('1045', '1035', '虾峙', '虾峙岛', '中国,浙江省,舟山群岛新区,虾峙岛', '3', 'xiazhi', '0580', '316000', 'X', '122.244686', '29.752941'); +INSERT INTO `yoshop_region` VALUES ('1046', '0', '安徽', '安徽省', '中国,安徽省', '1', 'anhui', '', '', 'A', '117.283042', '31.86119'); +INSERT INTO `yoshop_region` VALUES ('1047', '1046', '合肥', '合肥市', '中国,安徽省,合肥市', '2', 'hefei', '0551', '230001', 'H', '117.283042', '31.86119'); +INSERT INTO `yoshop_region` VALUES ('1048', '1047', '瑶海', '瑶海区', '中国,安徽省,合肥市,瑶海区', '3', 'yaohai', '0551', '230011', 'Y', '117.30947', '31.85809'); +INSERT INTO `yoshop_region` VALUES ('1049', '1047', '庐阳', '庐阳区', '中国,安徽省,合肥市,庐阳区', '3', 'luyang', '0551', '230001', 'L', '117.26452', '31.87874'); +INSERT INTO `yoshop_region` VALUES ('1050', '1047', '蜀山', '蜀山区', '中国,安徽省,合肥市,蜀山区', '3', 'shushan', '0551', '230031', 'S', '117.26104', '31.85117'); +INSERT INTO `yoshop_region` VALUES ('1051', '1047', '包河', '包河区', '中国,安徽省,合肥市,包河区', '3', 'baohe', '0551', '230041', 'B', '117.30984', '31.79502'); +INSERT INTO `yoshop_region` VALUES ('1052', '1047', '长丰', '长丰县', '中国,安徽省,合肥市,长丰县', '3', 'changfeng', '0551', '231100', 'C', '117.16549', '32.47959'); +INSERT INTO `yoshop_region` VALUES ('1053', '1047', '肥东', '肥东县', '中国,安徽省,合肥市,肥东县', '3', 'feidong', '0551', '231600', 'F', '117.47128', '31.88525'); +INSERT INTO `yoshop_region` VALUES ('1054', '1047', '肥西', '肥西县', '中国,安徽省,合肥市,肥西县', '3', 'feixi', '0551', '231200', 'F', '117.16845', '31.72143'); +INSERT INTO `yoshop_region` VALUES ('1055', '1047', '庐江', '庐江县', '中国,安徽省,合肥市,庐江县', '3', 'lujiang', '0565', '231500', 'L', '117.289844', '31.251488'); +INSERT INTO `yoshop_region` VALUES ('1056', '1047', '巢湖', '巢湖市', '中国,安徽省,合肥市,巢湖市', '3', 'chaohu', '0565', '238000', 'C', '117.874155', '31.600518'); +INSERT INTO `yoshop_region` VALUES ('1057', '1046', '芜湖', '芜湖市', '中国,安徽省,芜湖市', '2', 'wuhu', '0551', '241000', 'W', '118.376451', '31.326319'); +INSERT INTO `yoshop_region` VALUES ('1058', '1057', '镜湖', '镜湖区', '中国,安徽省,芜湖市,镜湖区', '3', 'jinghu', '0553', '241000', 'J', '118.38525', '31.34038'); +INSERT INTO `yoshop_region` VALUES ('1059', '1057', '弋江', '弋江区', '中国,安徽省,芜湖市,弋江区', '3', 'yijiang', '0553', '241000', null, '118.37265', '31.31178'); +INSERT INTO `yoshop_region` VALUES ('1060', '1057', '鸠江', '鸠江区', '中国,安徽省,芜湖市,鸠江区', '3', 'jiujiang', '0553', '241000', null, '118.39215', '31.36928'); +INSERT INTO `yoshop_region` VALUES ('1061', '1057', '三山', '三山区', '中国,安徽省,芜湖市,三山区', '3', 'sanshan', '0553', '241000', 'S', '118.22509', '31.20703'); +INSERT INTO `yoshop_region` VALUES ('1062', '1057', '芜湖', '芜湖县', '中国,安徽省,芜湖市,芜湖县', '3', 'wuhu', '0553', '241100', 'W', '118.57525', '31.13476'); +INSERT INTO `yoshop_region` VALUES ('1063', '1057', '繁昌', '繁昌县', '中国,安徽省,芜湖市,繁昌县', '3', 'fanchang', '0553', '241200', 'F', '118.19982', '31.08319'); +INSERT INTO `yoshop_region` VALUES ('1064', '1057', '南陵', '南陵县', '中国,安徽省,芜湖市,南陵县', '3', 'nanling', '0553', '242400', 'N', '118.33688', '30.91969'); +INSERT INTO `yoshop_region` VALUES ('1065', '1057', '无为', '无为县', '中国,安徽省,芜湖市,无为县', '3', 'wuwei', '0565', '238300', 'W', '117.911432', '31.303075'); +INSERT INTO `yoshop_region` VALUES ('1066', '1046', '蚌埠', '蚌埠市', '中国,安徽省,蚌埠市', '2', 'bengbu', '0552', '233000', 'B', '117.36237', '32.934037'); +INSERT INTO `yoshop_region` VALUES ('1067', '1066', '龙子湖', '龙子湖区', '中国,安徽省,蚌埠市,龙子湖区', '3', 'longzihu', '0552', '233000', 'L', '117.39379', '32.94301'); +INSERT INTO `yoshop_region` VALUES ('1068', '1066', '蚌山', '蚌山区', '中国,安徽省,蚌埠市,蚌山区', '3', 'bengshan', '0552', '233000', 'B', '117.36767', '32.94411'); +INSERT INTO `yoshop_region` VALUES ('1069', '1066', '禹会', '禹会区', '中国,安徽省,蚌埠市,禹会区', '3', 'yuhui', '0552', '233010', 'Y', '117.35315', '32.93336'); +INSERT INTO `yoshop_region` VALUES ('1070', '1066', '淮上', '淮上区', '中国,安徽省,蚌埠市,淮上区', '3', 'huaishang', '0552', '233002', 'H', '117.35983', '32.96423'); +INSERT INTO `yoshop_region` VALUES ('1071', '1066', '怀远', '怀远县', '中国,安徽省,蚌埠市,怀远县', '3', 'huaiyuan', '0552', '233400', 'H', '117.20507', '32.97007'); +INSERT INTO `yoshop_region` VALUES ('1072', '1066', '五河', '五河县', '中国,安徽省,蚌埠市,五河县', '3', 'wuhe', '0552', '233300', 'W', '117.89144', '33.14457'); +INSERT INTO `yoshop_region` VALUES ('1073', '1066', '固镇', '固镇县', '中国,安徽省,蚌埠市,固镇县', '3', 'guzhen', '0552', '233700', 'G', '117.31558', '33.31803'); +INSERT INTO `yoshop_region` VALUES ('1074', '1046', '淮南', '淮南市', '中国,安徽省,淮南市', '2', 'huainan', '0554', '232001', 'H', '117.025449', '32.645947'); +INSERT INTO `yoshop_region` VALUES ('1075', '1074', '大通', '大通区', '中国,安徽省,淮南市,大通区', '3', 'datong', '0554', '232033', 'D', '117.05255', '32.63265'); +INSERT INTO `yoshop_region` VALUES ('1076', '1074', '田家庵', '田家庵区', '中国,安徽省,淮南市,田家庵区', '3', 'tianjiaan', '0554', '232000', 'T', '117.01739', '32.64697'); +INSERT INTO `yoshop_region` VALUES ('1077', '1074', '谢家集', '谢家集区', '中国,安徽省,淮南市,谢家集区', '3', 'xiejiaji', '0554', '232052', 'X', '116.86377', '32.59818'); +INSERT INTO `yoshop_region` VALUES ('1078', '1074', '八公山', '八公山区', '中国,安徽省,淮南市,八公山区', '3', 'bagongshan', '0554', '232072', 'B', '116.83694', '32.62941'); +INSERT INTO `yoshop_region` VALUES ('1079', '1074', '潘集', '潘集区', '中国,安徽省,淮南市,潘集区', '3', 'panji', '0554', '232082', 'P', '116.81622', '32.78287'); +INSERT INTO `yoshop_region` VALUES ('1080', '1074', '凤台', '凤台县', '中国,安徽省,淮南市,凤台县', '3', 'fengtai', '0554', '232100', 'F', '116.71569', '32.70752'); +INSERT INTO `yoshop_region` VALUES ('1081', '1046', '马鞍山', '马鞍山市', '中国,安徽省,马鞍山市', '2', 'ma\'anshan', '0555', '243001', 'M', '118.507906', '31.689362'); +INSERT INTO `yoshop_region` VALUES ('1082', '1081', '花山', '花山区', '中国,安徽省,马鞍山市,花山区', '3', 'huashan', '0555', '243000', 'H', '118.51231', '31.7001'); +INSERT INTO `yoshop_region` VALUES ('1083', '1081', '雨山', '雨山区', '中国,安徽省,马鞍山市,雨山区', '3', 'yushan', '0555', '243071', 'Y', '118.49869', '31.68219'); +INSERT INTO `yoshop_region` VALUES ('1084', '1081', '博望', '博望区', '中国,安徽省,马鞍山市,博望区', '3', 'bowang', '0555', '243131', 'B', '118.844387', '31.561871'); +INSERT INTO `yoshop_region` VALUES ('1085', '1081', '当涂', '当涂县', '中国,安徽省,马鞍山市,当涂县', '3', 'dangtu', '0555', '243100', 'D', '118.49786', '31.57098'); +INSERT INTO `yoshop_region` VALUES ('1086', '1081', '含山', '含山县', '中国,安徽省,马鞍山市,含山县', '3', 'hanshan', '0555', '238100', 'H', '118.105545', '31.727758'); +INSERT INTO `yoshop_region` VALUES ('1087', '1081', '和县', '和县', '中国,安徽省,马鞍山市,和县', '3', 'hexian', '0555', '238200', 'H', '118.351405', '31.741794'); +INSERT INTO `yoshop_region` VALUES ('1088', '1046', '淮北', '淮北市', '中国,安徽省,淮北市', '2', 'huaibei', '0561', '235000', 'H', '116.794664', '33.971707'); +INSERT INTO `yoshop_region` VALUES ('1089', '1088', '杜集', '杜集区', '中国,安徽省,淮北市,杜集区', '3', 'duji', '0561', '235000', 'D', '116.82998', '33.99363'); +INSERT INTO `yoshop_region` VALUES ('1090', '1088', '相山', '相山区', '中国,安徽省,淮北市,相山区', '3', 'xiangshan', '0561', '235000', 'X', '116.79464', '33.95979'); +INSERT INTO `yoshop_region` VALUES ('1091', '1088', '烈山', '烈山区', '中国,安徽省,淮北市,烈山区', '3', 'lieshan', '0561', '235000', 'L', '116.81448', '33.89355'); +INSERT INTO `yoshop_region` VALUES ('1092', '1088', '濉溪', '濉溪县', '中国,安徽省,淮北市,濉溪县', '3', 'suixi', '0561', '235100', null, '116.76785', '33.91455'); +INSERT INTO `yoshop_region` VALUES ('1093', '1046', '铜陵', '铜陵市', '中国,安徽省,铜陵市', '2', 'tongling', '0562', '244000', 'T', '117.816576', '30.929935'); +INSERT INTO `yoshop_region` VALUES ('1094', '1093', '铜官山', '铜官山区', '中国,安徽省,铜陵市,铜官山区', '3', 'tongguanshan', '0562', '244000', 'T', '117.81525', '30.93423'); +INSERT INTO `yoshop_region` VALUES ('1095', '1093', '狮子山', '狮子山区', '中国,安徽省,铜陵市,狮子山区', '3', 'shizishan', '0562', '244000', 'S', '117.89178', '30.92631'); +INSERT INTO `yoshop_region` VALUES ('1096', '1093', '郊区', '郊区', '中国,安徽省,铜陵市,郊区', '3', 'jiaoqu', '0562', '244000', 'J', '117.80868', '30.91976'); +INSERT INTO `yoshop_region` VALUES ('1097', '1093', '铜陵', '铜陵县', '中国,安徽省,铜陵市,铜陵县', '3', 'tongling', '0562', '244100', 'T', '117.79113', '30.95365'); +INSERT INTO `yoshop_region` VALUES ('1098', '1046', '安庆', '安庆市', '中国,安徽省,安庆市', '2', 'anqing', '0556', '246001', 'A', '117.053571', '30.524816'); +INSERT INTO `yoshop_region` VALUES ('1099', '1098', '迎江', '迎江区', '中国,安徽省,安庆市,迎江区', '3', 'yingjiang', '0556', '246001', 'Y', '117.0493', '30.50421'); +INSERT INTO `yoshop_region` VALUES ('1100', '1098', '大观', '大观区', '中国,安徽省,安庆市,大观区', '3', 'daguan', '0556', '246002', 'D', '117.03426', '30.51216'); +INSERT INTO `yoshop_region` VALUES ('1101', '1098', '宜秀', '宜秀区', '中国,安徽省,安庆市,宜秀区', '3', 'yixiu', '0556', '246003', 'Y', '117.06127', '30.50783'); +INSERT INTO `yoshop_region` VALUES ('1102', '1098', '怀宁', '怀宁县', '中国,安徽省,安庆市,怀宁县', '3', 'huaining', '0556', '246100', 'H', '116.82968', '30.73376'); +INSERT INTO `yoshop_region` VALUES ('1103', '1098', '枞阳', '枞阳县', '中国,安徽省,安庆市,枞阳县', '3', 'zongyang', '0556', '246700', null, '117.22015', '30.69956'); +INSERT INTO `yoshop_region` VALUES ('1104', '1098', '潜山', '潜山县', '中国,安徽省,安庆市,潜山县', '3', 'qianshan', '0556', '246300', 'Q', '116.57574', '30.63037'); +INSERT INTO `yoshop_region` VALUES ('1105', '1098', '太湖', '太湖县', '中国,安徽省,安庆市,太湖县', '3', 'taihu', '0556', '246400', 'T', '116.3088', '30.4541'); +INSERT INTO `yoshop_region` VALUES ('1106', '1098', '宿松', '宿松县', '中国,安徽省,安庆市,宿松县', '3', 'susong', '0556', '246500', 'S', '116.12915', '30.1536'); +INSERT INTO `yoshop_region` VALUES ('1107', '1098', '望江', '望江县', '中国,安徽省,安庆市,望江县', '3', 'wangjiang', '0556', '246200', 'W', '116.68814', '30.12585'); +INSERT INTO `yoshop_region` VALUES ('1108', '1098', '岳西', '岳西县', '中国,安徽省,安庆市,岳西县', '3', 'yuexi', '0556', '246600', 'Y', '116.35995', '30.84983'); +INSERT INTO `yoshop_region` VALUES ('1109', '1098', '桐城', '桐城市', '中国,安徽省,安庆市,桐城市', '3', 'tongcheng', '0556', '231400', 'T', '116.95071', '31.05216'); +INSERT INTO `yoshop_region` VALUES ('1110', '1046', '黄山', '黄山市', '中国,安徽省,黄山市', '2', 'huangshan', '0559', '245000', 'H', '118.317325', '29.709239'); +INSERT INTO `yoshop_region` VALUES ('1111', '1110', '屯溪', '屯溪区', '中国,安徽省,黄山市,屯溪区', '3', 'tunxi', '0559', '245000', 'T', '118.33368', '29.71138'); +INSERT INTO `yoshop_region` VALUES ('1112', '1110', '黄山', '黄山区', '中国,安徽省,黄山市,黄山区', '3', 'huangshan', '0559', '242700', 'H', '118.1416', '30.2729'); +INSERT INTO `yoshop_region` VALUES ('1113', '1110', '徽州', '徽州区', '中国,安徽省,黄山市,徽州区', '3', 'huizhou', '0559', '245061', 'H', '118.33654', '29.82784'); +INSERT INTO `yoshop_region` VALUES ('1114', '1110', '歙县', '歙县', '中国,安徽省,黄山市,歙县', '3', 'shexian', '0559', '245200', null, '118.43676', '29.86745'); +INSERT INTO `yoshop_region` VALUES ('1115', '1110', '休宁', '休宁县', '中国,安徽省,黄山市,休宁县', '3', 'xiuning', '0559', '245400', 'X', '118.18136', '29.78607'); +INSERT INTO `yoshop_region` VALUES ('1116', '1110', '黟县', '黟县', '中国,安徽省,黄山市,黟县', '3', 'yixian', '0559', '245500', null, '117.94137', '29.92588'); +INSERT INTO `yoshop_region` VALUES ('1117', '1110', '祁门', '祁门县', '中国,安徽省,黄山市,祁门县', '3', 'qimen', '0559', '245600', 'Q', '117.71847', '29.85723'); +INSERT INTO `yoshop_region` VALUES ('1118', '1046', '滁州', '滁州市', '中国,安徽省,滁州市', '2', 'chuzhou', '0550', '239000', 'C', '118.316264', '32.303627'); +INSERT INTO `yoshop_region` VALUES ('1119', '1118', '琅琊', '琅琊区', '中国,安徽省,滁州市,琅琊区', '3', 'langya', '0550', '239000', 'L', '118.30538', '32.29521'); +INSERT INTO `yoshop_region` VALUES ('1120', '1118', '南谯', '南谯区', '中国,安徽省,滁州市,南谯区', '3', 'nanqiao', '0550', '239000', 'N', '118.31222', '32.31861'); +INSERT INTO `yoshop_region` VALUES ('1121', '1118', '来安', '来安县', '中国,安徽省,滁州市,来安县', '3', 'lai\'an', '0550', '239200', 'L', '118.43438', '32.45176'); +INSERT INTO `yoshop_region` VALUES ('1122', '1118', '全椒', '全椒县', '中国,安徽省,滁州市,全椒县', '3', 'quanjiao', '0550', '239500', 'Q', '118.27291', '32.08524'); +INSERT INTO `yoshop_region` VALUES ('1123', '1118', '定远', '定远县', '中国,安徽省,滁州市,定远县', '3', 'dingyuan', '0550', '233200', 'D', '117.68035', '32.52488'); +INSERT INTO `yoshop_region` VALUES ('1124', '1118', '凤阳', '凤阳县', '中国,安徽省,滁州市,凤阳县', '3', 'fengyang', '0550', '233100', 'F', '117.56454', '32.86507'); +INSERT INTO `yoshop_region` VALUES ('1125', '1118', '天长', '天长市', '中国,安徽省,滁州市,天长市', '3', 'tianchang', '0550', '239300', 'T', '118.99868', '32.69124'); +INSERT INTO `yoshop_region` VALUES ('1126', '1118', '明光', '明光市', '中国,安徽省,滁州市,明光市', '3', 'mingguang', '0550', '239400', 'M', '117.99093', '32.77819'); +INSERT INTO `yoshop_region` VALUES ('1127', '1046', '阜阳', '阜阳市', '中国,安徽省,阜阳市', '2', 'fuyang', '0558', '236033', 'F', '115.819729', '32.896969'); +INSERT INTO `yoshop_region` VALUES ('1128', '1127', '颍州', '颍州区', '中国,安徽省,阜阳市,颍州区', '3', 'yingzhou', '0558', '236001', null, '115.80694', '32.88346'); +INSERT INTO `yoshop_region` VALUES ('1129', '1127', '颍东', '颍东区', '中国,安徽省,阜阳市,颍东区', '3', 'yingdong', '0558', '236058', null, '115.85659', '32.91296'); +INSERT INTO `yoshop_region` VALUES ('1130', '1127', '颍泉', '颍泉区', '中国,安徽省,阜阳市,颍泉区', '3', 'yingquan', '0558', '236045', null, '115.80712', '32.9249'); +INSERT INTO `yoshop_region` VALUES ('1131', '1127', '临泉', '临泉县', '中国,安徽省,阜阳市,临泉县', '3', 'linquan', '0558', '236400', 'L', '115.26232', '33.06758'); +INSERT INTO `yoshop_region` VALUES ('1132', '1127', '太和', '太和县', '中国,安徽省,阜阳市,太和县', '3', 'taihe', '0558', '236600', 'T', '115.62191', '33.16025'); +INSERT INTO `yoshop_region` VALUES ('1133', '1127', '阜南', '阜南县', '中国,安徽省,阜阳市,阜南县', '3', 'funan', '0558', '236300', 'F', '115.58563', '32.63551'); +INSERT INTO `yoshop_region` VALUES ('1134', '1127', '颍上', '颍上县', '中国,安徽省,阜阳市,颍上县', '3', 'yingshang', '0558', '236200', null, '116.26458', '32.62998'); +INSERT INTO `yoshop_region` VALUES ('1135', '1127', '界首', '界首市', '中国,安徽省,阜阳市,界首市', '3', 'jieshou', '0558', '236500', 'J', '115.37445', '33.25714'); +INSERT INTO `yoshop_region` VALUES ('1136', '1046', '宿州', '宿州市', '中国,安徽省,宿州市', '2', 'suzhou', '0557', '234000', 'S', '116.984084', '33.633891'); +INSERT INTO `yoshop_region` VALUES ('1137', '1136', '埇桥', '埇桥区', '中国,安徽省,宿州市,埇桥区', '3', 'yongqiao', '0557', '234000', null, '116.97731', '33.64058'); +INSERT INTO `yoshop_region` VALUES ('1138', '1136', '砀山', '砀山县', '中国,安徽省,宿州市,砀山县', '3', 'dangshan', '0557', '235300', null, '116.35363', '34.42356'); +INSERT INTO `yoshop_region` VALUES ('1139', '1136', '萧县', '萧县', '中国,安徽省,宿州市,萧县', '3', 'xiaoxian', '0557', '235200', 'X', '116.94546', '34.1879'); +INSERT INTO `yoshop_region` VALUES ('1140', '1136', '灵璧', '灵璧县', '中国,安徽省,宿州市,灵璧县', '3', 'lingbi', '0557', '234200', 'L', '117.55813', '33.54339'); +INSERT INTO `yoshop_region` VALUES ('1141', '1136', '泗县', '泗县', '中国,安徽省,宿州市,泗县', '3', 'sixian', '0557', '234300', null, '117.91033', '33.48295'); +INSERT INTO `yoshop_region` VALUES ('1142', '1046', '六安', '六安市', '中国,安徽省,六安市', '2', 'lu\'an', '0564', '237000', 'L', '116.507676', '31.752889'); +INSERT INTO `yoshop_region` VALUES ('1143', '1142', '金安', '金安区', '中国,安徽省,六安市,金安区', '3', 'jin\'an', '0564', '237005', 'J', '116.50912', '31.75573'); +INSERT INTO `yoshop_region` VALUES ('1144', '1142', '裕安', '裕安区', '中国,安徽省,六安市,裕安区', '3', 'yu\'an', '0564', '237010', 'Y', '116.47985', '31.73787'); +INSERT INTO `yoshop_region` VALUES ('1145', '1142', '寿县', '寿县', '中国,安徽省,六安市,寿县', '3', 'shouxian', '0564', '232200', 'S', '116.78466', '32.57653'); +INSERT INTO `yoshop_region` VALUES ('1146', '1142', '霍邱', '霍邱县', '中国,安徽省,六安市,霍邱县', '3', 'huoqiu', '0564', '237400', 'H', '116.27795', '32.353'); +INSERT INTO `yoshop_region` VALUES ('1147', '1142', '舒城', '舒城县', '中国,安徽省,六安市,舒城县', '3', 'shucheng', '0564', '231300', 'S', '116.94491', '31.46413'); +INSERT INTO `yoshop_region` VALUES ('1148', '1142', '金寨', '金寨县', '中国,安徽省,六安市,金寨县', '3', 'jinzhai', '0564', '237300', 'J', '115.93463', '31.7351'); +INSERT INTO `yoshop_region` VALUES ('1149', '1142', '霍山', '霍山县', '中国,安徽省,六安市,霍山县', '3', 'huoshan', '0564', '237200', 'H', '116.33291', '31.3929'); +INSERT INTO `yoshop_region` VALUES ('1150', '1046', '亳州', '亳州市', '中国,安徽省,亳州市', '2', 'bozhou', '0558', '236802', null, '115.782939', '33.869338'); +INSERT INTO `yoshop_region` VALUES ('1151', '1150', '谯城', '谯城区', '中国,安徽省,亳州市,谯城区', '3', 'qiaocheng', '0558', '236800', null, '115.77941', '33.87532'); +INSERT INTO `yoshop_region` VALUES ('1152', '1150', '涡阳', '涡阳县', '中国,安徽省,亳州市,涡阳县', '3', 'guoyang', '0558', '233600', 'W', '116.21682', '33.50911'); +INSERT INTO `yoshop_region` VALUES ('1153', '1150', '蒙城', '蒙城县', '中国,安徽省,亳州市,蒙城县', '3', 'mengcheng', '0558', '233500', 'M', '116.5646', '33.26477'); +INSERT INTO `yoshop_region` VALUES ('1154', '1150', '利辛', '利辛县', '中国,安徽省,亳州市,利辛县', '3', 'lixin', '0558', '236700', 'L', '116.208', '33.14198'); +INSERT INTO `yoshop_region` VALUES ('1155', '1046', '池州', '池州市', '中国,安徽省,池州市', '2', 'chizhou', '0566', '247100', 'C', '117.489157', '30.656037'); +INSERT INTO `yoshop_region` VALUES ('1156', '1155', '贵池', '贵池区', '中国,安徽省,池州市,贵池区', '3', 'guichi', '0566', '247100', 'G', '117.48722', '30.65283'); +INSERT INTO `yoshop_region` VALUES ('1157', '1155', '东至', '东至县', '中国,安徽省,池州市,东至县', '3', 'dongzhi', '0566', '247200', 'D', '117.02719', '30.0969'); +INSERT INTO `yoshop_region` VALUES ('1158', '1155', '石台', '石台县', '中国,安徽省,池州市,石台县', '3', 'shitai', '0566', '245100', 'S', '117.48666', '30.21042'); +INSERT INTO `yoshop_region` VALUES ('1159', '1155', '青阳', '青阳县', '中国,安徽省,池州市,青阳县', '3', 'qingyang', '0566', '242800', 'Q', '117.84744', '30.63932'); +INSERT INTO `yoshop_region` VALUES ('1160', '1046', '宣城', '宣城市', '中国,安徽省,宣城市', '2', 'xuancheng', '0563', '242000', 'X', '118.757995', '30.945667'); +INSERT INTO `yoshop_region` VALUES ('1161', '1160', '宣州', '宣州区', '中国,安徽省,宣城市,宣州区', '3', 'xuanzhou', '0563', '242000', 'X', '118.75462', '30.94439'); +INSERT INTO `yoshop_region` VALUES ('1162', '1160', '郎溪', '郎溪县', '中国,安徽省,宣城市,郎溪县', '3', 'langxi', '0563', '242100', 'L', '119.17923', '31.12599'); +INSERT INTO `yoshop_region` VALUES ('1163', '1160', '广德', '广德县', '中国,安徽省,宣城市,广德县', '3', 'guangde', '0563', '242200', 'G', '119.41769', '30.89371'); +INSERT INTO `yoshop_region` VALUES ('1164', '1160', '泾县', '泾县', '中国,安徽省,宣城市,泾县', '3', 'jingxian', '0563', '242500', null, '118.41964', '30.69498'); +INSERT INTO `yoshop_region` VALUES ('1165', '1160', '绩溪', '绩溪县', '中国,安徽省,宣城市,绩溪县', '3', 'jixi', '0563', '245300', 'J', '118.59765', '30.07069'); +INSERT INTO `yoshop_region` VALUES ('1166', '1160', '旌德', '旌德县', '中国,安徽省,宣城市,旌德县', '3', 'jingde', '0563', '242600', null, '118.54299', '30.28898'); +INSERT INTO `yoshop_region` VALUES ('1167', '1160', '宁国', '宁国市', '中国,安徽省,宣城市,宁国市', '3', 'ningguo', '0563', '242300', 'N', '118.98349', '30.6238'); +INSERT INTO `yoshop_region` VALUES ('1168', '0', '福建', '福建省', '中国,福建省', '1', 'fujian', '', '', 'F', '119.306239', '26.075302'); +INSERT INTO `yoshop_region` VALUES ('1169', '1168', '福州', '福州市', '中国,福建省,福州市', '2', 'fuzhou', '0591', '350001', 'F', '119.306239', '26.075302'); +INSERT INTO `yoshop_region` VALUES ('1170', '1169', '鼓楼', '鼓楼区', '中国,福建省,福州市,鼓楼区', '3', 'gulou', '0591', '350001', 'G', '119.30384', '26.08225'); +INSERT INTO `yoshop_region` VALUES ('1171', '1169', '台江', '台江区', '中国,福建省,福州市,台江区', '3', 'taijiang', '0591', '350004', 'T', '119.30899', '26.06204'); +INSERT INTO `yoshop_region` VALUES ('1172', '1169', '仓山', '仓山区', '中国,福建省,福州市,仓山区', '3', 'cangshan', '0591', '350007', 'C', '119.31543', '26.04335'); +INSERT INTO `yoshop_region` VALUES ('1173', '1169', '马尾', '马尾区', '中国,福建省,福州市,马尾区', '3', 'mawei', '0591', '350015', 'M', '119.4555', '25.98942'); +INSERT INTO `yoshop_region` VALUES ('1174', '1169', '晋安', '晋安区', '中国,福建省,福州市,晋安区', '3', 'jin\'an', '0591', '350011', 'J', '119.32828', '26.0818'); +INSERT INTO `yoshop_region` VALUES ('1175', '1169', '闽侯', '闽侯县', '中国,福建省,福州市,闽侯县', '3', 'minhou', '0591', '350100', 'M', '119.13388', '26.15014'); +INSERT INTO `yoshop_region` VALUES ('1176', '1169', '连江', '连江县', '中国,福建省,福州市,连江县', '3', 'lianjiang', '0591', '350500', 'L', '119.53433', '26.19466'); +INSERT INTO `yoshop_region` VALUES ('1177', '1169', '罗源', '罗源县', '中国,福建省,福州市,罗源县', '3', 'luoyuan', '0591', '350600', 'L', '119.5509', '26.48752'); +INSERT INTO `yoshop_region` VALUES ('1178', '1169', '闽清', '闽清县', '中国,福建省,福州市,闽清县', '3', 'minqing', '0591', '350800', 'M', '118.8623', '26.21901'); +INSERT INTO `yoshop_region` VALUES ('1179', '1169', '永泰', '永泰县', '中国,福建省,福州市,永泰县', '3', 'yongtai', '0591', '350700', 'Y', '118.936', '25.86816'); +INSERT INTO `yoshop_region` VALUES ('1180', '1169', '平潭', '平潭县', '中国,福建省,福州市,平潭县', '3', 'pingtan', '0591', '350400', 'P', '119.791197', '25.503672'); +INSERT INTO `yoshop_region` VALUES ('1181', '1169', '福清', '福清市', '中国,福建省,福州市,福清市', '3', 'fuqing', '0591', '350300', 'F', '119.38507', '25.72086'); +INSERT INTO `yoshop_region` VALUES ('1182', '1169', '长乐', '长乐市', '中国,福建省,福州市,长乐市', '3', 'changle', '0591', '350200', 'C', '119.52313', '25.96276'); +INSERT INTO `yoshop_region` VALUES ('1183', '1168', '厦门', '厦门市', '中国,福建省,厦门市', '2', 'xiamen', '0592', '361003', 'X', '118.11022', '24.490474'); +INSERT INTO `yoshop_region` VALUES ('1184', '1183', '思明', '思明区', '中国,福建省,厦门市,思明区', '3', 'siming', '0592', '361001', 'S', '118.08233', '24.44543'); +INSERT INTO `yoshop_region` VALUES ('1185', '1183', '海沧', '海沧区', '中国,福建省,厦门市,海沧区', '3', 'haicang', '0592', '361026', 'H', '118.03289', '24.48461'); +INSERT INTO `yoshop_region` VALUES ('1186', '1183', '湖里', '湖里区', '中国,福建省,厦门市,湖里区', '3', 'huli', '0592', '361006', 'H', '118.14621', '24.51253'); +INSERT INTO `yoshop_region` VALUES ('1187', '1183', '集美', '集美区', '中国,福建省,厦门市,集美区', '3', 'jimei', '0592', '361021', 'J', '118.09719', '24.57584'); +INSERT INTO `yoshop_region` VALUES ('1188', '1183', '同安', '同安区', '中国,福建省,厦门市,同安区', '3', 'tong\'an', '0592', '361100', 'T', '118.15197', '24.72308'); +INSERT INTO `yoshop_region` VALUES ('1189', '1183', '翔安', '翔安区', '中国,福建省,厦门市,翔安区', '3', 'xiang\'an', '0592', '361101', 'X', '118.24783', '24.61863'); +INSERT INTO `yoshop_region` VALUES ('1190', '1168', '莆田', '莆田市', '中国,福建省,莆田市', '2', 'putian', '0594', '351100', 'P', '119.007558', '25.431011'); +INSERT INTO `yoshop_region` VALUES ('1191', '1190', '城厢', '城厢区', '中国,福建省,莆田市,城厢区', '3', 'chengxiang', '0594', '351100', 'C', '118.99462', '25.41872'); +INSERT INTO `yoshop_region` VALUES ('1192', '1190', '涵江', '涵江区', '中国,福建省,莆田市,涵江区', '3', 'hanjiang', '0594', '351111', 'H', '119.11621', '25.45876'); +INSERT INTO `yoshop_region` VALUES ('1193', '1190', '荔城', '荔城区', '中国,福建省,莆田市,荔城区', '3', 'licheng', '0594', '351100', 'L', '119.01339', '25.43369'); +INSERT INTO `yoshop_region` VALUES ('1194', '1190', '秀屿', '秀屿区', '中国,福建省,莆田市,秀屿区', '3', 'xiuyu', '0594', '351152', 'X', '119.10553', '25.31831'); +INSERT INTO `yoshop_region` VALUES ('1195', '1190', '仙游', '仙游县', '中国,福建省,莆田市,仙游县', '3', 'xianyou', '0594', '351200', 'X', '118.69177', '25.36214'); +INSERT INTO `yoshop_region` VALUES ('1196', '1168', '三明', '三明市', '中国,福建省,三明市', '2', 'sanming', '0598', '365000', 'S', '117.635001', '26.265444'); +INSERT INTO `yoshop_region` VALUES ('1197', '1196', '梅列', '梅列区', '中国,福建省,三明市,梅列区', '3', 'meilie', '0598', '365000', 'M', '117.64585', '26.27171'); +INSERT INTO `yoshop_region` VALUES ('1198', '1196', '三元', '三元区', '中国,福建省,三明市,三元区', '3', 'sanyuan', '0598', '365001', 'S', '117.60788', '26.23372'); +INSERT INTO `yoshop_region` VALUES ('1199', '1196', '明溪', '明溪县', '中国,福建省,三明市,明溪县', '3', 'mingxi', '0598', '365200', 'M', '117.20498', '26.35294'); +INSERT INTO `yoshop_region` VALUES ('1200', '1196', '清流', '清流县', '中国,福建省,三明市,清流县', '3', 'qingliu', '0598', '365300', 'Q', '116.8146', '26.17144'); +INSERT INTO `yoshop_region` VALUES ('1201', '1196', '宁化', '宁化县', '中国,福建省,三明市,宁化县', '3', 'ninghua', '0598', '365400', 'N', '116.66101', '26.25874'); +INSERT INTO `yoshop_region` VALUES ('1202', '1196', '大田', '大田县', '中国,福建省,三明市,大田县', '3', 'datian', '0598', '366100', 'D', '117.8471', '25.6926'); +INSERT INTO `yoshop_region` VALUES ('1203', '1196', '尤溪', '尤溪县', '中国,福建省,三明市,尤溪县', '3', 'youxi', '0598', '365100', 'Y', '118.19049', '26.17002'); +INSERT INTO `yoshop_region` VALUES ('1204', '1196', '沙县', '沙县', '中国,福建省,三明市,沙县', '3', 'shaxian', '0598', '365500', 'S', '117.79266', '26.39615'); +INSERT INTO `yoshop_region` VALUES ('1205', '1196', '将乐', '将乐县', '中国,福建省,三明市,将乐县', '3', 'jiangle', '0598', '353300', 'J', '117.47317', '26.72837'); +INSERT INTO `yoshop_region` VALUES ('1206', '1196', '泰宁', '泰宁县', '中国,福建省,三明市,泰宁县', '3', 'taining', '0598', '354400', 'T', '117.17578', '26.9001'); +INSERT INTO `yoshop_region` VALUES ('1207', '1196', '建宁', '建宁县', '中国,福建省,三明市,建宁县', '3', 'jianning', '0598', '354500', 'J', '116.84603', '26.83091'); +INSERT INTO `yoshop_region` VALUES ('1208', '1196', '永安', '永安市', '中国,福建省,三明市,永安市', '3', 'yong\'an', '0598', '366000', 'Y', '117.36517', '25.94136'); +INSERT INTO `yoshop_region` VALUES ('1209', '1168', '泉州', '泉州市', '中国,福建省,泉州市', '2', 'quanzhou', '0595', '362000', 'Q', '118.589421', '24.908853'); +INSERT INTO `yoshop_region` VALUES ('1210', '1209', '鲤城', '鲤城区', '中国,福建省,泉州市,鲤城区', '3', 'licheng', '0595', '362000', 'L', '118.56591', '24.88741'); +INSERT INTO `yoshop_region` VALUES ('1211', '1209', '丰泽', '丰泽区', '中国,福建省,泉州市,丰泽区', '3', 'fengze', '0595', '362000', 'F', '118.61328', '24.89119'); +INSERT INTO `yoshop_region` VALUES ('1212', '1209', '洛江', '洛江区', '中国,福建省,泉州市,洛江区', '3', 'luojiang', '0595', '362011', 'L', '118.67111', '24.93984'); +INSERT INTO `yoshop_region` VALUES ('1213', '1209', '泉港', '泉港区', '中国,福建省,泉州市,泉港区', '3', 'quangang', '0595', '362114', 'Q', '118.91586', '25.12005'); +INSERT INTO `yoshop_region` VALUES ('1214', '1209', '惠安', '惠安县', '中国,福建省,泉州市,惠安县', '3', 'hui\'an', '0595', '362100', 'H', '118.79687', '25.03059'); +INSERT INTO `yoshop_region` VALUES ('1215', '1209', '安溪', '安溪县', '中国,福建省,泉州市,安溪县', '3', 'anxi', '0595', '362400', 'A', '118.18719', '25.05627'); +INSERT INTO `yoshop_region` VALUES ('1216', '1209', '永春', '永春县', '中国,福建省,泉州市,永春县', '3', 'yongchun', '0595', '362600', 'Y', '118.29437', '25.32183'); +INSERT INTO `yoshop_region` VALUES ('1217', '1209', '德化', '德化县', '中国,福建省,泉州市,德化县', '3', 'dehua', '0595', '362500', 'D', '118.24176', '25.49224'); +INSERT INTO `yoshop_region` VALUES ('1218', '1209', '金门', '金门县', '中国,福建省,泉州市,金门县', '3', 'jinmen', '', '', 'J', '118.32263', '24.42922'); +INSERT INTO `yoshop_region` VALUES ('1219', '1209', '石狮', '石狮市', '中国,福建省,泉州市,石狮市', '3', 'shishi', '0595', '362700', 'S', '118.64779', '24.73242'); +INSERT INTO `yoshop_region` VALUES ('1220', '1209', '晋江', '晋江市', '中国,福建省,泉州市,晋江市', '3', 'jinjiang', '0595', '362200', 'J', '118.55194', '24.78141'); +INSERT INTO `yoshop_region` VALUES ('1221', '1209', '南安', '南安市', '中国,福建省,泉州市,南安市', '3', 'nan\'an', '0595', '362300', 'N', '118.38589', '24.96055'); +INSERT INTO `yoshop_region` VALUES ('1222', '1168', '漳州', '漳州市', '中国,福建省,漳州市', '2', 'zhangzhou', '0596', '363005', 'Z', '117.661801', '24.510897'); +INSERT INTO `yoshop_region` VALUES ('1223', '1222', '芗城', '芗城区', '中国,福建省,漳州市,芗城区', '3', 'xiangcheng', '0596', '363000', null, '117.65402', '24.51081'); +INSERT INTO `yoshop_region` VALUES ('1224', '1222', '龙文', '龙文区', '中国,福建省,漳州市,龙文区', '3', 'longwen', '0596', '363005', 'L', '117.70971', '24.50323'); +INSERT INTO `yoshop_region` VALUES ('1225', '1222', '云霄', '云霄县', '中国,福建省,漳州市,云霄县', '3', 'yunxiao', '0596', '363300', 'Y', '117.34051', '23.95534'); +INSERT INTO `yoshop_region` VALUES ('1226', '1222', '漳浦', '漳浦县', '中国,福建省,漳州市,漳浦县', '3', 'zhangpu', '0596', '363200', 'Z', '117.61367', '24.11706'); +INSERT INTO `yoshop_region` VALUES ('1227', '1222', '诏安', '诏安县', '中国,福建省,漳州市,诏安县', '3', 'zhao\'an', '0596', '363500', null, '117.17501', '23.71148'); +INSERT INTO `yoshop_region` VALUES ('1228', '1222', '长泰', '长泰县', '中国,福建省,漳州市,长泰县', '3', 'changtai', '0596', '363900', 'C', '117.75924', '24.62526'); +INSERT INTO `yoshop_region` VALUES ('1229', '1222', '东山', '东山县', '中国,福建省,漳州市,东山县', '3', 'dongshan', '0596', '363400', 'D', '117.42822', '23.70109'); +INSERT INTO `yoshop_region` VALUES ('1230', '1222', '南靖', '南靖县', '中国,福建省,漳州市,南靖县', '3', 'nanjing', '0596', '363600', 'N', '117.35736', '24.51448'); +INSERT INTO `yoshop_region` VALUES ('1231', '1222', '平和', '平和县', '中国,福建省,漳州市,平和县', '3', 'pinghe', '0596', '363700', 'P', '117.3124', '24.36395'); +INSERT INTO `yoshop_region` VALUES ('1232', '1222', '华安', '华安县', '中国,福建省,漳州市,华安县', '3', 'hua\'an', '0596', '363800', 'H', '117.54077', '25.00563'); +INSERT INTO `yoshop_region` VALUES ('1233', '1222', '龙海', '龙海市', '中国,福建省,漳州市,龙海市', '3', 'longhai', '0596', '363100', 'L', '117.81802', '24.44655'); +INSERT INTO `yoshop_region` VALUES ('1234', '1168', '南平', '南平市', '中国,福建省,南平市', '2', 'nanping', '0599', '353000', 'N', '118.178459', '26.635627'); +INSERT INTO `yoshop_region` VALUES ('1235', '1234', '延平', '延平区', '中国,福建省,南平市,延平区', '3', 'yanping', '0600', '353000', 'Y', '118.18189', '26.63745'); +INSERT INTO `yoshop_region` VALUES ('1236', '1234', '建阳', '建阳区', '中国,福建省,南平市,建阳区', '3', 'jianyang', '0599', '354200', 'J', '118.12267', '27.332067'); +INSERT INTO `yoshop_region` VALUES ('1237', '1234', '顺昌', '顺昌县', '中国,福建省,南平市,顺昌县', '3', 'shunchang', '0605', '353200', 'S', '117.8103', '26.79298'); +INSERT INTO `yoshop_region` VALUES ('1238', '1234', '浦城', '浦城县', '中国,福建省,南平市,浦城县', '3', 'pucheng', '0606', '353400', 'P', '118.54007', '27.91888'); +INSERT INTO `yoshop_region` VALUES ('1239', '1234', '光泽', '光泽县', '中国,福建省,南平市,光泽县', '3', 'guangze', '0607', '354100', 'G', '117.33346', '27.54231'); +INSERT INTO `yoshop_region` VALUES ('1240', '1234', '松溪', '松溪县', '中国,福建省,南平市,松溪县', '3', 'songxi', '0608', '353500', 'S', '118.78533', '27.52624'); +INSERT INTO `yoshop_region` VALUES ('1241', '1234', '政和', '政和县', '中国,福建省,南平市,政和县', '3', 'zhenghe', '0609', '353600', 'Z', '118.85571', '27.36769'); +INSERT INTO `yoshop_region` VALUES ('1242', '1234', '邵武', '邵武市', '中国,福建省,南平市,邵武市', '3', 'shaowu', '0601', '354000', 'S', '117.4924', '27.34033'); +INSERT INTO `yoshop_region` VALUES ('1243', '1234', '武夷山', '武夷山市', '中国,福建省,南平市,武夷山市', '3', 'wuyishan', '0602', '354300', 'W', '118.03665', '27.75543'); +INSERT INTO `yoshop_region` VALUES ('1244', '1234', '建瓯', '建瓯市', '中国,福建省,南平市,建瓯市', '3', 'jianou', '0603', '353100', 'J', '118.29766', '27.02301'); +INSERT INTO `yoshop_region` VALUES ('1245', '1168', '龙岩', '龙岩市', '中国,福建省,龙岩市', '2', 'longyan', '0597', '364000', 'L', '117.02978', '25.091603'); +INSERT INTO `yoshop_region` VALUES ('1246', '1245', '新罗', '新罗区', '中国,福建省,龙岩市,新罗区', '3', 'xinluo', '0597', '364000', 'X', '117.03693', '25.09834'); +INSERT INTO `yoshop_region` VALUES ('1247', '1245', '长汀', '长汀县', '中国,福建省,龙岩市,长汀县', '3', 'changting', '0597', '366300', 'C', '116.35888', '25.82773'); +INSERT INTO `yoshop_region` VALUES ('1248', '1245', '永定', '永定区', '中国,福建省,龙岩市,永定区', '3', 'yongding', '0597', '364100', 'Y', '116.73199', '24.72302'); +INSERT INTO `yoshop_region` VALUES ('1249', '1245', '上杭', '上杭县', '中国,福建省,龙岩市,上杭县', '3', 'shanghang', '0597', '364200', 'S', '116.42022', '25.04943'); +INSERT INTO `yoshop_region` VALUES ('1250', '1245', '武平', '武平县', '中国,福建省,龙岩市,武平县', '3', 'wuping', '0597', '364300', 'W', '116.10229', '25.09244'); +INSERT INTO `yoshop_region` VALUES ('1251', '1245', '连城', '连城县', '中国,福建省,龙岩市,连城县', '3', 'liancheng', '0597', '366200', 'L', '116.75454', '25.7103'); +INSERT INTO `yoshop_region` VALUES ('1252', '1245', '漳平', '漳平市', '中国,福建省,龙岩市,漳平市', '3', 'zhangping', '0597', '364400', 'Z', '117.41992', '25.29109'); +INSERT INTO `yoshop_region` VALUES ('1253', '1168', '宁德', '宁德市', '中国,福建省,宁德市', '2', 'ningde', '0593', '352100', 'N', '119.527082', '26.65924'); +INSERT INTO `yoshop_region` VALUES ('1254', '1253', '蕉城', '蕉城区', '中国,福建省,宁德市,蕉城区', '3', 'jiaocheng', '0593', '352100', 'J', '119.52643', '26.66048'); +INSERT INTO `yoshop_region` VALUES ('1255', '1253', '霞浦', '霞浦县', '中国,福建省,宁德市,霞浦县', '3', 'xiapu', '0593', '355100', 'X', '119.99893', '26.88578'); +INSERT INTO `yoshop_region` VALUES ('1256', '1253', '古田', '古田县', '中国,福建省,宁德市,古田县', '3', 'gutian', '0593', '352200', 'G', '118.74688', '26.57682'); +INSERT INTO `yoshop_region` VALUES ('1257', '1253', '屏南', '屏南县', '中国,福建省,宁德市,屏南县', '3', 'pingnan', '0593', '352300', 'P', '118.98861', '26.91099'); +INSERT INTO `yoshop_region` VALUES ('1258', '1253', '寿宁', '寿宁县', '中国,福建省,宁德市,寿宁县', '3', 'shouning', '0593', '355500', 'S', '119.5039', '27.45996'); +INSERT INTO `yoshop_region` VALUES ('1259', '1253', '周宁', '周宁县', '中国,福建省,宁德市,周宁县', '3', 'zhouning', '0593', '355400', 'Z', '119.33837', '27.10664'); +INSERT INTO `yoshop_region` VALUES ('1260', '1253', '柘荣', '柘荣县', '中国,福建省,宁德市,柘荣县', '3', 'zherong', '0593', '355300', null, '119.89971', '27.23543'); +INSERT INTO `yoshop_region` VALUES ('1261', '1253', '福安', '福安市', '中国,福建省,宁德市,福安市', '3', 'fu\'an', '0593', '355000', 'F', '119.6495', '27.08673'); +INSERT INTO `yoshop_region` VALUES ('1262', '1253', '福鼎', '福鼎市', '中国,福建省,宁德市,福鼎市', '3', 'fuding', '0593', '355200', 'F', '120.21664', '27.3243'); +INSERT INTO `yoshop_region` VALUES ('1263', '0', '江西', '江西省', '中国,江西省', '1', 'jiangxi', '', '', 'J', '115.892151', '28.676493'); +INSERT INTO `yoshop_region` VALUES ('1264', '1263', '南昌', '南昌市', '中国,江西省,南昌市', '2', 'nanchang', '0791', '330008', 'N', '115.892151', '28.676493'); +INSERT INTO `yoshop_region` VALUES ('1265', '1264', '东湖', '东湖区', '中国,江西省,南昌市,东湖区', '3', 'donghu', '0791', '330006', 'D', '115.8988', '28.68505'); +INSERT INTO `yoshop_region` VALUES ('1266', '1264', '西湖', '西湖区', '中国,江西省,南昌市,西湖区', '3', 'xihu', '0791', '330009', 'X', '115.87728', '28.65688'); +INSERT INTO `yoshop_region` VALUES ('1267', '1264', '青云谱', '青云谱区', '中国,江西省,南昌市,青云谱区', '3', 'qingyunpu', '0791', '330001', 'Q', '115.915', '28.63199'); +INSERT INTO `yoshop_region` VALUES ('1268', '1264', '湾里', '湾里区', '中国,江西省,南昌市,湾里区', '3', 'wanli', '0791', '330004', 'W', '115.73104', '28.71529'); +INSERT INTO `yoshop_region` VALUES ('1269', '1264', '青山湖', '青山湖区', '中国,江西省,南昌市,青山湖区', '3', 'qingshanhu', '0791', '330029', 'Q', '115.9617', '28.68206'); +INSERT INTO `yoshop_region` VALUES ('1270', '1264', '南昌', '南昌县', '中国,江西省,南昌市,南昌县', '3', 'nanchang', '0791', '330200', 'N', '115.94393', '28.54559'); +INSERT INTO `yoshop_region` VALUES ('1271', '1264', '新建', '新建县', '中国,江西省,南昌市,新建县', '3', 'xinjian', '0791', '330100', 'X', '115.81546', '28.69248'); +INSERT INTO `yoshop_region` VALUES ('1272', '1264', '安义', '安义县', '中国,江西省,南昌市,安义县', '3', 'anyi', '0791', '330500', 'A', '115.54879', '28.84602'); +INSERT INTO `yoshop_region` VALUES ('1273', '1264', '进贤', '进贤县', '中国,江西省,南昌市,进贤县', '3', 'jinxian', '0791', '331700', 'J', '116.24087', '28.37679'); +INSERT INTO `yoshop_region` VALUES ('1274', '1263', '景德镇', '景德镇市', '中国,江西省,景德镇市', '2', 'jingdezhen', '0798', '333000', 'J', '117.214664', '29.29256'); +INSERT INTO `yoshop_region` VALUES ('1275', '1274', '昌江', '昌江区', '中国,江西省,景德镇市,昌江区', '3', 'changjiang', '0799', '333000', 'C', '117.18359', '29.27321'); +INSERT INTO `yoshop_region` VALUES ('1276', '1274', '珠山', '珠山区', '中国,江西省,景德镇市,珠山区', '3', 'zhushan', '0800', '333000', 'Z', '117.20233', '29.30127'); +INSERT INTO `yoshop_region` VALUES ('1277', '1274', '浮梁', '浮梁县', '中国,江西省,景德镇市,浮梁县', '3', 'fuliang', '0802', '333400', 'F', '117.21517', '29.35156'); +INSERT INTO `yoshop_region` VALUES ('1278', '1274', '乐平', '乐平市', '中国,江西省,景德镇市,乐平市', '3', 'leping', '0801', '333300', 'L', '117.12887', '28.96295'); +INSERT INTO `yoshop_region` VALUES ('1279', '1263', '萍乡', '萍乡市', '中国,江西省,萍乡市', '2', 'pingxiang', '0799', '337000', 'P', '113.852186', '27.622946'); +INSERT INTO `yoshop_region` VALUES ('1280', '1279', '安源', '安源区', '中国,江西省,萍乡市,安源区', '3', 'anyuan', '0800', '337000', 'A', '113.89135', '27.61653'); +INSERT INTO `yoshop_region` VALUES ('1281', '1279', '湘东', '湘东区', '中国,江西省,萍乡市,湘东区', '3', 'xiangdong', '0801', '337016', 'X', '113.73294', '27.64007'); +INSERT INTO `yoshop_region` VALUES ('1282', '1279', '莲花', '莲花县', '中国,江西省,萍乡市,莲花县', '3', 'lianhua', '0802', '337100', 'L', '113.96142', '27.12866'); +INSERT INTO `yoshop_region` VALUES ('1283', '1279', '上栗', '上栗县', '中国,江西省,萍乡市,上栗县', '3', 'shangli', '0803', '337009', 'S', '113.79403', '27.87467'); +INSERT INTO `yoshop_region` VALUES ('1284', '1279', '芦溪', '芦溪县', '中国,江西省,萍乡市,芦溪县', '3', 'luxi', '0804', '337053', 'L', '114.02951', '27.63063'); +INSERT INTO `yoshop_region` VALUES ('1285', '1263', '九江', '九江市', '中国,江西省,九江市', '2', 'jiujiang', '0792', '332000', 'J', '115.992811', '29.712034'); +INSERT INTO `yoshop_region` VALUES ('1286', '1285', '庐山', '庐山区', '中国,江西省,九江市,庐山区', '3', 'lushan', '0792', '332005', 'L', '115.98904', '29.67177'); +INSERT INTO `yoshop_region` VALUES ('1287', '1285', '浔阳', '浔阳区', '中国,江西省,九江市,浔阳区', '3', 'xunyang', '0792', '332000', null, '115.98986', '29.72786'); +INSERT INTO `yoshop_region` VALUES ('1288', '1285', '九江', '九江县', '中国,江西省,九江市,九江县', '3', 'jiujiang', '0792', '332100', 'J', '115.91128', '29.60852'); +INSERT INTO `yoshop_region` VALUES ('1289', '1285', '武宁', '武宁县', '中国,江西省,九江市,武宁县', '3', 'wuning', '0792', '332300', 'W', '115.10061', '29.2584'); +INSERT INTO `yoshop_region` VALUES ('1290', '1285', '修水', '修水县', '中国,江西省,九江市,修水县', '3', 'xiushui', '0792', '332400', 'X', '114.54684', '29.02539'); +INSERT INTO `yoshop_region` VALUES ('1291', '1285', '永修', '永修县', '中国,江西省,九江市,永修县', '3', 'yongxiu', '0792', '330300', 'Y', '115.80911', '29.02093'); +INSERT INTO `yoshop_region` VALUES ('1292', '1285', '德安', '德安县', '中国,江西省,九江市,德安县', '3', 'de\'an', '0792', '330400', 'D', '115.75601', '29.31341'); +INSERT INTO `yoshop_region` VALUES ('1293', '1285', '星子', '星子县', '中国,江西省,九江市,星子县', '3', 'xingzi', '0792', '332800', 'X', '116.04492', '29.44608'); +INSERT INTO `yoshop_region` VALUES ('1294', '1285', '都昌', '都昌县', '中国,江西省,九江市,都昌县', '3', 'duchang', '0792', '332600', 'D', '116.20401', '29.27327'); +INSERT INTO `yoshop_region` VALUES ('1295', '1285', '湖口', '湖口县', '中国,江西省,九江市,湖口县', '3', 'hukou', '0792', '332500', 'H', '116.21853', '29.73818'); +INSERT INTO `yoshop_region` VALUES ('1296', '1285', '彭泽', '彭泽县', '中国,江西省,九江市,彭泽县', '3', 'pengze', '0792', '332700', 'P', '116.55011', '29.89589'); +INSERT INTO `yoshop_region` VALUES ('1297', '1285', '瑞昌', '瑞昌市', '中国,江西省,九江市,瑞昌市', '3', 'ruichang', '0792', '332200', 'R', '115.66705', '29.67183'); +INSERT INTO `yoshop_region` VALUES ('1298', '1285', '共青城', '共青城市', '中国,江西省,九江市,共青城市', '3', 'gongqingcheng', '0792', '332020', 'G', '115.801939', '29.238785'); +INSERT INTO `yoshop_region` VALUES ('1299', '1263', '新余', '新余市', '中国,江西省,新余市', '2', 'xinyu', '0790', '338025', 'X', '114.930835', '27.810834'); +INSERT INTO `yoshop_region` VALUES ('1300', '1299', '渝水', '渝水区', '中国,江西省,新余市,渝水区', '3', 'yushui', '0790', '338025', 'Y', '114.944', '27.80098'); +INSERT INTO `yoshop_region` VALUES ('1301', '1299', '分宜', '分宜县', '中国,江西省,新余市,分宜县', '3', 'fenyi', '0790', '336600', 'F', '114.69189', '27.81475'); +INSERT INTO `yoshop_region` VALUES ('1302', '1263', '鹰潭', '鹰潭市', '中国,江西省,鹰潭市', '2', 'yingtan', '0701', '335000', 'Y', '117.033838', '28.238638'); +INSERT INTO `yoshop_region` VALUES ('1303', '1302', '月湖', '月湖区', '中国,江西省,鹰潭市,月湖区', '3', 'yuehu', '0701', '335000', 'Y', '117.03732', '28.23913'); +INSERT INTO `yoshop_region` VALUES ('1304', '1302', '余江', '余江县', '中国,江西省,鹰潭市,余江县', '3', 'yujiang', '0701', '335200', 'Y', '116.81851', '28.21034'); +INSERT INTO `yoshop_region` VALUES ('1305', '1302', '贵溪', '贵溪市', '中国,江西省,鹰潭市,贵溪市', '3', 'guixi', '0701', '335400', 'G', '117.24246', '28.2926'); +INSERT INTO `yoshop_region` VALUES ('1306', '1263', '赣州', '赣州市', '中国,江西省,赣州市', '2', 'ganzhou', '0797', '341000', 'G', '114.940278', '25.85097'); +INSERT INTO `yoshop_region` VALUES ('1307', '1306', '章贡', '章贡区', '中国,江西省,赣州市,章贡区', '3', 'zhanggong', '0797', '341000', 'Z', '114.94284', '25.8624'); +INSERT INTO `yoshop_region` VALUES ('1308', '1306', '南康', '南康区', '中国,江西省,赣州市,南康区', '3', 'nankang', '0797', '341400', 'N', '114.756933', '25.661721'); +INSERT INTO `yoshop_region` VALUES ('1309', '1306', '赣县', '赣县', '中国,江西省,赣州市,赣县', '3', 'ganxian', '0797', '341100', 'G', '115.01171', '25.86149'); +INSERT INTO `yoshop_region` VALUES ('1310', '1306', '信丰', '信丰县', '中国,江西省,赣州市,信丰县', '3', 'xinfeng', '0797', '341600', 'X', '114.92279', '25.38612'); +INSERT INTO `yoshop_region` VALUES ('1311', '1306', '大余', '大余县', '中国,江西省,赣州市,大余县', '3', 'dayu', '0797', '341500', 'D', '114.35757', '25.39561'); +INSERT INTO `yoshop_region` VALUES ('1312', '1306', '上犹', '上犹县', '中国,江西省,赣州市,上犹县', '3', 'shangyou', '0797', '341200', 'S', '114.54138', '25.79567'); +INSERT INTO `yoshop_region` VALUES ('1313', '1306', '崇义', '崇义县', '中国,江西省,赣州市,崇义县', '3', 'chongyi', '0797', '341300', 'C', '114.30835', '25.68186'); +INSERT INTO `yoshop_region` VALUES ('1314', '1306', '安远', '安远县', '中国,江西省,赣州市,安远县', '3', 'anyuan', '0797', '342100', 'A', '115.39483', '25.1371'); +INSERT INTO `yoshop_region` VALUES ('1315', '1306', '龙南', '龙南县', '中国,江西省,赣州市,龙南县', '3', 'longnan', '0797', '341700', 'L', '114.78994', '24.91086'); +INSERT INTO `yoshop_region` VALUES ('1316', '1306', '定南', '定南县', '中国,江西省,赣州市,定南县', '3', 'dingnan', '0797', '341900', 'D', '115.02713', '24.78395'); +INSERT INTO `yoshop_region` VALUES ('1317', '1306', '全南', '全南县', '中国,江西省,赣州市,全南县', '3', 'quannan', '0797', '341800', 'Q', '114.5292', '24.74324'); +INSERT INTO `yoshop_region` VALUES ('1318', '1306', '宁都', '宁都县', '中国,江西省,赣州市,宁都县', '3', 'ningdu', '0797', '342800', 'N', '116.01565', '26.47227'); +INSERT INTO `yoshop_region` VALUES ('1319', '1306', '于都', '于都县', '中国,江西省,赣州市,于都县', '3', 'yudu', '0797', '342300', 'Y', '115.41415', '25.95257'); +INSERT INTO `yoshop_region` VALUES ('1320', '1306', '兴国', '兴国县', '中国,江西省,赣州市,兴国县', '3', 'xingguo', '0797', '342400', 'X', '115.36309', '26.33776'); +INSERT INTO `yoshop_region` VALUES ('1321', '1306', '会昌', '会昌县', '中国,江西省,赣州市,会昌县', '3', 'huichang', '0797', '342600', 'H', '115.78555', '25.60068'); +INSERT INTO `yoshop_region` VALUES ('1322', '1306', '寻乌', '寻乌县', '中国,江西省,赣州市,寻乌县', '3', 'xunwu', '0797', '342200', 'X', '115.64852', '24.95513'); +INSERT INTO `yoshop_region` VALUES ('1323', '1306', '石城', '石城县', '中国,江西省,赣州市,石城县', '3', 'shicheng', '0797', '342700', 'S', '116.3442', '26.32617'); +INSERT INTO `yoshop_region` VALUES ('1324', '1306', '瑞金', '瑞金市', '中国,江西省,赣州市,瑞金市', '3', 'ruijin', '0797', '342500', 'R', '116.02703', '25.88557'); +INSERT INTO `yoshop_region` VALUES ('1325', '1263', '吉安', '吉安市', '中国,江西省,吉安市', '2', 'ji\'an', '0796', '343000', 'J', '114.986373', '27.111699'); +INSERT INTO `yoshop_region` VALUES ('1326', '1325', '吉州', '吉州区', '中国,江西省,吉安市,吉州区', '3', 'jizhou', '0796', '343000', 'J', '114.97598', '27.10669'); +INSERT INTO `yoshop_region` VALUES ('1327', '1325', '青原', '青原区', '中国,江西省,吉安市,青原区', '3', 'qingyuan', '0796', '343009', 'Q', '115.01747', '27.10577'); +INSERT INTO `yoshop_region` VALUES ('1328', '1325', '吉安', '吉安县', '中国,江西省,吉安市,吉安县', '3', 'ji\'an', '0796', '343100', 'J', '114.90695', '27.04048'); +INSERT INTO `yoshop_region` VALUES ('1329', '1325', '吉水', '吉水县', '中国,江西省,吉安市,吉水县', '3', 'jishui', '0796', '331600', 'J', '115.1343', '27.21071'); +INSERT INTO `yoshop_region` VALUES ('1330', '1325', '峡江', '峡江县', '中国,江西省,吉安市,峡江县', '3', 'xiajiang', '0796', '331409', 'X', '115.31723', '27.576'); +INSERT INTO `yoshop_region` VALUES ('1331', '1325', '新干', '新干县', '中国,江西省,吉安市,新干县', '3', 'xingan', '0796', '331300', 'X', '115.39306', '27.74092'); +INSERT INTO `yoshop_region` VALUES ('1332', '1325', '永丰', '永丰县', '中国,江西省,吉安市,永丰县', '3', 'yongfeng', '0796', '331500', 'Y', '115.44238', '27.31785'); +INSERT INTO `yoshop_region` VALUES ('1333', '1325', '泰和', '泰和县', '中国,江西省,吉安市,泰和县', '3', 'taihe', '0796', '343700', 'T', '114.90789', '26.79113'); +INSERT INTO `yoshop_region` VALUES ('1334', '1325', '遂川', '遂川县', '中国,江西省,吉安市,遂川县', '3', 'suichuan', '0796', '343900', 'S', '114.51629', '26.32598'); +INSERT INTO `yoshop_region` VALUES ('1335', '1325', '万安', '万安县', '中国,江西省,吉安市,万安县', '3', 'wan\'an', '0796', '343800', 'W', '114.78659', '26.45931'); +INSERT INTO `yoshop_region` VALUES ('1336', '1325', '安福', '安福县', '中国,江西省,吉安市,安福县', '3', 'anfu', '0796', '343200', 'A', '114.61956', '27.39276'); +INSERT INTO `yoshop_region` VALUES ('1337', '1325', '永新', '永新县', '中国,江西省,吉安市,永新县', '3', 'yongxin', '0796', '343400', 'Y', '114.24246', '26.94488'); +INSERT INTO `yoshop_region` VALUES ('1338', '1325', '井冈山', '井冈山市', '中国,江西省,吉安市,井冈山市', '3', 'jinggangshan', '0796', '343600', 'J', '114.28949', '26.74804'); +INSERT INTO `yoshop_region` VALUES ('1339', '1263', '宜春', '宜春市', '中国,江西省,宜春市', '2', 'yichun', '0795', '336000', 'Y', '114.391136', '27.8043'); +INSERT INTO `yoshop_region` VALUES ('1340', '1339', '袁州', '袁州区', '中国,江西省,宜春市,袁州区', '3', 'yuanzhou', '0795', '336000', 'Y', '114.38246', '27.79649'); +INSERT INTO `yoshop_region` VALUES ('1341', '1339', '奉新', '奉新县', '中国,江西省,宜春市,奉新县', '3', 'fengxin', '0795', '330700', 'F', '115.40036', '28.6879'); +INSERT INTO `yoshop_region` VALUES ('1342', '1339', '万载', '万载县', '中国,江西省,宜春市,万载县', '3', 'wanzai', '0795', '336100', 'W', '114.4458', '28.10656'); +INSERT INTO `yoshop_region` VALUES ('1343', '1339', '上高', '上高县', '中国,江西省,宜春市,上高县', '3', 'shanggao', '0795', '336400', 'S', '114.92459', '28.23423'); +INSERT INTO `yoshop_region` VALUES ('1344', '1339', '宜丰', '宜丰县', '中国,江西省,宜春市,宜丰县', '3', 'yifeng', '0795', '336300', 'Y', '114.7803', '28.38555'); +INSERT INTO `yoshop_region` VALUES ('1345', '1339', '靖安', '靖安县', '中国,江西省,宜春市,靖安县', '3', 'jing\'an', '0795', '330600', 'J', '115.36279', '28.86167'); +INSERT INTO `yoshop_region` VALUES ('1346', '1339', '铜鼓', '铜鼓县', '中国,江西省,宜春市,铜鼓县', '3', 'tonggu', '0795', '336200', 'T', '114.37036', '28.52311'); +INSERT INTO `yoshop_region` VALUES ('1347', '1339', '丰城', '丰城市', '中国,江西省,宜春市,丰城市', '3', 'fengcheng', '0795', '331100', 'F', '115.77114', '28.15918'); +INSERT INTO `yoshop_region` VALUES ('1348', '1339', '樟树', '樟树市', '中国,江西省,宜春市,樟树市', '3', 'zhangshu', '0795', '331200', 'Z', '115.5465', '28.05332'); +INSERT INTO `yoshop_region` VALUES ('1349', '1339', '高安', '高安市', '中国,江西省,宜春市,高安市', '3', 'gao\'an', '0795', '330800', 'G', '115.3753', '28.4178'); +INSERT INTO `yoshop_region` VALUES ('1350', '1263', '抚州', '抚州市', '中国,江西省,抚州市', '2', 'fuzhou', '0794', '344000', 'F', '116.358351', '27.98385'); +INSERT INTO `yoshop_region` VALUES ('1351', '1350', '临川', '临川区', '中国,江西省,抚州市,临川区', '3', 'linchuan', '0794', '344000', 'L', '116.35919', '27.97721'); +INSERT INTO `yoshop_region` VALUES ('1352', '1350', '南城', '南城县', '中国,江西省,抚州市,南城县', '3', 'nancheng', '0794', '344700', 'N', '116.64419', '27.55381'); +INSERT INTO `yoshop_region` VALUES ('1353', '1350', '黎川', '黎川县', '中国,江西省,抚州市,黎川县', '3', 'lichuan', '0794', '344600', 'L', '116.90771', '27.28232'); +INSERT INTO `yoshop_region` VALUES ('1354', '1350', '南丰', '南丰县', '中国,江西省,抚州市,南丰县', '3', 'nanfeng', '0794', '344500', 'N', '116.5256', '27.21842'); +INSERT INTO `yoshop_region` VALUES ('1355', '1350', '崇仁', '崇仁县', '中国,江西省,抚州市,崇仁县', '3', 'chongren', '0794', '344200', 'C', '116.06021', '27.75962'); +INSERT INTO `yoshop_region` VALUES ('1356', '1350', '乐安', '乐安县', '中国,江西省,抚州市,乐安县', '3', 'le\'an', '0794', '344300', 'L', '115.83108', '27.42812'); +INSERT INTO `yoshop_region` VALUES ('1357', '1350', '宜黄', '宜黄县', '中国,江西省,抚州市,宜黄县', '3', 'yihuang', '0794', '344400', 'Y', '116.23626', '27.55487'); +INSERT INTO `yoshop_region` VALUES ('1358', '1350', '金溪', '金溪县', '中国,江西省,抚州市,金溪县', '3', 'jinxi', '0794', '344800', 'J', '116.77392', '27.90753'); +INSERT INTO `yoshop_region` VALUES ('1359', '1350', '资溪', '资溪县', '中国,江西省,抚州市,资溪县', '3', 'zixi', '0794', '335300', 'Z', '117.06939', '27.70493'); +INSERT INTO `yoshop_region` VALUES ('1360', '1350', '东乡', '东乡县', '中国,江西省,抚州市,东乡县', '3', 'dongxiang', '0794', '331800', 'D', '116.59039', '28.23614'); +INSERT INTO `yoshop_region` VALUES ('1361', '1350', '广昌', '广昌县', '中国,江西省,抚州市,广昌县', '3', 'guangchang', '0794', '344900', 'G', '116.32547', '26.8341'); +INSERT INTO `yoshop_region` VALUES ('1362', '1263', '上饶', '上饶市', '中国,江西省,上饶市', '2', 'shangrao', '0793', '334000', 'S', '117.971185', '28.44442'); +INSERT INTO `yoshop_region` VALUES ('1363', '1362', '信州', '信州区', '中国,江西省,上饶市,信州区', '3', 'xinzhou', '0793', '334000', 'X', '117.96682', '28.43121'); +INSERT INTO `yoshop_region` VALUES ('1364', '1362', '上饶', '上饶县', '中国,江西省,上饶市,上饶县', '3', 'shangrao', '0793', '334100', 'S', '117.90884', '28.44856'); +INSERT INTO `yoshop_region` VALUES ('1365', '1362', '广丰', '广丰县', '中国,江西省,上饶市,广丰县', '3', 'guangfeng', '0793', '334600', 'G', '118.19158', '28.43766'); +INSERT INTO `yoshop_region` VALUES ('1366', '1362', '玉山', '玉山县', '中国,江西省,上饶市,玉山县', '3', 'yushan', '0793', '334700', 'Y', '118.24462', '28.6818'); +INSERT INTO `yoshop_region` VALUES ('1367', '1362', '铅山', '铅山县', '中国,江西省,上饶市,铅山县', '3', 'yanshan', '0793', '334500', 'Q', '117.70996', '28.31549'); +INSERT INTO `yoshop_region` VALUES ('1368', '1362', '横峰', '横峰县', '中国,江西省,上饶市,横峰县', '3', 'hengfeng', '0793', '334300', 'H', '117.5964', '28.40716'); +INSERT INTO `yoshop_region` VALUES ('1369', '1362', '弋阳', '弋阳县', '中国,江西省,上饶市,弋阳县', '3', 'yiyang', '0793', '334400', null, '117.45929', '28.37451'); +INSERT INTO `yoshop_region` VALUES ('1370', '1362', '余干', '余干县', '中国,江西省,上饶市,余干县', '3', 'yugan', '0793', '335100', 'Y', '116.69555', '28.70206'); +INSERT INTO `yoshop_region` VALUES ('1371', '1362', '鄱阳', '鄱阳县', '中国,江西省,上饶市,鄱阳县', '3', 'poyang', '0793', '333100', null, '116.69967', '29.0118'); +INSERT INTO `yoshop_region` VALUES ('1372', '1362', '万年', '万年县', '中国,江西省,上饶市,万年县', '3', 'wannian', '0793', '335500', 'W', '117.06884', '28.69537'); +INSERT INTO `yoshop_region` VALUES ('1373', '1362', '婺源', '婺源县', '中国,江西省,上饶市,婺源县', '3', 'wuyuan', '0793', '333200', null, '117.86105', '29.24841'); +INSERT INTO `yoshop_region` VALUES ('1374', '1362', '德兴', '德兴市', '中国,江西省,上饶市,德兴市', '3', 'dexing', '0793', '334200', 'D', '117.57919', '28.94736'); +INSERT INTO `yoshop_region` VALUES ('1375', '0', '山东', '山东省', '中国,山东省', '1', 'shandong', '', '', 'S', '117.000923', '36.675807'); +INSERT INTO `yoshop_region` VALUES ('1376', '1375', '济南', '济南市', '中国,山东省,济南市', '2', 'jinan', '0531', '250001', 'J', '117.000923', '36.675807'); +INSERT INTO `yoshop_region` VALUES ('1377', '1376', '历下', '历下区', '中国,山东省,济南市,历下区', '3', 'lixia', '0531', '250014', 'L', '117.0768', '36.66661'); +INSERT INTO `yoshop_region` VALUES ('1378', '1376', '市中区', '市中区', '中国,山东省,济南市,市中区', '3', 'shizhongqu', '0531', '250001', 'S', '116.99741', '36.65101'); +INSERT INTO `yoshop_region` VALUES ('1379', '1376', '槐荫', '槐荫区', '中国,山东省,济南市,槐荫区', '3', 'huaiyin', '0531', '250117', 'H', '116.90075', '36.65136'); +INSERT INTO `yoshop_region` VALUES ('1380', '1376', '天桥', '天桥区', '中国,山东省,济南市,天桥区', '3', 'tianqiao', '0531', '250031', 'T', '116.98749', '36.67801'); +INSERT INTO `yoshop_region` VALUES ('1381', '1376', '历城', '历城区', '中国,山东省,济南市,历城区', '3', 'licheng', '0531', '250100', 'L', '117.06509', '36.67995'); +INSERT INTO `yoshop_region` VALUES ('1382', '1376', '长清', '长清区', '中国,山东省,济南市,长清区', '3', 'changqing', '0531', '250300', 'C', '116.75192', '36.55352'); +INSERT INTO `yoshop_region` VALUES ('1383', '1376', '平阴', '平阴县', '中国,山东省,济南市,平阴县', '3', 'pingyin', '0531', '250400', 'P', '116.45587', '36.28955'); +INSERT INTO `yoshop_region` VALUES ('1384', '1376', '济阳', '济阳县', '中国,山东省,济南市,济阳县', '3', 'jiyang', '0531', '251400', 'J', '117.17327', '36.97845'); +INSERT INTO `yoshop_region` VALUES ('1385', '1376', '商河', '商河县', '中国,山东省,济南市,商河县', '3', 'shanghe', '0531', '251600', 'S', '117.15722', '37.31119'); +INSERT INTO `yoshop_region` VALUES ('1386', '1376', '章丘', '章丘市', '中国,山东省,济南市,章丘市', '3', 'zhangqiu', '0531', '250200', 'Z', '117.53677', '36.71392'); +INSERT INTO `yoshop_region` VALUES ('1387', '1375', '青岛', '青岛市', '中国,山东省,青岛市', '2', 'qingdao', '0532', '266001', 'Q', '120.369557', '36.094406'); +INSERT INTO `yoshop_region` VALUES ('1388', '1387', '市南', '市南区', '中国,山东省,青岛市,市南区', '3', 'shinan', '0532', '266001', 'S', '120.38773', '36.06671'); +INSERT INTO `yoshop_region` VALUES ('1389', '1387', '市北', '市北区', '中国,山东省,青岛市,市北区', '3', 'shibei', '0532', '266011', 'S', '120.37469', '36.08734'); +INSERT INTO `yoshop_region` VALUES ('1390', '1387', '黄岛', '黄岛区', '中国,山东省,青岛市,黄岛区', '3', 'huangdao', '0532', '266500', 'H', '120.19775', '35.96065'); +INSERT INTO `yoshop_region` VALUES ('1391', '1387', '崂山', '崂山区', '中国,山东省,青岛市,崂山区', '3', 'laoshan', '0532', '266100', null, '120.46923', '36.10717'); +INSERT INTO `yoshop_region` VALUES ('1392', '1387', '李沧', '李沧区', '中国,山东省,青岛市,李沧区', '3', 'licang', '0532', '266021', 'L', '120.43286', '36.14502'); +INSERT INTO `yoshop_region` VALUES ('1393', '1387', '城阳', '城阳区', '中国,山东省,青岛市,城阳区', '3', 'chengyang', '0532', '266041', 'C', '120.39621', '36.30735'); +INSERT INTO `yoshop_region` VALUES ('1394', '1387', '胶州', '胶州市', '中国,山东省,青岛市,胶州市', '3', 'jiaozhou', '0532', '266300', 'J', '120.0335', '36.26442'); +INSERT INTO `yoshop_region` VALUES ('1395', '1387', '即墨', '即墨市', '中国,山东省,青岛市,即墨市', '3', 'jimo', '0532', '266200', 'J', '120.44699', '36.38907'); +INSERT INTO `yoshop_region` VALUES ('1396', '1387', '平度', '平度市', '中国,山东省,青岛市,平度市', '3', 'pingdu', '0532', '266700', 'P', '119.95996', '36.78688'); +INSERT INTO `yoshop_region` VALUES ('1397', '1387', '莱西', '莱西市', '中国,山东省,青岛市,莱西市', '3', 'laixi', '0532', '266600', 'L', '120.51773', '36.88804'); +INSERT INTO `yoshop_region` VALUES ('1398', '1387', '西海岸', '西海岸新区', '中国,山东省,青岛市,西海岸新区', '3', 'xihai\'an', '0532', '266500', 'X', '120.19775', '35.96065'); +INSERT INTO `yoshop_region` VALUES ('1399', '1375', '淄博', '淄博市', '中国,山东省,淄博市', '2', 'zibo', '0533', '255039', 'Z', '118.047648', '36.814939'); +INSERT INTO `yoshop_region` VALUES ('1400', '1399', '淄川', '淄川区', '中国,山东省,淄博市,淄川区', '3', 'zichuan', '0533', '255100', 'Z', '117.96655', '36.64339'); +INSERT INTO `yoshop_region` VALUES ('1401', '1399', '张店', '张店区', '中国,山东省,淄博市,张店区', '3', 'zhangdian', '0533', '255022', 'Z', '118.01788', '36.80676'); +INSERT INTO `yoshop_region` VALUES ('1402', '1399', '博山', '博山区', '中国,山东省,淄博市,博山区', '3', 'boshan', '0533', '255200', 'B', '117.86166', '36.49469'); +INSERT INTO `yoshop_region` VALUES ('1403', '1399', '临淄', '临淄区', '中国,山东省,淄博市,临淄区', '3', 'linzi', '0533', '255400', 'L', '118.30966', '36.8259'); +INSERT INTO `yoshop_region` VALUES ('1404', '1399', '周村', '周村区', '中国,山东省,淄博市,周村区', '3', 'zhoucun', '0533', '255300', 'Z', '117.86969', '36.80322'); +INSERT INTO `yoshop_region` VALUES ('1405', '1399', '桓台', '桓台县', '中国,山东省,淄博市,桓台县', '3', 'huantai', '0533', '256400', 'H', '118.09698', '36.96036'); +INSERT INTO `yoshop_region` VALUES ('1406', '1399', '高青', '高青县', '中国,山东省,淄博市,高青县', '3', 'gaoqing', '0533', '256300', 'G', '117.82708', '37.17197'); +INSERT INTO `yoshop_region` VALUES ('1407', '1399', '沂源', '沂源县', '中国,山东省,淄博市,沂源县', '3', 'yiyuan', '0533', '256100', 'Y', '118.17105', '36.18536'); +INSERT INTO `yoshop_region` VALUES ('1408', '1375', '枣庄', '枣庄市', '中国,山东省,枣庄市', '2', 'zaozhuang', '0632', '277101', 'Z', '117.557964', '34.856424'); +INSERT INTO `yoshop_region` VALUES ('1409', '1408', '市中区', '市中区', '中国,山东省,枣庄市,市中区', '3', 'shizhongqu', '0632', '277101', 'S', '117.55603', '34.86391'); +INSERT INTO `yoshop_region` VALUES ('1410', '1408', '薛城', '薛城区', '中国,山东省,枣庄市,薛城区', '3', 'xuecheng', '0632', '277000', 'X', '117.26318', '34.79498'); +INSERT INTO `yoshop_region` VALUES ('1411', '1408', '峄城', '峄城区', '中国,山东省,枣庄市,峄城区', '3', 'yicheng', '0632', '277300', null, '117.59057', '34.77225'); +INSERT INTO `yoshop_region` VALUES ('1412', '1408', '台儿庄', '台儿庄区', '中国,山东省,枣庄市,台儿庄区', '3', 'taierzhuang', '0632', '277400', 'T', '117.73452', '34.56363'); +INSERT INTO `yoshop_region` VALUES ('1413', '1408', '山亭', '山亭区', '中国,山东省,枣庄市,山亭区', '3', 'shanting', '0632', '277200', 'S', '117.4663', '35.09541'); +INSERT INTO `yoshop_region` VALUES ('1414', '1408', '滕州', '滕州市', '中国,山东省,枣庄市,滕州市', '3', 'tengzhou', '0632', '277500', null, '117.165', '35.10534'); +INSERT INTO `yoshop_region` VALUES ('1415', '1375', '东营', '东营市', '中国,山东省,东营市', '2', 'dongying', '0546', '257093', 'D', '118.4963', '37.461266'); +INSERT INTO `yoshop_region` VALUES ('1416', '1415', '东营', '东营区', '中国,山东省,东营市,东营区', '3', 'dongying', '0546', '257029', 'D', '118.5816', '37.44875'); +INSERT INTO `yoshop_region` VALUES ('1417', '1415', '河口', '河口区', '中国,山东省,东营市,河口区', '3', 'hekou', '0546', '257200', 'H', '118.5249', '37.88541'); +INSERT INTO `yoshop_region` VALUES ('1418', '1415', '垦利', '垦利县', '中国,山东省,东营市,垦利县', '3', 'kenli', '0546', '257500', 'K', '118.54815', '37.58825'); +INSERT INTO `yoshop_region` VALUES ('1419', '1415', '利津', '利津县', '中国,山东省,东营市,利津县', '3', 'lijin', '0546', '257400', 'L', '118.25637', '37.49157'); +INSERT INTO `yoshop_region` VALUES ('1420', '1415', '广饶', '广饶县', '中国,山东省,东营市,广饶县', '3', 'guangrao', '0546', '257300', 'G', '118.40704', '37.05381'); +INSERT INTO `yoshop_region` VALUES ('1421', '1375', '烟台', '烟台市', '中国,山东省,烟台市', '2', 'yantai', '0635', '264010', 'Y', '121.391382', '37.539297'); +INSERT INTO `yoshop_region` VALUES ('1422', '1421', '芝罘', '芝罘区', '中国,山东省,烟台市,芝罘区', '3', 'zhifu', '0635', '264001', 'Z', '121.40023', '37.54064'); +INSERT INTO `yoshop_region` VALUES ('1423', '1421', '福山', '福山区', '中国,山东省,烟台市,福山区', '3', 'fushan', '0635', '265500', 'F', '121.26812', '37.49841'); +INSERT INTO `yoshop_region` VALUES ('1424', '1421', '牟平', '牟平区', '中国,山东省,烟台市,牟平区', '3', 'muping', '0635', '264100', 'M', '121.60067', '37.38846'); +INSERT INTO `yoshop_region` VALUES ('1425', '1421', '莱山', '莱山区', '中国,山东省,烟台市,莱山区', '3', 'laishan', '0635', '264600', 'L', '121.44512', '37.51165'); +INSERT INTO `yoshop_region` VALUES ('1426', '1421', '长岛', '长岛县', '中国,山东省,烟台市,长岛县', '3', 'changdao', '0635', '265800', 'C', '120.738', '37.91754'); +INSERT INTO `yoshop_region` VALUES ('1427', '1421', '龙口', '龙口市', '中国,山东省,烟台市,龙口市', '3', 'longkou', '0635', '265700', 'L', '120.50634', '37.64064'); +INSERT INTO `yoshop_region` VALUES ('1428', '1421', '莱阳', '莱阳市', '中国,山东省,烟台市,莱阳市', '3', 'laiyang', '0635', '265200', 'L', '120.71066', '36.98012'); +INSERT INTO `yoshop_region` VALUES ('1429', '1421', '莱州', '莱州市', '中国,山东省,烟台市,莱州市', '3', 'laizhou', '0635', '261400', 'L', '119.94137', '37.17806'); +INSERT INTO `yoshop_region` VALUES ('1430', '1421', '蓬莱', '蓬莱市', '中国,山东省,烟台市,蓬莱市', '3', 'penglai', '0635', '265600', 'P', '120.75988', '37.81119'); +INSERT INTO `yoshop_region` VALUES ('1431', '1421', '招远', '招远市', '中国,山东省,烟台市,招远市', '3', 'zhaoyuan', '0635', '265400', 'Z', '120.40481', '37.36269'); +INSERT INTO `yoshop_region` VALUES ('1432', '1421', '栖霞', '栖霞市', '中国,山东省,烟台市,栖霞市', '3', 'qixia', '0635', '265300', 'Q', '120.85025', '37.33571'); +INSERT INTO `yoshop_region` VALUES ('1433', '1421', '海阳', '海阳市', '中国,山东省,烟台市,海阳市', '3', 'haiyang', '0635', '265100', 'H', '121.15976', '36.77622'); +INSERT INTO `yoshop_region` VALUES ('1434', '1375', '潍坊', '潍坊市', '中国,山东省,潍坊市', '2', 'weifang', '0536', '261041', 'W', '119.107078', '36.70925'); +INSERT INTO `yoshop_region` VALUES ('1435', '1434', '潍城', '潍城区', '中国,山东省,潍坊市,潍城区', '3', 'weicheng', '0536', '261021', 'W', '119.10582', '36.7139'); +INSERT INTO `yoshop_region` VALUES ('1436', '1434', '寒亭', '寒亭区', '中国,山东省,潍坊市,寒亭区', '3', 'hanting', '0536', '261100', 'H', '119.21832', '36.77504'); +INSERT INTO `yoshop_region` VALUES ('1437', '1434', '坊子', '坊子区', '中国,山东省,潍坊市,坊子区', '3', 'fangzi', '0536', '261200', 'F', '119.16476', '36.65218'); +INSERT INTO `yoshop_region` VALUES ('1438', '1434', '奎文', '奎文区', '中国,山东省,潍坊市,奎文区', '3', 'kuiwen', '0536', '261031', 'K', '119.12532', '36.70723'); +INSERT INTO `yoshop_region` VALUES ('1439', '1434', '临朐', '临朐县', '中国,山东省,潍坊市,临朐县', '3', 'linqu', '0536', '262600', 'L', '118.544', '36.51216'); +INSERT INTO `yoshop_region` VALUES ('1440', '1434', '昌乐', '昌乐县', '中国,山东省,潍坊市,昌乐县', '3', 'changle', '0536', '262400', 'C', '118.83017', '36.7078'); +INSERT INTO `yoshop_region` VALUES ('1441', '1434', '青州', '青州市', '中国,山东省,潍坊市,青州市', '3', 'qingzhou', '0536', '262500', 'Q', '118.47915', '36.68505'); +INSERT INTO `yoshop_region` VALUES ('1442', '1434', '诸城', '诸城市', '中国,山东省,潍坊市,诸城市', '3', 'zhucheng', '0536', '262200', 'Z', '119.40988', '35.99662'); +INSERT INTO `yoshop_region` VALUES ('1443', '1434', '寿光', '寿光市', '中国,山东省,潍坊市,寿光市', '3', 'shouguang', '0536', '262700', 'S', '118.74047', '36.88128'); +INSERT INTO `yoshop_region` VALUES ('1444', '1434', '安丘', '安丘市', '中国,山东省,潍坊市,安丘市', '3', 'anqiu', '0536', '262100', 'A', '119.2189', '36.47847'); +INSERT INTO `yoshop_region` VALUES ('1445', '1434', '高密', '高密市', '中国,山东省,潍坊市,高密市', '3', 'gaomi', '0536', '261500', 'G', '119.75701', '36.38397'); +INSERT INTO `yoshop_region` VALUES ('1446', '1434', '昌邑', '昌邑市', '中国,山东省,潍坊市,昌邑市', '3', 'changyi', '0536', '261300', 'C', '119.39767', '36.86008'); +INSERT INTO `yoshop_region` VALUES ('1447', '1375', '济宁', '济宁市', '中国,山东省,济宁市', '2', 'jining', '0537', '272119', 'J', '116.587245', '35.415393'); +INSERT INTO `yoshop_region` VALUES ('1448', '1447', '任城', '任城区', '中国,山东省,济宁市,任城区', '3', 'rencheng', '0537', '272113', 'R', '116.59504', '35.40659'); +INSERT INTO `yoshop_region` VALUES ('1449', '1447', '兖州', '兖州区', '中国,山东省,济宁市,兖州区', '3', 'yanzhou', '0537', '272000', null, '116.826546', '35.552305'); +INSERT INTO `yoshop_region` VALUES ('1450', '1447', '微山', '微山县', '中国,山东省,济宁市,微山县', '3', 'weishan', '0537', '277600', 'W', '117.12875', '34.80712'); +INSERT INTO `yoshop_region` VALUES ('1451', '1447', '鱼台', '鱼台县', '中国,山东省,济宁市,鱼台县', '3', 'yutai', '0537', '272300', 'Y', '116.64761', '34.99674'); +INSERT INTO `yoshop_region` VALUES ('1452', '1447', '金乡', '金乡县', '中国,山东省,济宁市,金乡县', '3', 'jinxiang', '0537', '272200', 'J', '116.31146', '35.065'); +INSERT INTO `yoshop_region` VALUES ('1453', '1447', '嘉祥', '嘉祥县', '中国,山东省,济宁市,嘉祥县', '3', 'jiaxiang', '0537', '272400', 'J', '116.34249', '35.40836'); +INSERT INTO `yoshop_region` VALUES ('1454', '1447', '汶上', '汶上县', '中国,山东省,济宁市,汶上县', '3', 'wenshang', '0537', '272501', null, '116.48742', '35.73295'); +INSERT INTO `yoshop_region` VALUES ('1455', '1447', '泗水', '泗水县', '中国,山东省,济宁市,泗水县', '3', 'sishui', '0537', '273200', null, '117.27948', '35.66113'); +INSERT INTO `yoshop_region` VALUES ('1456', '1447', '梁山', '梁山县', '中国,山东省,济宁市,梁山县', '3', 'liangshan', '0537', '272600', 'L', '116.09683', '35.80322'); +INSERT INTO `yoshop_region` VALUES ('1457', '1447', '曲阜', '曲阜市', '中国,山东省,济宁市,曲阜市', '3', 'qufu', '0537', '273100', 'Q', '116.98645', '35.58091'); +INSERT INTO `yoshop_region` VALUES ('1458', '1447', '邹城', '邹城市', '中国,山东省,济宁市,邹城市', '3', 'zoucheng', '0537', '273500', 'Z', '116.97335', '35.40531'); +INSERT INTO `yoshop_region` VALUES ('1459', '1375', '泰安', '泰安市', '中国,山东省,泰安市', '2', 'tai\'an', '0538', '271000', 'T', '117.129063', '36.194968'); +INSERT INTO `yoshop_region` VALUES ('1460', '1459', '泰山', '泰山区', '中国,山东省,泰安市,泰山区', '3', 'taishan', '0538', '271000', 'T', '117.13446', '36.19411'); +INSERT INTO `yoshop_region` VALUES ('1461', '1459', '岱岳', '岱岳区', '中国,山东省,泰安市,岱岳区', '3', 'daiyue', '0538', '271000', null, '117.04174', '36.1875'); +INSERT INTO `yoshop_region` VALUES ('1462', '1459', '宁阳', '宁阳县', '中国,山东省,泰安市,宁阳县', '3', 'ningyang', '0538', '271400', 'N', '116.80542', '35.7599'); +INSERT INTO `yoshop_region` VALUES ('1463', '1459', '东平', '东平县', '中国,山东省,泰安市,东平县', '3', 'dongping', '0538', '271500', 'D', '116.47113', '35.93792'); +INSERT INTO `yoshop_region` VALUES ('1464', '1459', '新泰', '新泰市', '中国,山东省,泰安市,新泰市', '3', 'xintai', '0538', '271200', 'X', '117.76959', '35.90887'); +INSERT INTO `yoshop_region` VALUES ('1465', '1459', '肥城', '肥城市', '中国,山东省,泰安市,肥城市', '3', 'feicheng', '0538', '271600', 'F', '116.76815', '36.18247'); +INSERT INTO `yoshop_region` VALUES ('1466', '1375', '威海', '威海市', '中国,山东省,威海市', '2', 'weihai', '0631', '264200', 'W', '122.116394', '37.509691'); +INSERT INTO `yoshop_region` VALUES ('1467', '1466', '环翠', '环翠区', '中国,山东省,威海市,环翠区', '3', 'huancui', '0631', '264200', 'H', '122.12344', '37.50199'); +INSERT INTO `yoshop_region` VALUES ('1468', '1466', '文登', '文登区', '中国,山东省,威海市,文登区', '3', 'wendeng', '0631', '266440', 'W', '122.057139', '37.196211'); +INSERT INTO `yoshop_region` VALUES ('1469', '1466', '荣成', '荣成市', '中国,山东省,威海市,荣成市', '3', 'rongcheng', '0631', '264300', 'R', '122.48773', '37.1652'); +INSERT INTO `yoshop_region` VALUES ('1470', '1466', '乳山', '乳山市', '中国,山东省,威海市,乳山市', '3', 'rushan', '0631', '264500', 'R', '121.53814', '36.91918'); +INSERT INTO `yoshop_region` VALUES ('1471', '1375', '日照', '日照市', '中国,山东省,日照市', '2', 'rizhao', '0633', '276800', 'R', '119.461208', '35.428588'); +INSERT INTO `yoshop_region` VALUES ('1472', '1471', '东港', '东港区', '中国,山东省,日照市,东港区', '3', 'donggang', '0633', '276800', 'D', '119.46237', '35.42541'); +INSERT INTO `yoshop_region` VALUES ('1473', '1471', '岚山', '岚山区', '中国,山东省,日照市,岚山区', '3', 'lanshan', '0633', '276808', null, '119.31884', '35.12203'); +INSERT INTO `yoshop_region` VALUES ('1474', '1471', '五莲', '五莲县', '中国,山东省,日照市,五莲县', '3', 'wulian', '0633', '262300', 'W', '119.207', '35.75004'); +INSERT INTO `yoshop_region` VALUES ('1475', '1471', '莒县', '莒县', '中国,山东省,日照市,莒县', '3', 'juxian', '0633', '276500', null, '118.83789', '35.58054'); +INSERT INTO `yoshop_region` VALUES ('1476', '1375', '莱芜', '莱芜市', '中国,山东省,莱芜市', '2', 'laiwu', '0634', '271100', 'L', '117.677736', '36.214397'); +INSERT INTO `yoshop_region` VALUES ('1477', '1476', '莱城', '莱城区', '中国,山东省,莱芜市,莱城区', '3', 'laicheng', '0634', '271199', 'L', '117.65986', '36.2032'); +INSERT INTO `yoshop_region` VALUES ('1478', '1476', '钢城', '钢城区', '中国,山东省,莱芜市,钢城区', '3', 'gangcheng', '0634', '271100', 'G', '117.8049', '36.06319'); +INSERT INTO `yoshop_region` VALUES ('1479', '1375', '临沂', '临沂市', '中国,山东省,临沂市', '2', 'linyi', '0539', '253000', 'L', '118.326443', '35.065282'); +INSERT INTO `yoshop_region` VALUES ('1480', '1479', '兰山', '兰山区', '中国,山东省,临沂市,兰山区', '3', 'lanshan', '0539', '276002', 'L', '118.34817', '35.06872'); +INSERT INTO `yoshop_region` VALUES ('1481', '1479', '罗庄', '罗庄区', '中国,山东省,临沂市,罗庄区', '3', 'luozhuang', '0539', '276022', 'L', '118.28466', '34.99627'); +INSERT INTO `yoshop_region` VALUES ('1482', '1479', '河东', '河东区', '中国,山东省,临沂市,河东区', '3', 'hedong', '0539', '276034', 'H', '118.41055', '35.08803'); +INSERT INTO `yoshop_region` VALUES ('1483', '1479', '沂南', '沂南县', '中国,山东省,临沂市,沂南县', '3', 'yinan', '0539', '276300', 'Y', '118.47061', '35.55131'); +INSERT INTO `yoshop_region` VALUES ('1484', '1479', '郯城', '郯城县', '中国,山东省,临沂市,郯城县', '3', 'tancheng', '0539', '276100', null, '118.36712', '34.61354'); +INSERT INTO `yoshop_region` VALUES ('1485', '1479', '沂水', '沂水县', '中国,山东省,临沂市,沂水县', '3', 'yishui', '0539', '276400', 'Y', '118.63009', '35.78731'); +INSERT INTO `yoshop_region` VALUES ('1486', '1479', '兰陵', '兰陵县', '中国,山东省,临沂市,兰陵县', '3', 'lanling', '0539', '277700', 'L', '117.856592', '34.738315'); +INSERT INTO `yoshop_region` VALUES ('1487', '1479', '费县', '费县', '中国,山东省,临沂市,费县', '3', 'feixian', '0539', '273400', 'F', '117.97836', '35.26562'); +INSERT INTO `yoshop_region` VALUES ('1488', '1479', '平邑', '平邑县', '中国,山东省,临沂市,平邑县', '3', 'pingyi', '0539', '273300', 'P', '117.63867', '35.50573'); +INSERT INTO `yoshop_region` VALUES ('1489', '1479', '莒南', '莒南县', '中国,山东省,临沂市,莒南县', '3', 'junan', '0539', '276600', null, '118.83227', '35.17539'); +INSERT INTO `yoshop_region` VALUES ('1490', '1479', '蒙阴', '蒙阴县', '中国,山东省,临沂市,蒙阴县', '3', 'mengyin', '0539', '276200', 'M', '117.94592', '35.70996'); +INSERT INTO `yoshop_region` VALUES ('1491', '1479', '临沭', '临沭县', '中国,山东省,临沂市,临沭县', '3', 'linshu', '0539', '276700', 'L', '118.65267', '34.92091'); +INSERT INTO `yoshop_region` VALUES ('1492', '1375', '德州', '德州市', '中国,山东省,德州市', '2', 'dezhou', '0534', '253000', 'D', '116.307428', '37.453968'); +INSERT INTO `yoshop_region` VALUES ('1493', '1492', '德城', '德城区', '中国,山东省,德州市,德城区', '3', 'decheng', '0534', '253012', 'D', '116.29943', '37.45126'); +INSERT INTO `yoshop_region` VALUES ('1494', '1492', '陵城', '陵城区', '中国,山东省,德州市,陵城区', '3', 'lingcheng', '0534', '253500', 'L', '116.57601', '37.33571'); +INSERT INTO `yoshop_region` VALUES ('1495', '1492', '宁津', '宁津县', '中国,山东省,德州市,宁津县', '3', 'ningjin', '0534', '253400', 'N', '116.79702', '37.65301'); +INSERT INTO `yoshop_region` VALUES ('1496', '1492', '庆云', '庆云县', '中国,山东省,德州市,庆云县', '3', 'qingyun', '0534', '253700', 'Q', '117.38635', '37.77616'); +INSERT INTO `yoshop_region` VALUES ('1497', '1492', '临邑', '临邑县', '中国,山东省,德州市,临邑县', '3', 'linyi', '0534', '251500', 'L', '116.86547', '37.19053'); +INSERT INTO `yoshop_region` VALUES ('1498', '1492', '齐河', '齐河县', '中国,山东省,德州市,齐河县', '3', 'qihe', '0534', '251100', 'Q', '116.75515', '36.79532'); +INSERT INTO `yoshop_region` VALUES ('1499', '1492', '平原', '平原县', '中国,山东省,德州市,平原县', '3', 'pingyuan', '0534', '253100', 'P', '116.43432', '37.16632'); +INSERT INTO `yoshop_region` VALUES ('1500', '1492', '夏津', '夏津县', '中国,山东省,德州市,夏津县', '3', 'xiajin', '0534', '253200', 'X', '116.0017', '36.94852'); +INSERT INTO `yoshop_region` VALUES ('1501', '1492', '武城', '武城县', '中国,山东省,德州市,武城县', '3', 'wucheng', '0534', '253300', 'W', '116.07009', '37.21403'); +INSERT INTO `yoshop_region` VALUES ('1502', '1492', '乐陵', '乐陵市', '中国,山东省,德州市,乐陵市', '3', 'leling', '0534', '253600', 'L', '117.23141', '37.73164'); +INSERT INTO `yoshop_region` VALUES ('1503', '1492', '禹城', '禹城市', '中国,山东省,德州市,禹城市', '3', 'yucheng', '0534', '251200', 'Y', '116.64309', '36.93444'); +INSERT INTO `yoshop_region` VALUES ('1504', '1375', '聊城', '聊城市', '中国,山东省,聊城市', '2', 'liaocheng', '0635', '252052', 'L', '115.980367', '36.456013'); +INSERT INTO `yoshop_region` VALUES ('1505', '1504', '东昌府', '东昌府区', '中国,山东省,聊城市,东昌府区', '3', 'dongchangfu', '0635', '252000', 'D', '115.97383', '36.44458'); +INSERT INTO `yoshop_region` VALUES ('1506', '1504', '阳谷', '阳谷县', '中国,山东省,聊城市,阳谷县', '3', 'yanggu', '0635', '252300', 'Y', '115.79126', '36.11444'); +INSERT INTO `yoshop_region` VALUES ('1507', '1504', '莘县', '莘县', '中国,山东省,聊城市,莘县', '3', 'shenxian', '0635', '252400', null, '115.6697', '36.23423'); +INSERT INTO `yoshop_region` VALUES ('1508', '1504', '茌平', '茌平县', '中国,山东省,聊城市,茌平县', '3', 'chiping', '0635', '252100', null, '116.25491', '36.57969'); +INSERT INTO `yoshop_region` VALUES ('1509', '1504', '东阿', '东阿县', '中国,山东省,聊城市,东阿县', '3', 'dong\'e', '0635', '252200', 'D', '116.25012', '36.33209'); +INSERT INTO `yoshop_region` VALUES ('1510', '1504', '冠县', '冠县', '中国,山东省,聊城市,冠县', '3', 'guanxian', '0635', '252500', 'G', '115.44195', '36.48429'); +INSERT INTO `yoshop_region` VALUES ('1511', '1504', '高唐', '高唐县', '中国,山东省,聊城市,高唐县', '3', 'gaotang', '0635', '252800', 'G', '116.23172', '36.86535'); +INSERT INTO `yoshop_region` VALUES ('1512', '1504', '临清', '临清市', '中国,山东省,聊城市,临清市', '3', 'linqing', '0635', '252600', 'L', '115.70629', '36.83945'); +INSERT INTO `yoshop_region` VALUES ('1513', '1375', '滨州', '滨州市', '中国,山东省,滨州市', '2', 'binzhou', '0543', '256619', 'B', '118.016974', '37.383542'); +INSERT INTO `yoshop_region` VALUES ('1514', '1513', '滨城', '滨城区', '中国,山东省,滨州市,滨城区', '3', 'bincheng', '0543', '256613', 'B', '118.02026', '37.38524'); +INSERT INTO `yoshop_region` VALUES ('1515', '1513', '沾化', '沾化区', '中国,山东省,滨州市,沾化区', '3', 'zhanhua', '0543', '256800', 'Z', '118.13214', '37.69832'); +INSERT INTO `yoshop_region` VALUES ('1516', '1513', '惠民', '惠民县', '中国,山东省,滨州市,惠民县', '3', 'huimin', '0543', '251700', 'H', '117.51113', '37.49013'); +INSERT INTO `yoshop_region` VALUES ('1517', '1513', '阳信', '阳信县', '中国,山东省,滨州市,阳信县', '3', 'yangxin', '0543', '251800', 'Y', '117.58139', '37.64198'); +INSERT INTO `yoshop_region` VALUES ('1518', '1513', '无棣', '无棣县', '中国,山东省,滨州市,无棣县', '3', 'wudi', '0543', '251900', 'W', '117.61395', '37.74009'); +INSERT INTO `yoshop_region` VALUES ('1519', '1513', '博兴', '博兴县', '中国,山东省,滨州市,博兴县', '3', 'boxing', '0543', '256500', 'B', '118.1336', '37.14316'); +INSERT INTO `yoshop_region` VALUES ('1520', '1513', '邹平', '邹平县', '中国,山东省,滨州市,邹平县', '3', 'zouping', '0543', '256200', 'Z', '117.74307', '36.86295'); +INSERT INTO `yoshop_region` VALUES ('1521', '1513', '北海新区', '北海新区', '中国,山东省,滨州市,北海新区', '3', 'beihaixinqu', '0543', '256200', 'B', '118.016974', '37.383542'); +INSERT INTO `yoshop_region` VALUES ('1522', '1375', '菏泽', '菏泽市', '中国,山东省,菏泽市', '2', 'heze', '0530', '274020', 'H', '115.469381', '35.246531'); +INSERT INTO `yoshop_region` VALUES ('1523', '1522', '牡丹', '牡丹区', '中国,山东省,菏泽市,牡丹区', '3', 'mudan', '0530', '274009', 'M', '115.41662', '35.25091'); +INSERT INTO `yoshop_region` VALUES ('1524', '1522', '曹县', '曹县', '中国,山东省,菏泽市,曹县', '3', 'caoxian', '0530', '274400', 'C', '115.54226', '34.82659'); +INSERT INTO `yoshop_region` VALUES ('1525', '1522', '单县', '单县', '中国,山东省,菏泽市,单县', '3', 'shanxian', '0530', '273700', 'D', '116.08703', '34.79514'); +INSERT INTO `yoshop_region` VALUES ('1526', '1522', '成武', '成武县', '中国,山东省,菏泽市,成武县', '3', 'chengwu', '0530', '274200', 'C', '115.8897', '34.95332'); +INSERT INTO `yoshop_region` VALUES ('1527', '1522', '巨野', '巨野县', '中国,山东省,菏泽市,巨野县', '3', 'juye', '0530', '274900', 'J', '116.09497', '35.39788'); +INSERT INTO `yoshop_region` VALUES ('1528', '1522', '郓城', '郓城县', '中国,山东省,菏泽市,郓城县', '3', 'yuncheng', '0530', '274700', null, '115.94439', '35.60044'); +INSERT INTO `yoshop_region` VALUES ('1529', '1522', '鄄城', '鄄城县', '中国,山东省,菏泽市,鄄城县', '3', 'juancheng', '0530', '274600', null, '115.50997', '35.56412'); +INSERT INTO `yoshop_region` VALUES ('1530', '1522', '定陶', '定陶县', '中国,山东省,菏泽市,定陶县', '3', 'dingtao', '0530', '274100', 'D', '115.57287', '35.07118'); +INSERT INTO `yoshop_region` VALUES ('1531', '1522', '东明', '东明县', '中国,山东省,菏泽市,东明县', '3', 'dongming', '0530', '274500', 'D', '115.09079', '35.28906'); +INSERT INTO `yoshop_region` VALUES ('1532', '0', '河南', '河南省', '中国,河南省', '1', 'henan', '', '', 'H', '113.665412', '34.757975'); +INSERT INTO `yoshop_region` VALUES ('1533', '1532', '郑州', '郑州市', '中国,河南省,郑州市', '2', 'zhengzhou', '0371', '450000', 'Z', '113.665412', '34.757975'); +INSERT INTO `yoshop_region` VALUES ('1534', '1533', '中原', '中原区', '中国,河南省,郑州市,中原区', '3', 'zhongyuan', '0371', '450007', 'Z', '113.61333', '34.74827'); +INSERT INTO `yoshop_region` VALUES ('1535', '1533', '二七', '二七区', '中国,河南省,郑州市,二七区', '3', 'erqi', '0371', '450052', 'E', '113.63931', '34.72336'); +INSERT INTO `yoshop_region` VALUES ('1536', '1533', '管城', '管城回族区', '中国,河南省,郑州市,管城回族区', '3', 'guancheng', '0371', '450000', 'G', '113.67734', '34.75383'); +INSERT INTO `yoshop_region` VALUES ('1537', '1533', '金水', '金水区', '中国,河南省,郑州市,金水区', '3', 'jinshui', '0371', '450003', 'J', '113.66057', '34.80028'); +INSERT INTO `yoshop_region` VALUES ('1538', '1533', '上街', '上街区', '中国,河南省,郑州市,上街区', '3', 'shangjie', '0371', '450041', 'S', '113.30897', '34.80276'); +INSERT INTO `yoshop_region` VALUES ('1539', '1533', '惠济', '惠济区', '中国,河南省,郑州市,惠济区', '3', 'huiji', '0371', '450053', 'H', '113.61688', '34.86735'); +INSERT INTO `yoshop_region` VALUES ('1540', '1533', '中牟', '中牟县', '中国,河南省,郑州市,中牟县', '3', 'zhongmu', '0371', '451450', 'Z', '113.97619', '34.71899'); +INSERT INTO `yoshop_region` VALUES ('1541', '1533', '巩义', '巩义市', '中国,河南省,郑州市,巩义市', '3', 'gongyi', '0371', '451200', 'G', '113.022', '34.74794'); +INSERT INTO `yoshop_region` VALUES ('1542', '1533', '荥阳', '荥阳市', '中国,河南省,郑州市,荥阳市', '3', 'xingyang', '0371', '450100', null, '113.38345', '34.78759'); +INSERT INTO `yoshop_region` VALUES ('1543', '1533', '新密', '新密市', '中国,河南省,郑州市,新密市', '3', 'xinmi', '0371', '452300', 'X', '113.3869', '34.53704'); +INSERT INTO `yoshop_region` VALUES ('1544', '1533', '新郑', '新郑市', '中国,河南省,郑州市,新郑市', '3', 'xinzheng', '0371', '451100', 'X', '113.73645', '34.3955'); +INSERT INTO `yoshop_region` VALUES ('1545', '1533', '登封', '登封市', '中国,河南省,郑州市,登封市', '3', 'dengfeng', '0371', '452470', 'D', '113.05023', '34.45345'); +INSERT INTO `yoshop_region` VALUES ('1546', '1532', '开封', '开封市', '中国,河南省,开封市', '2', 'kaifeng', '0378', '475001', 'K', '114.341447', '34.797049'); +INSERT INTO `yoshop_region` VALUES ('1547', '1546', '龙亭', '龙亭区', '中国,河南省,开封市,龙亭区', '3', 'longting', '0378', '475100', 'L', '114.35484', '34.79995'); +INSERT INTO `yoshop_region` VALUES ('1548', '1546', '顺河', '顺河回族区', '中国,河南省,开封市,顺河回族区', '3', 'shunhe', '0378', '475000', 'S', '114.36123', '34.79586'); +INSERT INTO `yoshop_region` VALUES ('1549', '1546', '鼓楼', '鼓楼区', '中国,河南省,开封市,鼓楼区', '3', 'gulou', '0378', '475000', 'G', '114.35559', '34.79517'); +INSERT INTO `yoshop_region` VALUES ('1550', '1546', '禹王台', '禹王台区', '中国,河南省,开封市,禹王台区', '3', 'yuwangtai', '0378', '475003', 'Y', '114.34787', '34.77693'); +INSERT INTO `yoshop_region` VALUES ('1551', '1546', '祥符', '祥符区', '中国,河南省,开封市,祥符区', '3', 'xiangfu', '0378', '475100', 'X', '114.43859', '34.75874'); +INSERT INTO `yoshop_region` VALUES ('1552', '1546', '杞县', '杞县', '中国,河南省,开封市,杞县', '3', 'qixian', '0378', '475200', null, '114.7828', '34.55033'); +INSERT INTO `yoshop_region` VALUES ('1553', '1546', '通许', '通许县', '中国,河南省,开封市,通许县', '3', 'tongxu', '0378', '475400', 'T', '114.46716', '34.47522'); +INSERT INTO `yoshop_region` VALUES ('1554', '1546', '尉氏', '尉氏县', '中国,河南省,开封市,尉氏县', '3', 'weishi', '0378', '475500', 'W', '114.19284', '34.41223'); +INSERT INTO `yoshop_region` VALUES ('1555', '1546', '兰考', '兰考县', '中国,河南省,开封市,兰考县', '3', 'lankao', '0378', '475300', 'L', '114.81961', '34.8235'); +INSERT INTO `yoshop_region` VALUES ('1556', '1532', '洛阳', '洛阳市', '中国,河南省,洛阳市', '2', 'luoyang', '0379', '471000', 'L', '112.434468', '34.663041'); +INSERT INTO `yoshop_region` VALUES ('1557', '1556', '老城', '老城区', '中国,河南省,洛阳市,老城区', '3', 'laocheng', '0379', '471002', 'L', '112.46902', '34.68364'); +INSERT INTO `yoshop_region` VALUES ('1558', '1556', '西工', '西工区', '中国,河南省,洛阳市,西工区', '3', 'xigong', '0379', '471000', 'X', '112.4371', '34.67'); +INSERT INTO `yoshop_region` VALUES ('1559', '1556', '瀍河', '瀍河回族区', '中国,河南省,洛阳市,瀍河回族区', '3', 'chanhe', '0379', '471002', null, '112.50018', '34.67985'); +INSERT INTO `yoshop_region` VALUES ('1560', '1556', '涧西', '涧西区', '中国,河南省,洛阳市,涧西区', '3', 'jianxi', '0379', '471003', 'J', '112.39588', '34.65823'); +INSERT INTO `yoshop_region` VALUES ('1561', '1556', '吉利', '吉利区', '中国,河南省,洛阳市,吉利区', '3', 'jili', '0379', '471012', 'J', '112.58905', '34.90088'); +INSERT INTO `yoshop_region` VALUES ('1562', '1556', '洛龙', '洛龙区', '中国,河南省,洛阳市,洛龙区', '3', 'luolong', '0379', '471000', 'L', '112.46412', '34.61866'); +INSERT INTO `yoshop_region` VALUES ('1563', '1556', '孟津', '孟津县', '中国,河南省,洛阳市,孟津县', '3', 'mengjin', '0379', '471100', 'M', '112.44351', '34.826'); +INSERT INTO `yoshop_region` VALUES ('1564', '1556', '新安', '新安县', '中国,河南省,洛阳市,新安县', '3', 'xin\'an', '0379', '471800', 'X', '112.13238', '34.72814'); +INSERT INTO `yoshop_region` VALUES ('1565', '1556', '栾川', '栾川县', '中国,河南省,洛阳市,栾川县', '3', 'luanchuan', '0379', '471500', null, '111.61779', '33.78576'); +INSERT INTO `yoshop_region` VALUES ('1566', '1556', '嵩县', '嵩县', '中国,河南省,洛阳市,嵩县', '3', 'songxian', '0379', '471400', null, '112.08526', '34.13466'); +INSERT INTO `yoshop_region` VALUES ('1567', '1556', '汝阳', '汝阳县', '中国,河南省,洛阳市,汝阳县', '3', 'ruyang', '0379', '471200', 'R', '112.47314', '34.15387'); +INSERT INTO `yoshop_region` VALUES ('1568', '1556', '宜阳', '宜阳县', '中国,河南省,洛阳市,宜阳县', '3', 'yiyang', '0379', '471600', 'Y', '112.17907', '34.51523'); +INSERT INTO `yoshop_region` VALUES ('1569', '1556', '洛宁', '洛宁县', '中国,河南省,洛阳市,洛宁县', '3', 'luoning', '0379', '471700', 'L', '111.65087', '34.38913'); +INSERT INTO `yoshop_region` VALUES ('1570', '1556', '伊川', '伊川县', '中国,河南省,洛阳市,伊川县', '3', 'yichuan', '0379', '471300', 'Y', '112.42947', '34.42205'); +INSERT INTO `yoshop_region` VALUES ('1571', '1556', '偃师', '偃师市', '中国,河南省,洛阳市,偃师市', '3', 'yanshi', '0379', '471900', null, '112.7922', '34.7281'); +INSERT INTO `yoshop_region` VALUES ('1572', '1532', '平顶山', '平顶山市', '中国,河南省,平顶山市', '2', 'pingdingshan', '0375', '467000', 'P', '113.307718', '33.735241'); +INSERT INTO `yoshop_region` VALUES ('1573', '1572', '新华', '新华区', '中国,河南省,平顶山市,新华区', '3', 'xinhua', '0375', '467002', 'X', '113.29402', '33.7373'); +INSERT INTO `yoshop_region` VALUES ('1574', '1572', '卫东', '卫东区', '中国,河南省,平顶山市,卫东区', '3', 'weidong', '0375', '467021', 'W', '113.33511', '33.73472'); +INSERT INTO `yoshop_region` VALUES ('1575', '1572', '石龙', '石龙区', '中国,河南省,平顶山市,石龙区', '3', 'shilong', '0375', '467045', 'S', '112.89879', '33.89878'); +INSERT INTO `yoshop_region` VALUES ('1576', '1572', '湛河', '湛河区', '中国,河南省,平顶山市,湛河区', '3', 'zhanhe', '0375', '467000', 'Z', '113.29252', '33.7362'); +INSERT INTO `yoshop_region` VALUES ('1577', '1572', '宝丰', '宝丰县', '中国,河南省,平顶山市,宝丰县', '3', 'baofeng', '0375', '467400', 'B', '113.05493', '33.86916'); +INSERT INTO `yoshop_region` VALUES ('1578', '1572', '叶县', '叶县', '中国,河南省,平顶山市,叶县', '3', 'yexian', '0375', '467200', 'Y', '113.35104', '33.62225'); +INSERT INTO `yoshop_region` VALUES ('1579', '1572', '鲁山', '鲁山县', '中国,河南省,平顶山市,鲁山县', '3', 'lushan', '0375', '467300', 'L', '112.9057', '33.73879'); +INSERT INTO `yoshop_region` VALUES ('1580', '1572', '郏县', '郏县', '中国,河南省,平顶山市,郏县', '3', 'jiaxian', '0375', '467100', null, '113.21588', '33.97072'); +INSERT INTO `yoshop_region` VALUES ('1581', '1572', '舞钢', '舞钢市', '中国,河南省,平顶山市,舞钢市', '3', 'wugang', '0375', '462500', 'W', '113.52417', '33.2938'); +INSERT INTO `yoshop_region` VALUES ('1582', '1572', '汝州', '汝州市', '中国,河南省,平顶山市,汝州市', '3', 'ruzhou', '0375', '467500', 'R', '112.84301', '34.16135'); +INSERT INTO `yoshop_region` VALUES ('1583', '1532', '安阳', '安阳市', '中国,河南省,安阳市', '2', 'anyang', '0372', '455000', 'A', '114.352482', '36.103442'); +INSERT INTO `yoshop_region` VALUES ('1584', '1583', '文峰', '文峰区', '中国,河南省,安阳市,文峰区', '3', 'wenfeng', '0372', '455000', 'W', '114.35708', '36.09046'); +INSERT INTO `yoshop_region` VALUES ('1585', '1583', '北关', '北关区', '中国,河南省,安阳市,北关区', '3', 'beiguan', '0372', '455001', 'B', '114.35735', '36.11872'); +INSERT INTO `yoshop_region` VALUES ('1586', '1583', '殷都', '殷都区', '中国,河南省,安阳市,殷都区', '3', 'yindu', '0372', '455004', 'Y', '114.3034', '36.1099'); +INSERT INTO `yoshop_region` VALUES ('1587', '1583', '龙安', '龙安区', '中国,河南省,安阳市,龙安区', '3', 'long\'an', '0372', '455001', 'L', '114.34814', '36.11904'); +INSERT INTO `yoshop_region` VALUES ('1588', '1583', '安阳', '安阳县', '中国,河南省,安阳市,安阳县', '3', 'anyang', '0372', '455000', 'A', '114.36605', '36.06695'); +INSERT INTO `yoshop_region` VALUES ('1589', '1583', '汤阴', '汤阴县', '中国,河南省,安阳市,汤阴县', '3', 'tangyin', '0372', '456150', 'T', '114.35839', '35.92152'); +INSERT INTO `yoshop_region` VALUES ('1590', '1583', '滑县', '滑县', '中国,河南省,安阳市,滑县', '3', 'huaxian', '0372', '456400', 'H', '114.52066', '35.5807'); +INSERT INTO `yoshop_region` VALUES ('1591', '1583', '内黄', '内黄县', '中国,河南省,安阳市,内黄县', '3', 'neihuang', '0372', '456350', 'N', '114.90673', '35.95269'); +INSERT INTO `yoshop_region` VALUES ('1592', '1583', '林州', '林州市', '中国,河南省,安阳市,林州市', '3', 'linzhou', '0372', '456550', 'L', '113.81558', '36.07804'); +INSERT INTO `yoshop_region` VALUES ('1593', '1532', '鹤壁', '鹤壁市', '中国,河南省,鹤壁市', '2', 'hebi', '0392', '458030', 'H', '114.295444', '35.748236'); +INSERT INTO `yoshop_region` VALUES ('1594', '1593', '鹤山', '鹤山区', '中国,河南省,鹤壁市,鹤山区', '3', 'heshan', '0392', '458010', 'H', '114.16336', '35.95458'); +INSERT INTO `yoshop_region` VALUES ('1595', '1593', '山城', '山城区', '中国,河南省,鹤壁市,山城区', '3', 'shancheng', '0392', '458000', 'S', '114.18443', '35.89773'); +INSERT INTO `yoshop_region` VALUES ('1596', '1593', '淇滨', '淇滨区', '中国,河南省,鹤壁市,淇滨区', '3', 'qibin', '0392', '458000', null, '114.29867', '35.74127'); +INSERT INTO `yoshop_region` VALUES ('1597', '1593', '浚县', '浚县', '中国,河南省,鹤壁市,浚县', '3', 'xunxian', '0392', '456250', 'J', '114.54879', '35.67085'); +INSERT INTO `yoshop_region` VALUES ('1598', '1593', '淇县', '淇县', '中国,河南省,鹤壁市,淇县', '3', 'qixian', '0392', '456750', null, '114.1976', '35.60782'); +INSERT INTO `yoshop_region` VALUES ('1599', '1532', '新乡', '新乡市', '中国,河南省,新乡市', '2', 'xinxiang', '0373', '453000', 'X', '113.883991', '35.302616'); +INSERT INTO `yoshop_region` VALUES ('1600', '1599', '红旗', '红旗区', '中国,河南省,新乡市,红旗区', '3', 'hongqi', '0373', '453000', 'H', '113.87523', '35.30367'); +INSERT INTO `yoshop_region` VALUES ('1601', '1599', '卫滨', '卫滨区', '中国,河南省,新乡市,卫滨区', '3', 'weibin', '0373', '453000', 'W', '113.86578', '35.30211'); +INSERT INTO `yoshop_region` VALUES ('1602', '1599', '凤泉', '凤泉区', '中国,河南省,新乡市,凤泉区', '3', 'fengquan', '0373', '453011', 'F', '113.91507', '35.38399'); +INSERT INTO `yoshop_region` VALUES ('1603', '1599', '牧野', '牧野区', '中国,河南省,新乡市,牧野区', '3', 'muye', '0373', '453002', 'M', '113.9086', '35.3149'); +INSERT INTO `yoshop_region` VALUES ('1604', '1599', '新乡', '新乡县', '中国,河南省,新乡市,新乡县', '3', 'xinxiang', '0373', '453700', 'X', '113.80511', '35.19075'); +INSERT INTO `yoshop_region` VALUES ('1605', '1599', '获嘉', '获嘉县', '中国,河南省,新乡市,获嘉县', '3', 'huojia', '0373', '453800', 'H', '113.66159', '35.26521'); +INSERT INTO `yoshop_region` VALUES ('1606', '1599', '原阳', '原阳县', '中国,河南省,新乡市,原阳县', '3', 'yuanyang', '0373', '453500', 'Y', '113.93994', '35.06565'); +INSERT INTO `yoshop_region` VALUES ('1607', '1599', '延津', '延津县', '中国,河南省,新乡市,延津县', '3', 'yanjin', '0373', '453200', 'Y', '114.20266', '35.14327'); +INSERT INTO `yoshop_region` VALUES ('1608', '1599', '封丘', '封丘县', '中国,河南省,新乡市,封丘县', '3', 'fengqiu', '0373', '453300', 'F', '114.41915', '35.04166'); +INSERT INTO `yoshop_region` VALUES ('1609', '1599', '长垣', '长垣县', '中国,河南省,新乡市,长垣县', '3', 'changyuan', '0373', '453400', 'C', '114.66882', '35.20046'); +INSERT INTO `yoshop_region` VALUES ('1610', '1599', '卫辉', '卫辉市', '中国,河南省,新乡市,卫辉市', '3', 'weihui', '0373', '453100', 'W', '114.06454', '35.39843'); +INSERT INTO `yoshop_region` VALUES ('1611', '1599', '辉县', '辉县市', '中国,河南省,新乡市,辉县市', '3', 'huixian', '0373', '453600', 'H', '113.8067', '35.46307'); +INSERT INTO `yoshop_region` VALUES ('1612', '1532', '焦作', '焦作市', '中国,河南省,焦作市', '2', 'jiaozuo', '0391', '454002', 'J', '113.238266', '35.23904'); +INSERT INTO `yoshop_region` VALUES ('1613', '1612', '解放', '解放区', '中国,河南省,焦作市,解放区', '3', 'jiefang', '0391', '454000', 'J', '113.22933', '35.24023'); +INSERT INTO `yoshop_region` VALUES ('1614', '1612', '中站', '中站区', '中国,河南省,焦作市,中站区', '3', 'zhongzhan', '0391', '454191', 'Z', '113.18315', '35.23665'); +INSERT INTO `yoshop_region` VALUES ('1615', '1612', '马村', '马村区', '中国,河南省,焦作市,马村区', '3', 'macun', '0391', '454171', 'M', '113.3187', '35.26908'); +INSERT INTO `yoshop_region` VALUES ('1616', '1612', '山阳', '山阳区', '中国,河南省,焦作市,山阳区', '3', 'shanyang', '0391', '454002', 'S', '113.25464', '35.21436'); +INSERT INTO `yoshop_region` VALUES ('1617', '1612', '修武', '修武县', '中国,河南省,焦作市,修武县', '3', 'xiuwu', '0391', '454350', 'X', '113.44775', '35.22357'); +INSERT INTO `yoshop_region` VALUES ('1618', '1612', '博爱', '博爱县', '中国,河南省,焦作市,博爱县', '3', 'boai', '0391', '454450', 'B', '113.06698', '35.16943'); +INSERT INTO `yoshop_region` VALUES ('1619', '1612', '武陟', '武陟县', '中国,河南省,焦作市,武陟县', '3', 'wuzhi', '0391', '454950', 'W', '113.39718', '35.09505'); +INSERT INTO `yoshop_region` VALUES ('1620', '1612', '温县', '温县', '中国,河南省,焦作市,温县', '3', 'wenxian', '0391', '454850', 'W', '113.08065', '34.94022'); +INSERT INTO `yoshop_region` VALUES ('1621', '1612', '沁阳', '沁阳市', '中国,河南省,焦作市,沁阳市', '3', 'qinyang', '0391', '454550', 'Q', '112.94494', '35.08935'); +INSERT INTO `yoshop_region` VALUES ('1622', '1612', '孟州', '孟州市', '中国,河南省,焦作市,孟州市', '3', 'mengzhou', '0391', '454750', 'M', '112.79138', '34.9071'); +INSERT INTO `yoshop_region` VALUES ('1623', '1532', '濮阳', '濮阳市', '中国,河南省,濮阳市', '2', 'puyang', '0393', '457000', null, '115.041299', '35.768234'); +INSERT INTO `yoshop_region` VALUES ('1624', '1623', '华龙', '华龙区', '中国,河南省,濮阳市,华龙区', '3', 'hualong', '0393', '457001', 'H', '115.07446', '35.77736'); +INSERT INTO `yoshop_region` VALUES ('1625', '1623', '清丰', '清丰县', '中国,河南省,濮阳市,清丰县', '3', 'qingfeng', '0393', '457300', 'Q', '115.10415', '35.88507'); +INSERT INTO `yoshop_region` VALUES ('1626', '1623', '南乐', '南乐县', '中国,河南省,濮阳市,南乐县', '3', 'nanle', '0393', '457400', 'N', '115.20639', '36.07686'); +INSERT INTO `yoshop_region` VALUES ('1627', '1623', '范县', '范县', '中国,河南省,濮阳市,范县', '3', 'fanxian', '0393', '457500', 'F', '115.50405', '35.85178'); +INSERT INTO `yoshop_region` VALUES ('1628', '1623', '台前', '台前县', '中国,河南省,濮阳市,台前县', '3', 'taiqian', '0393', '457600', 'T', '115.87158', '35.96923'); +INSERT INTO `yoshop_region` VALUES ('1629', '1623', '濮阳', '濮阳县', '中国,河南省,濮阳市,濮阳县', '3', 'puyang', '0393', '457100', null, '115.03057', '35.70745'); +INSERT INTO `yoshop_region` VALUES ('1630', '1532', '许昌', '许昌市', '中国,河南省,许昌市', '2', 'xuchang', '0374', '461000', 'X', '113.826063', '34.022956'); +INSERT INTO `yoshop_region` VALUES ('1631', '1630', '魏都', '魏都区', '中国,河南省,许昌市,魏都区', '3', 'weidu', '0374', '461000', 'W', '113.8227', '34.02544'); +INSERT INTO `yoshop_region` VALUES ('1632', '1630', '许昌', '许昌县', '中国,河南省,许昌市,许昌县', '3', 'xuchang', '0374', '461100', 'X', '113.84707', '34.00406'); +INSERT INTO `yoshop_region` VALUES ('1633', '1630', '鄢陵', '鄢陵县', '中国,河南省,许昌市,鄢陵县', '3', 'yanling', '0374', '461200', null, '114.18795', '34.10317'); +INSERT INTO `yoshop_region` VALUES ('1634', '1630', '襄城', '襄城县', '中国,河南省,许昌市,襄城县', '3', 'xiangcheng', '0374', '461700', 'X', '113.48196', '33.84928'); +INSERT INTO `yoshop_region` VALUES ('1635', '1630', '禹州', '禹州市', '中国,河南省,许昌市,禹州市', '3', 'yuzhou', '0374', '461670', 'Y', '113.48803', '34.14054'); +INSERT INTO `yoshop_region` VALUES ('1636', '1630', '长葛', '长葛市', '中国,河南省,许昌市,长葛市', '3', 'changge', '0374', '461500', 'C', '113.77328', '34.21846'); +INSERT INTO `yoshop_region` VALUES ('1637', '1532', '漯河', '漯河市', '中国,河南省,漯河市', '2', 'luohe', '0395', '462000', null, '114.026405', '33.575855'); +INSERT INTO `yoshop_region` VALUES ('1638', '1637', '源汇', '源汇区', '中国,河南省,漯河市,源汇区', '3', 'yuanhui', '0395', '462000', 'Y', '114.00647', '33.55627'); +INSERT INTO `yoshop_region` VALUES ('1639', '1637', '郾城', '郾城区', '中国,河南省,漯河市,郾城区', '3', 'yancheng', '0395', '462300', null, '114.00694', '33.58723'); +INSERT INTO `yoshop_region` VALUES ('1640', '1637', '召陵', '召陵区', '中国,河南省,漯河市,召陵区', '3', 'zhaoling', '0395', '462300', 'Z', '114.09399', '33.58601'); +INSERT INTO `yoshop_region` VALUES ('1641', '1637', '舞阳', '舞阳县', '中国,河南省,漯河市,舞阳县', '3', 'wuyang', '0395', '462400', 'W', '113.59848', '33.43243'); +INSERT INTO `yoshop_region` VALUES ('1642', '1637', '临颍', '临颍县', '中国,河南省,漯河市,临颍县', '3', 'linying', '0395', '462600', 'L', '113.93661', '33.81123'); +INSERT INTO `yoshop_region` VALUES ('1643', '1532', '三门峡', '三门峡市', '中国,河南省,三门峡市', '2', 'sanmenxia', '0398', '472000', 'S', '111.194099', '34.777338'); +INSERT INTO `yoshop_region` VALUES ('1644', '1643', '湖滨', '湖滨区', '中国,河南省,三门峡市,湖滨区', '3', 'hubin', '0398', '472000', 'H', '111.20006', '34.77872'); +INSERT INTO `yoshop_region` VALUES ('1645', '1643', '渑池', '渑池县', '中国,河南省,三门峡市,渑池县', '3', 'mianchi', '0398', '472400', null, '111.76184', '34.76725'); +INSERT INTO `yoshop_region` VALUES ('1646', '1643', '陕县', '陕县', '中国,河南省,三门峡市,陕县', '3', 'shanxian', '0398', '472100', 'S', '111.10333', '34.72052'); +INSERT INTO `yoshop_region` VALUES ('1647', '1643', '卢氏', '卢氏县', '中国,河南省,三门峡市,卢氏县', '3', 'lushi', '0398', '472200', 'L', '111.04782', '34.05436'); +INSERT INTO `yoshop_region` VALUES ('1648', '1643', '义马', '义马市', '中国,河南省,三门峡市,义马市', '3', 'yima', '0398', '472300', 'Y', '111.87445', '34.74721'); +INSERT INTO `yoshop_region` VALUES ('1649', '1643', '灵宝', '灵宝市', '中国,河南省,三门峡市,灵宝市', '3', 'lingbao', '0398', '472500', 'L', '110.8945', '34.51682'); +INSERT INTO `yoshop_region` VALUES ('1650', '1532', '南阳', '南阳市', '中国,河南省,南阳市', '2', 'nanyang', '0377', '473002', 'N', '112.540918', '32.999082'); +INSERT INTO `yoshop_region` VALUES ('1651', '1650', '宛城', '宛城区', '中国,河南省,南阳市,宛城区', '3', 'wancheng', '0377', '473001', 'W', '112.53955', '33.00378'); +INSERT INTO `yoshop_region` VALUES ('1652', '1650', '卧龙', '卧龙区', '中国,河南省,南阳市,卧龙区', '3', 'wolong', '0377', '473003', 'W', '112.53479', '32.98615'); +INSERT INTO `yoshop_region` VALUES ('1653', '1650', '南召', '南召县', '中国,河南省,南阳市,南召县', '3', 'nanzhao', '0377', '474650', 'N', '112.43194', '33.49098'); +INSERT INTO `yoshop_region` VALUES ('1654', '1650', '方城', '方城县', '中国,河南省,南阳市,方城县', '3', 'fangcheng', '0377', '473200', 'F', '113.01269', '33.25453'); +INSERT INTO `yoshop_region` VALUES ('1655', '1650', '西峡', '西峡县', '中国,河南省,南阳市,西峡县', '3', 'xixia', '0377', '474550', 'X', '111.48187', '33.29772'); +INSERT INTO `yoshop_region` VALUES ('1656', '1650', '镇平', '镇平县', '中国,河南省,南阳市,镇平县', '3', 'zhenping', '0377', '474250', 'Z', '112.2398', '33.03629'); +INSERT INTO `yoshop_region` VALUES ('1657', '1650', '内乡', '内乡县', '中国,河南省,南阳市,内乡县', '3', 'neixiang', '0377', '474350', 'N', '111.84957', '33.04671'); +INSERT INTO `yoshop_region` VALUES ('1658', '1650', '淅川', '淅川县', '中国,河南省,南阳市,淅川县', '3', 'xichuan', '0377', '474450', null, '111.48663', '33.13708'); +INSERT INTO `yoshop_region` VALUES ('1659', '1650', '社旗', '社旗县', '中国,河南省,南阳市,社旗县', '3', 'sheqi', '0377', '473300', 'S', '112.94656', '33.05503'); +INSERT INTO `yoshop_region` VALUES ('1660', '1650', '唐河', '唐河县', '中国,河南省,南阳市,唐河县', '3', 'tanghe', '0377', '473400', 'T', '112.83609', '32.69453'); +INSERT INTO `yoshop_region` VALUES ('1661', '1650', '新野', '新野县', '中国,河南省,南阳市,新野县', '3', 'xinye', '0377', '473500', 'X', '112.36151', '32.51698'); +INSERT INTO `yoshop_region` VALUES ('1662', '1650', '桐柏', '桐柏县', '中国,河南省,南阳市,桐柏县', '3', 'tongbai', '0377', '474750', 'T', '113.42886', '32.37917'); +INSERT INTO `yoshop_region` VALUES ('1663', '1650', '邓州', '邓州市', '中国,河南省,南阳市,邓州市', '3', 'dengzhou', '0377', '474150', 'D', '112.0896', '32.68577'); +INSERT INTO `yoshop_region` VALUES ('1664', '1532', '商丘', '商丘市', '中国,河南省,商丘市', '2', 'shangqiu', '0370', '476000', 'S', '115.650497', '34.437054'); +INSERT INTO `yoshop_region` VALUES ('1665', '1664', '梁园', '梁园区', '中国,河南省,商丘市,梁园区', '3', 'liangyuan', '0370', '476000', 'L', '115.64487', '34.44341'); +INSERT INTO `yoshop_region` VALUES ('1666', '1664', '睢阳', '睢阳区', '中国,河南省,商丘市,睢阳区', '3', 'suiyang', '0370', '476100', null, '115.65338', '34.38804'); +INSERT INTO `yoshop_region` VALUES ('1667', '1664', '民权', '民权县', '中国,河南省,商丘市,民权县', '3', 'minquan', '0370', '476800', 'M', '115.14621', '34.64931'); +INSERT INTO `yoshop_region` VALUES ('1668', '1664', '睢县', '睢县', '中国,河南省,商丘市,睢县', '3', 'suixian', '0370', '476900', null, '115.07168', '34.44539'); +INSERT INTO `yoshop_region` VALUES ('1669', '1664', '宁陵', '宁陵县', '中国,河南省,商丘市,宁陵县', '3', 'ningling', '0370', '476700', 'N', '115.30511', '34.45463'); +INSERT INTO `yoshop_region` VALUES ('1670', '1664', '柘城', '柘城县', '中国,河南省,商丘市,柘城县', '3', 'zhecheng', '0370', '476200', null, '115.30538', '34.0911'); +INSERT INTO `yoshop_region` VALUES ('1671', '1664', '虞城', '虞城县', '中国,河南省,商丘市,虞城县', '3', 'yucheng', '0370', '476300', 'Y', '115.86337', '34.40189'); +INSERT INTO `yoshop_region` VALUES ('1672', '1664', '夏邑', '夏邑县', '中国,河南省,商丘市,夏邑县', '3', 'xiayi', '0370', '476400', 'X', '116.13348', '34.23242'); +INSERT INTO `yoshop_region` VALUES ('1673', '1664', '永城', '永城市', '中国,河南省,商丘市,永城市', '3', 'yongcheng', '0370', '476600', 'Y', '116.44943', '33.92911'); +INSERT INTO `yoshop_region` VALUES ('1674', '1532', '信阳', '信阳市', '中国,河南省,信阳市', '2', 'xinyang', '0376', '464000', 'X', '114.075031', '32.123274'); +INSERT INTO `yoshop_region` VALUES ('1675', '1674', '浉河', '浉河区', '中国,河南省,信阳市,浉河区', '3', 'shihe', '0376', '464000', null, '114.05871', '32.1168'); +INSERT INTO `yoshop_region` VALUES ('1676', '1674', '平桥', '平桥区', '中国,河南省,信阳市,平桥区', '3', 'pingqiao', '0376', '464100', 'P', '114.12435', '32.10095'); +INSERT INTO `yoshop_region` VALUES ('1677', '1674', '罗山', '罗山县', '中国,河南省,信阳市,罗山县', '3', 'luoshan', '0376', '464200', 'L', '114.5314', '32.20277'); +INSERT INTO `yoshop_region` VALUES ('1678', '1674', '光山', '光山县', '中国,河南省,信阳市,光山县', '3', 'guangshan', '0376', '465450', 'G', '114.91873', '32.00992'); +INSERT INTO `yoshop_region` VALUES ('1679', '1674', '新县', '新县', '中国,河南省,信阳市,新县', '3', 'xinxian', '0376', '465550', 'X', '114.87924', '31.64386'); +INSERT INTO `yoshop_region` VALUES ('1680', '1674', '商城', '商城县', '中国,河南省,信阳市,商城县', '3', 'shangcheng', '0376', '465350', 'S', '115.40856', '31.79986'); +INSERT INTO `yoshop_region` VALUES ('1681', '1674', '固始', '固始县', '中国,河南省,信阳市,固始县', '3', 'gushi', '0376', '465250', 'G', '115.68298', '32.18011'); +INSERT INTO `yoshop_region` VALUES ('1682', '1674', '潢川', '潢川县', '中国,河南省,信阳市,潢川县', '3', 'huangchuan', '0376', '465150', null, '115.04696', '32.13763'); +INSERT INTO `yoshop_region` VALUES ('1683', '1674', '淮滨', '淮滨县', '中国,河南省,信阳市,淮滨县', '3', 'huaibin', '0376', '464400', 'H', '115.4205', '32.46614'); +INSERT INTO `yoshop_region` VALUES ('1684', '1674', '息县', '息县', '中国,河南省,信阳市,息县', '3', 'xixian', '0376', '464300', 'X', '114.7402', '32.34279'); +INSERT INTO `yoshop_region` VALUES ('1685', '1532', '周口', '周口市', '中国,河南省,周口市', '2', 'zhoukou', '0394', '466000', 'Z', '114.649653', '33.620357'); +INSERT INTO `yoshop_region` VALUES ('1686', '1685', '川汇', '川汇区', '中国,河南省,周口市,川汇区', '3', 'chuanhui', '0394', '466000', 'C', '114.64202', '33.6256'); +INSERT INTO `yoshop_region` VALUES ('1687', '1685', '扶沟', '扶沟县', '中国,河南省,周口市,扶沟县', '3', 'fugou', '0394', '461300', 'F', '114.39477', '34.05999'); +INSERT INTO `yoshop_region` VALUES ('1688', '1685', '西华', '西华县', '中国,河南省,周口市,西华县', '3', 'xihua', '0394', '466600', 'X', '114.52279', '33.78548'); +INSERT INTO `yoshop_region` VALUES ('1689', '1685', '商水', '商水县', '中国,河南省,周口市,商水县', '3', 'shangshui', '0394', '466100', 'S', '114.60604', '33.53912'); +INSERT INTO `yoshop_region` VALUES ('1690', '1685', '沈丘', '沈丘县', '中国,河南省,周口市,沈丘县', '3', 'shenqiu', '0394', '466300', 'S', '115.09851', '33.40936'); +INSERT INTO `yoshop_region` VALUES ('1691', '1685', '郸城', '郸城县', '中国,河南省,周口市,郸城县', '3', 'dancheng', '0394', '477150', 'D', '115.17715', '33.64485'); +INSERT INTO `yoshop_region` VALUES ('1692', '1685', '淮阳', '淮阳县', '中国,河南省,周口市,淮阳县', '3', 'huaiyang', '0394', '466700', 'H', '114.88848', '33.73211'); +INSERT INTO `yoshop_region` VALUES ('1693', '1685', '太康', '太康县', '中国,河南省,周口市,太康县', '3', 'taikang', '0394', '461400', 'T', '114.83773', '34.06376'); +INSERT INTO `yoshop_region` VALUES ('1694', '1685', '鹿邑', '鹿邑县', '中国,河南省,周口市,鹿邑县', '3', 'luyi', '0394', '477200', 'L', '115.48553', '33.85931'); +INSERT INTO `yoshop_region` VALUES ('1695', '1685', '项城', '项城市', '中国,河南省,周口市,项城市', '3', 'xiangcheng', '0394', '466200', 'X', '114.87558', '33.4672'); +INSERT INTO `yoshop_region` VALUES ('1696', '1532', '驻马店', '驻马店市', '中国,河南省,驻马店市', '2', 'zhumadian', '0396', '463000', 'Z', '114.024736', '32.980169'); +INSERT INTO `yoshop_region` VALUES ('1697', '1696', '驿城', '驿城区', '中国,河南省,驻马店市,驿城区', '3', 'yicheng', '0396', '463000', null, '113.99377', '32.97316'); +INSERT INTO `yoshop_region` VALUES ('1698', '1696', '西平', '西平县', '中国,河南省,驻马店市,西平县', '3', 'xiping', '0396', '463900', 'X', '114.02322', '33.3845'); +INSERT INTO `yoshop_region` VALUES ('1699', '1696', '上蔡', '上蔡县', '中国,河南省,驻马店市,上蔡县', '3', 'shangcai', '0396', '463800', 'S', '114.26825', '33.26825'); +INSERT INTO `yoshop_region` VALUES ('1700', '1696', '平舆', '平舆县', '中国,河南省,驻马店市,平舆县', '3', 'pingyu', '0396', '463400', 'P', '114.63552', '32.95727'); +INSERT INTO `yoshop_region` VALUES ('1701', '1696', '正阳', '正阳县', '中国,河南省,驻马店市,正阳县', '3', 'zhengyang', '0396', '463600', 'Z', '114.38952', '32.6039'); +INSERT INTO `yoshop_region` VALUES ('1702', '1696', '确山', '确山县', '中国,河南省,驻马店市,确山县', '3', 'queshan', '0396', '463200', 'Q', '114.02917', '32.80281'); +INSERT INTO `yoshop_region` VALUES ('1703', '1696', '泌阳', '泌阳县', '中国,河南省,驻马店市,泌阳县', '3', 'biyang', '0396', '463700', 'M', '113.32681', '32.71781'); +INSERT INTO `yoshop_region` VALUES ('1704', '1696', '汝南', '汝南县', '中国,河南省,驻马店市,汝南县', '3', 'runan', '0396', '463300', 'R', '114.36138', '33.00461'); +INSERT INTO `yoshop_region` VALUES ('1705', '1696', '遂平', '遂平县', '中国,河南省,驻马店市,遂平县', '3', 'suiping', '0396', '463100', 'S', '114.01297', '33.14571'); +INSERT INTO `yoshop_region` VALUES ('1706', '1696', '新蔡', '新蔡县', '中国,河南省,驻马店市,新蔡县', '3', 'xincai', '0396', '463500', 'X', '114.98199', '32.7502'); +INSERT INTO `yoshop_region` VALUES ('1707', '1532', ' ', '直辖县级', '中国,河南省,直辖县级', '2', '', '', '', 'Z', '113.665412', '34.757975'); +INSERT INTO `yoshop_region` VALUES ('1708', '1707', '济源', '济源市', '中国,河南省,直辖县级,济源市', '3', 'jiyuan', '0391', '454650', 'J', '112.590047', '35.090378'); +INSERT INTO `yoshop_region` VALUES ('1709', '0', '湖北', '湖北省', '中国,湖北省', '1', 'hubei', '', '', 'H', '114.298572', '30.584355'); +INSERT INTO `yoshop_region` VALUES ('1710', '1709', '武汉', '武汉市', '中国,湖北省,武汉市', '2', 'wuhan', '', '430014', 'W', '114.298572', '30.584355'); +INSERT INTO `yoshop_region` VALUES ('1711', '1710', '江岸', '江岸区', '中国,湖北省,武汉市,江岸区', '3', 'jiang\'an', '027', '430014', 'J', '114.30943', '30.59982'); +INSERT INTO `yoshop_region` VALUES ('1712', '1710', '江汉', '江汉区', '中国,湖北省,武汉市,江汉区', '3', 'jianghan', '027', '430021', 'J', '114.27093', '30.60146'); +INSERT INTO `yoshop_region` VALUES ('1713', '1710', '硚口', '硚口区', '中国,湖北省,武汉市,硚口区', '3', 'qiaokou', '027', '430033', null, '114.26422', '30.56945'); +INSERT INTO `yoshop_region` VALUES ('1714', '1710', '汉阳', '汉阳区', '中国,湖北省,武汉市,汉阳区', '3', 'hanyang', '027', '430050', 'H', '114.27478', '30.54915'); +INSERT INTO `yoshop_region` VALUES ('1715', '1710', '武昌', '武昌区', '中国,湖北省,武汉市,武昌区', '3', 'wuchang', '027', '430061', 'W', '114.31589', '30.55389'); +INSERT INTO `yoshop_region` VALUES ('1716', '1710', '青山', '青山区', '中国,湖北省,武汉市,青山区', '3', 'qingshan', '027', '430080', 'Q', '114.39117', '30.63427'); +INSERT INTO `yoshop_region` VALUES ('1717', '1710', '洪山', '洪山区', '中国,湖北省,武汉市,洪山区', '3', 'hongshan', '027', '430070', 'H', '114.34375', '30.49989'); +INSERT INTO `yoshop_region` VALUES ('1718', '1710', '东西湖', '东西湖区', '中国,湖北省,武汉市,东西湖区', '3', 'dongxihu', '027', '430040', 'D', '114.13708', '30.61989'); +INSERT INTO `yoshop_region` VALUES ('1719', '1710', '汉南', '汉南区', '中国,湖北省,武汉市,汉南区', '3', 'hannan', '027', '430090', 'H', '114.08462', '30.30879'); +INSERT INTO `yoshop_region` VALUES ('1720', '1710', '蔡甸', '蔡甸区', '中国,湖北省,武汉市,蔡甸区', '3', 'caidian', '027', '430100', 'C', '114.02929', '30.58197'); +INSERT INTO `yoshop_region` VALUES ('1721', '1710', '江夏', '江夏区', '中国,湖北省,武汉市,江夏区', '3', 'jiangxia', '027', '430200', 'J', '114.31301', '30.34653'); +INSERT INTO `yoshop_region` VALUES ('1722', '1710', '黄陂', '黄陂区', '中国,湖北省,武汉市,黄陂区', '3', 'huangpi', '027', '432200', 'H', '114.37512', '30.88151'); +INSERT INTO `yoshop_region` VALUES ('1723', '1710', '新洲', '新洲区', '中国,湖北省,武汉市,新洲区', '3', 'xinzhou', '027', '431400', 'X', '114.80136', '30.84145'); +INSERT INTO `yoshop_region` VALUES ('1724', '1709', '黄石', '黄石市', '中国,湖北省,黄石市', '2', 'huangshi', '0714', '435003', 'H', '115.077048', '30.220074'); +INSERT INTO `yoshop_region` VALUES ('1725', '1724', '黄石港', '黄石港区', '中国,湖北省,黄石市,黄石港区', '3', 'huangshigang', '0714', '435000', 'H', '115.06604', '30.22279'); +INSERT INTO `yoshop_region` VALUES ('1726', '1724', '西塞山', '西塞山区', '中国,湖北省,黄石市,西塞山区', '3', 'xisaishan', '0714', '435001', 'X', '115.11016', '30.20487'); +INSERT INTO `yoshop_region` VALUES ('1727', '1724', '下陆', '下陆区', '中国,湖北省,黄石市,下陆区', '3', 'xialu', '0714', '435005', 'X', '114.96112', '30.17368'); +INSERT INTO `yoshop_region` VALUES ('1728', '1724', '铁山', '铁山区', '中国,湖北省,黄石市,铁山区', '3', 'tieshan', '0714', '435006', 'T', '114.90109', '30.20678'); +INSERT INTO `yoshop_region` VALUES ('1729', '1724', '阳新', '阳新县', '中国,湖北省,黄石市,阳新县', '3', 'yangxin', '0714', '435200', 'Y', '115.21527', '29.83038'); +INSERT INTO `yoshop_region` VALUES ('1730', '1724', '大冶', '大冶市', '中国,湖北省,黄石市,大冶市', '3', 'daye', '0714', '435100', 'D', '114.97174', '30.09438'); +INSERT INTO `yoshop_region` VALUES ('1731', '1709', '十堰', '十堰市', '中国,湖北省,十堰市', '2', 'shiyan', '0719', '442000', 'S', '110.785239', '32.647017'); +INSERT INTO `yoshop_region` VALUES ('1732', '1731', '茅箭', '茅箭区', '中国,湖北省,十堰市,茅箭区', '3', 'maojian', '0719', '442012', 'M', '110.81341', '32.59153'); +INSERT INTO `yoshop_region` VALUES ('1733', '1731', '张湾', '张湾区', '中国,湖北省,十堰市,张湾区', '3', 'zhangwan', '0719', '442001', 'Z', '110.77067', '32.65195'); +INSERT INTO `yoshop_region` VALUES ('1734', '1731', '郧阳', '郧阳区', '中国,湖北省,十堰市,郧阳区', '3', 'yunyang', '0719', '442500', 'Y', '110.81854', '32.83593'); +INSERT INTO `yoshop_region` VALUES ('1735', '1731', '郧西', '郧西县', '中国,湖北省,十堰市,郧西县', '3', 'yunxi', '0719', '442600', 'Y', '110.42556', '32.99349'); +INSERT INTO `yoshop_region` VALUES ('1736', '1731', '竹山', '竹山县', '中国,湖北省,十堰市,竹山县', '3', 'zhushan', '0719', '442200', 'Z', '110.23071', '32.22536'); +INSERT INTO `yoshop_region` VALUES ('1737', '1731', '竹溪', '竹溪县', '中国,湖北省,十堰市,竹溪县', '3', 'zhuxi', '0719', '442300', 'Z', '109.71798', '32.31901'); +INSERT INTO `yoshop_region` VALUES ('1738', '1731', '房县', '房县', '中国,湖北省,十堰市,房县', '3', 'fangxian', '0719', '442100', 'F', '110.74386', '32.05794'); +INSERT INTO `yoshop_region` VALUES ('1739', '1731', '丹江口', '丹江口市', '中国,湖北省,十堰市,丹江口市', '3', 'danjiangkou', '0719', '442700', 'D', '111.51525', '32.54085'); +INSERT INTO `yoshop_region` VALUES ('1740', '1709', '宜昌', '宜昌市', '中国,湖北省,宜昌市', '2', 'yichang', '0717', '443000', 'Y', '111.290843', '30.702636'); +INSERT INTO `yoshop_region` VALUES ('1741', '1740', '西陵', '西陵区', '中国,湖北省,宜昌市,西陵区', '3', 'xiling', '0717', '443000', 'X', '111.28573', '30.71077'); +INSERT INTO `yoshop_region` VALUES ('1742', '1740', '伍家岗', '伍家岗区', '中国,湖北省,宜昌市,伍家岗区', '3', 'wujiagang', '0717', '443001', 'W', '111.3609', '30.64434'); +INSERT INTO `yoshop_region` VALUES ('1743', '1740', '点军', '点军区', '中国,湖北省,宜昌市,点军区', '3', 'dianjun', '0717', '443006', 'D', '111.26828', '30.6934'); +INSERT INTO `yoshop_region` VALUES ('1744', '1740', '猇亭', '猇亭区', '中国,湖北省,宜昌市,猇亭区', '3', 'xiaoting', '0717', '443007', null, '111.44079', '30.52663'); +INSERT INTO `yoshop_region` VALUES ('1745', '1740', '夷陵', '夷陵区', '中国,湖北省,宜昌市,夷陵区', '3', 'yiling', '0717', '443100', 'Y', '111.3262', '30.76881'); +INSERT INTO `yoshop_region` VALUES ('1746', '1740', '远安', '远安县', '中国,湖北省,宜昌市,远安县', '3', 'yuan\'an', '0717', '444200', 'Y', '111.6416', '31.05989'); +INSERT INTO `yoshop_region` VALUES ('1747', '1740', '兴山', '兴山县', '中国,湖北省,宜昌市,兴山县', '3', 'xingshan', '0717', '443711', 'X', '110.74951', '31.34686'); +INSERT INTO `yoshop_region` VALUES ('1748', '1740', '秭归', '秭归县', '中国,湖北省,宜昌市,秭归县', '3', 'zigui', '0717', '443600', null, '110.98156', '30.82702'); +INSERT INTO `yoshop_region` VALUES ('1749', '1740', '长阳', '长阳土家族自治县', '中国,湖北省,宜昌市,长阳土家族自治县', '3', 'changyang', '0717', '443500', 'C', '111.20105', '30.47052'); +INSERT INTO `yoshop_region` VALUES ('1750', '1740', '五峰', '五峰土家族自治县', '中国,湖北省,宜昌市,五峰土家族自治县', '3', 'wufeng', '0717', '443413', 'W', '110.6748', '30.19856'); +INSERT INTO `yoshop_region` VALUES ('1751', '1740', '宜都', '宜都市', '中国,湖北省,宜昌市,宜都市', '3', 'yidu', '0717', '443300', 'Y', '111.45025', '30.37807'); +INSERT INTO `yoshop_region` VALUES ('1752', '1740', '当阳', '当阳市', '中国,湖北省,宜昌市,当阳市', '3', 'dangyang', '0717', '444100', 'D', '111.78912', '30.8208'); +INSERT INTO `yoshop_region` VALUES ('1753', '1740', '枝江', '枝江市', '中国,湖北省,宜昌市,枝江市', '3', 'zhijiang', '0717', '443200', 'Z', '111.76855', '30.42612'); +INSERT INTO `yoshop_region` VALUES ('1754', '1709', '襄阳', '襄阳市', '中国,湖北省,襄阳市', '2', 'xiangyang', '0710', '441021', 'X', '112.144146', '32.042426'); +INSERT INTO `yoshop_region` VALUES ('1755', '1754', '襄城', '襄城区', '中国,湖北省,襄阳市,襄城区', '3', 'xiangcheng', '0710', '441021', 'X', '112.13372', '32.01017'); +INSERT INTO `yoshop_region` VALUES ('1756', '1754', '樊城', '樊城区', '中国,湖北省,襄阳市,樊城区', '3', 'fancheng', '0710', '441001', 'F', '112.13546', '32.04482'); +INSERT INTO `yoshop_region` VALUES ('1757', '1754', '襄州', '襄州区', '中国,湖北省,襄阳市,襄州区', '3', 'xiangzhou', '0710', '441100', 'X', '112.150327', '32.015088'); +INSERT INTO `yoshop_region` VALUES ('1758', '1754', '南漳', '南漳县', '中国,湖北省,襄阳市,南漳县', '3', 'nanzhang', '0710', '441500', 'N', '111.84603', '31.77653'); +INSERT INTO `yoshop_region` VALUES ('1759', '1754', '谷城', '谷城县', '中国,湖北省,襄阳市,谷城县', '3', 'gucheng', '0710', '441700', 'G', '111.65267', '32.26377'); +INSERT INTO `yoshop_region` VALUES ('1760', '1754', '保康', '保康县', '中国,湖北省,襄阳市,保康县', '3', 'baokang', '0710', '441600', 'B', '111.26138', '31.87874'); +INSERT INTO `yoshop_region` VALUES ('1761', '1754', '老河口', '老河口市', '中国,湖北省,襄阳市,老河口市', '3', 'laohekou', '0710', '441800', 'L', '111.67117', '32.38476'); +INSERT INTO `yoshop_region` VALUES ('1762', '1754', '枣阳', '枣阳市', '中国,湖北省,襄阳市,枣阳市', '3', 'zaoyang', '0710', '441200', 'Z', '112.77444', '32.13142'); +INSERT INTO `yoshop_region` VALUES ('1763', '1754', '宜城', '宜城市', '中国,湖北省,襄阳市,宜城市', '3', 'yicheng', '0710', '441400', 'Y', '112.25772', '31.71972'); +INSERT INTO `yoshop_region` VALUES ('1764', '1709', '鄂州', '鄂州市', '中国,湖北省,鄂州市', '2', 'ezhou', '0711', '436000', 'E', '114.890593', '30.396536'); +INSERT INTO `yoshop_region` VALUES ('1765', '1764', '梁子湖', '梁子湖区', '中国,湖北省,鄂州市,梁子湖区', '3', 'liangzihu', '0711', '436064', 'L', '114.68463', '30.10003'); +INSERT INTO `yoshop_region` VALUES ('1766', '1764', '华容', '华容区', '中国,湖北省,鄂州市,华容区', '3', 'huarong', '0711', '436030', 'H', '114.73568', '30.53328'); +INSERT INTO `yoshop_region` VALUES ('1767', '1764', '鄂城', '鄂城区', '中国,湖北省,鄂州市,鄂城区', '3', 'echeng', '0711', '436000', 'E', '114.89158', '30.40024'); +INSERT INTO `yoshop_region` VALUES ('1768', '1709', '荆门', '荆门市', '中国,湖北省,荆门市', '2', 'jingmen', '0724', '448000', 'J', '112.204251', '31.03542'); +INSERT INTO `yoshop_region` VALUES ('1769', '1768', '东宝', '东宝区', '中国,湖北省,荆门市,东宝区', '3', 'dongbao', '0724', '448004', 'D', '112.20147', '31.05192'); +INSERT INTO `yoshop_region` VALUES ('1770', '1768', '掇刀', '掇刀区', '中国,湖北省,荆门市,掇刀区', '3', 'duodao', '0724', '448124', 'D', '112.208', '30.97316'); +INSERT INTO `yoshop_region` VALUES ('1771', '1768', '京山', '京山县', '中国,湖北省,荆门市,京山县', '3', 'jingshan', '0724', '431800', 'J', '113.11074', '31.0224'); +INSERT INTO `yoshop_region` VALUES ('1772', '1768', '沙洋', '沙洋县', '中国,湖北省,荆门市,沙洋县', '3', 'shayang', '0724', '448200', 'S', '112.58853', '30.70916'); +INSERT INTO `yoshop_region` VALUES ('1773', '1768', '钟祥', '钟祥市', '中国,湖北省,荆门市,钟祥市', '3', 'zhongxiang', '0724', '431900', 'Z', '112.58932', '31.1678'); +INSERT INTO `yoshop_region` VALUES ('1774', '1709', '孝感', '孝感市', '中国,湖北省,孝感市', '2', 'xiaogan', '0712', '432100', 'X', '113.926655', '30.926423'); +INSERT INTO `yoshop_region` VALUES ('1775', '1774', '孝南', '孝南区', '中国,湖北省,孝感市,孝南区', '3', 'xiaonan', '0712', '432100', 'X', '113.91111', '30.9168'); +INSERT INTO `yoshop_region` VALUES ('1776', '1774', '孝昌', '孝昌县', '中国,湖北省,孝感市,孝昌县', '3', 'xiaochang', '0712', '432900', 'X', '113.99795', '31.25799'); +INSERT INTO `yoshop_region` VALUES ('1777', '1774', '大悟', '大悟县', '中国,湖北省,孝感市,大悟县', '3', 'dawu', '0712', '432800', 'D', '114.12564', '31.56176'); +INSERT INTO `yoshop_region` VALUES ('1778', '1774', '云梦', '云梦县', '中国,湖北省,孝感市,云梦县', '3', 'yunmeng', '0712', '432500', 'Y', '113.75289', '31.02093'); +INSERT INTO `yoshop_region` VALUES ('1779', '1774', '应城', '应城市', '中国,湖北省,孝感市,应城市', '3', 'yingcheng', '0712', '432400', 'Y', '113.57287', '30.92834'); +INSERT INTO `yoshop_region` VALUES ('1780', '1774', '安陆', '安陆市', '中国,湖北省,孝感市,安陆市', '3', 'anlu', '0712', '432600', 'A', '113.68557', '31.25693'); +INSERT INTO `yoshop_region` VALUES ('1781', '1774', '汉川', '汉川市', '中国,湖北省,孝感市,汉川市', '3', 'hanchuan', '0712', '432300', 'H', '113.83898', '30.66117'); +INSERT INTO `yoshop_region` VALUES ('1782', '1709', '荆州', '荆州市', '中国,湖北省,荆州市', '2', 'jingzhou', '0716', '434000', 'J', '112.23813', '30.326857'); +INSERT INTO `yoshop_region` VALUES ('1783', '1782', '沙市', '沙市区', '中国,湖北省,荆州市,沙市区', '3', 'shashi', '0716', '434000', 'S', '112.25543', '30.31107'); +INSERT INTO `yoshop_region` VALUES ('1784', '1782', '荆州', '荆州区', '中国,湖北省,荆州市,荆州区', '3', 'jingzhou', '0716', '434020', 'J', '112.19006', '30.35264'); +INSERT INTO `yoshop_region` VALUES ('1785', '1782', '公安', '公安县', '中国,湖北省,荆州市,公安县', '3', 'gong\'an', '0716', '434300', 'G', '112.23242', '30.05902'); +INSERT INTO `yoshop_region` VALUES ('1786', '1782', '监利', '监利县', '中国,湖北省,荆州市,监利县', '3', 'jianli', '0716', '433300', 'J', '112.89462', '29.81494'); +INSERT INTO `yoshop_region` VALUES ('1787', '1782', '江陵', '江陵县', '中国,湖北省,荆州市,江陵县', '3', 'jiangling', '0716', '434101', 'J', '112.42468', '30.04174'); +INSERT INTO `yoshop_region` VALUES ('1788', '1782', '石首', '石首市', '中国,湖北省,荆州市,石首市', '3', 'shishou', '0716', '434400', 'S', '112.42636', '29.72127'); +INSERT INTO `yoshop_region` VALUES ('1789', '1782', '洪湖', '洪湖市', '中国,湖北省,荆州市,洪湖市', '3', 'honghu', '0716', '433200', 'H', '113.47598', '29.827'); +INSERT INTO `yoshop_region` VALUES ('1790', '1782', '松滋', '松滋市', '中国,湖北省,荆州市,松滋市', '3', 'songzi', '0716', '434200', 'S', '111.76739', '30.16965'); +INSERT INTO `yoshop_region` VALUES ('1791', '1709', '黄冈', '黄冈市', '中国,湖北省,黄冈市', '2', 'huanggang', '0713', '438000', 'H', '114.879365', '30.447711'); +INSERT INTO `yoshop_region` VALUES ('1792', '1791', '黄州', '黄州区', '中国,湖北省,黄冈市,黄州区', '3', 'huangzhou', '0713', '438000', 'H', '114.88008', '30.43436'); +INSERT INTO `yoshop_region` VALUES ('1793', '1791', '团风', '团风县', '中国,湖北省,黄冈市,团风县', '3', 'tuanfeng', '0713', '438800', 'T', '114.87228', '30.64359'); +INSERT INTO `yoshop_region` VALUES ('1794', '1791', '红安', '红安县', '中国,湖北省,黄冈市,红安县', '3', 'hong\'an', '0713', '438401', 'H', '114.6224', '31.28668'); +INSERT INTO `yoshop_region` VALUES ('1795', '1791', '罗田', '罗田县', '中国,湖北省,黄冈市,罗田县', '3', 'luotian', '0713', '438600', 'L', '115.39971', '30.78255'); +INSERT INTO `yoshop_region` VALUES ('1796', '1791', '英山', '英山县', '中国,湖北省,黄冈市,英山县', '3', 'yingshan', '0713', '438700', 'Y', '115.68142', '30.73516'); +INSERT INTO `yoshop_region` VALUES ('1797', '1791', '浠水', '浠水县', '中国,湖北省,黄冈市,浠水县', '3', 'xishui', '0713', '438200', null, '115.26913', '30.45265'); +INSERT INTO `yoshop_region` VALUES ('1798', '1791', '蕲春', '蕲春县', '中国,湖北省,黄冈市,蕲春县', '3', 'qichun', '0713', '435300', null, '115.43615', '30.22613'); +INSERT INTO `yoshop_region` VALUES ('1799', '1791', '黄梅', '黄梅县', '中国,湖北省,黄冈市,黄梅县', '3', 'huangmei', '0713', '435500', 'H', '115.94427', '30.07033'); +INSERT INTO `yoshop_region` VALUES ('1800', '1791', '麻城', '麻城市', '中国,湖北省,黄冈市,麻城市', '3', 'macheng', '0713', '438300', 'M', '115.00988', '31.17228'); +INSERT INTO `yoshop_region` VALUES ('1801', '1791', '武穴', '武穴市', '中国,湖北省,黄冈市,武穴市', '3', 'wuxue', '0713', '435400', 'W', '115.55975', '29.84446'); +INSERT INTO `yoshop_region` VALUES ('1802', '1709', '咸宁', '咸宁市', '中国,湖北省,咸宁市', '2', 'xianning', '0715', '437000', 'X', '114.328963', '29.832798'); +INSERT INTO `yoshop_region` VALUES ('1803', '1802', '咸安', '咸安区', '中国,湖北省,咸宁市,咸安区', '3', 'xian\'an', '0715', '437000', 'X', '114.29872', '29.8529'); +INSERT INTO `yoshop_region` VALUES ('1804', '1802', '嘉鱼', '嘉鱼县', '中国,湖北省,咸宁市,嘉鱼县', '3', 'jiayu', '0715', '437200', 'J', '113.93927', '29.97054'); +INSERT INTO `yoshop_region` VALUES ('1805', '1802', '通城', '通城县', '中国,湖北省,咸宁市,通城县', '3', 'tongcheng', '0715', '437400', 'T', '113.81582', '29.24568'); +INSERT INTO `yoshop_region` VALUES ('1806', '1802', '崇阳', '崇阳县', '中国,湖北省,咸宁市,崇阳县', '3', 'chongyang', '0715', '437500', 'C', '114.03982', '29.55564'); +INSERT INTO `yoshop_region` VALUES ('1807', '1802', '通山', '通山县', '中国,湖北省,咸宁市,通山县', '3', 'tongshan', '0715', '437600', 'T', '114.48239', '29.6063'); +INSERT INTO `yoshop_region` VALUES ('1808', '1802', '赤壁', '赤壁市', '中国,湖北省,咸宁市,赤壁市', '3', 'chibi', '0715', '437300', 'C', '113.90039', '29.72454'); +INSERT INTO `yoshop_region` VALUES ('1809', '1709', '随州', '随州市', '中国,湖北省,随州市', '2', 'suizhou', '0722', '441300', 'S', '113.37377', '31.717497'); +INSERT INTO `yoshop_region` VALUES ('1810', '1809', '曾都', '曾都区', '中国,湖北省,随州市,曾都区', '3', 'zengdu', '0722', '441300', 'Z', '113.37128', '31.71614'); +INSERT INTO `yoshop_region` VALUES ('1811', '1809', '随县', '随县', '中国,湖北省,随州市,随县', '3', 'suixian', '0722', '441309', 'S', '113.82663', '31.6179'); +INSERT INTO `yoshop_region` VALUES ('1812', '1809', '广水', '广水市', '中国,湖北省,随州市,广水市', '3', 'guangshui', '0722', '432700', 'G', '113.82663', '31.6179'); +INSERT INTO `yoshop_region` VALUES ('1813', '1709', '恩施', '恩施土家族苗族自治州', '中国,湖北省,恩施土家族苗族自治州', '2', 'enshi', '0718', '445000', 'E', '109.48699', '30.283114'); +INSERT INTO `yoshop_region` VALUES ('1814', '1813', '恩施', '恩施市', '中国,湖北省,恩施土家族苗族自治州,恩施市', '3', 'enshi', '0718', '445000', 'E', '109.47942', '30.29502'); +INSERT INTO `yoshop_region` VALUES ('1815', '1813', '利川', '利川市', '中国,湖北省,恩施土家族苗族自治州,利川市', '3', 'lichuan', '0718', '445400', 'L', '108.93591', '30.29117'); +INSERT INTO `yoshop_region` VALUES ('1816', '1813', '建始', '建始县', '中国,湖北省,恩施土家族苗族自治州,建始县', '3', 'jianshi', '0718', '445300', 'J', '109.72207', '30.60209'); +INSERT INTO `yoshop_region` VALUES ('1817', '1813', '巴东', '巴东县', '中国,湖北省,恩施土家族苗族自治州,巴东县', '3', 'badong', '0718', '444300', 'B', '110.34066', '31.04233'); +INSERT INTO `yoshop_region` VALUES ('1818', '1813', '宣恩', '宣恩县', '中国,湖北省,恩施土家族苗族自治州,宣恩县', '3', 'xuanen', '0718', '445500', 'X', '109.49179', '29.98714'); +INSERT INTO `yoshop_region` VALUES ('1819', '1813', '咸丰', '咸丰县', '中国,湖北省,恩施土家族苗族自治州,咸丰县', '3', 'xianfeng', '0718', '445600', 'X', '109.152', '29.67983'); +INSERT INTO `yoshop_region` VALUES ('1820', '1813', '来凤', '来凤县', '中国,湖北省,恩施土家族苗族自治州,来凤县', '3', 'laifeng', '0718', '445700', 'L', '109.40716', '29.49373'); +INSERT INTO `yoshop_region` VALUES ('1821', '1813', '鹤峰', '鹤峰县', '中国,湖北省,恩施土家族苗族自治州,鹤峰县', '3', 'hefeng', '0718', '445800', 'H', '110.03091', '29.89072'); +INSERT INTO `yoshop_region` VALUES ('1822', '1709', ' ', '直辖县级', '中国,湖北省,直辖县级', '2', '', '', '', 'Z', '114.298572', '30.584355'); +INSERT INTO `yoshop_region` VALUES ('1823', '1822', '仙桃', '仙桃市', '中国,湖北省,直辖县级,仙桃市', '3', 'xiantao', '0728', '433000', 'X', '113.453974', '30.364953'); +INSERT INTO `yoshop_region` VALUES ('1824', '1822', '潜江', '潜江市', '中国,湖北省,直辖县级,潜江市', '3', 'qianjiang', '0728', '433100', 'Q', '112.896866', '30.421215'); +INSERT INTO `yoshop_region` VALUES ('1825', '1822', '天门', '天门市', '中国,湖北省,直辖县级,天门市', '3', 'tianmen', '0728', '431700', 'T', '113.165862', '30.653061'); +INSERT INTO `yoshop_region` VALUES ('1826', '1822', '神农架', '神农架林区', '中国,湖北省,直辖县级,神农架林区', '3', 'shennongjia', '0719', '442400', 'S', '110.671525', '31.744449'); +INSERT INTO `yoshop_region` VALUES ('1827', '0', '湖南', '湖南省', '中国,湖南省', '1', 'hunan', '', '', 'H', '112.982279', '28.19409'); +INSERT INTO `yoshop_region` VALUES ('1828', '1827', '长沙', '长沙市', '中国,湖南省,长沙市', '2', 'changsha', '0731', '410005', 'C', '112.982279', '28.19409'); +INSERT INTO `yoshop_region` VALUES ('1829', '1828', '芙蓉', '芙蓉区', '中国,湖南省,长沙市,芙蓉区', '3', 'furong', '0731', '410011', null, '113.03176', '28.1844'); +INSERT INTO `yoshop_region` VALUES ('1830', '1828', '天心', '天心区', '中国,湖南省,长沙市,天心区', '3', 'tianxin', '0731', '410004', 'T', '112.98991', '28.1127'); +INSERT INTO `yoshop_region` VALUES ('1831', '1828', '岳麓', '岳麓区', '中国,湖南省,长沙市,岳麓区', '3', 'yuelu', '0731', '410013', 'Y', '112.93133', '28.2351'); +INSERT INTO `yoshop_region` VALUES ('1832', '1828', '开福', '开福区', '中国,湖南省,长沙市,开福区', '3', 'kaifu', '0731', '410008', 'K', '112.98623', '28.25585'); +INSERT INTO `yoshop_region` VALUES ('1833', '1828', '雨花', '雨花区', '中国,湖南省,长沙市,雨花区', '3', 'yuhua', '0731', '410011', 'Y', '113.03567', '28.13541'); +INSERT INTO `yoshop_region` VALUES ('1834', '1828', '望城', '望城区', '中国,湖南省,长沙市,望城区', '3', 'wangcheng', '0731', '410200', 'W', '112.819549', '28.347458'); +INSERT INTO `yoshop_region` VALUES ('1835', '1828', '长沙', '长沙县', '中国,湖南省,长沙市,长沙县', '3', 'changsha', '0731', '410100', 'C', '113.08071', '28.24595'); +INSERT INTO `yoshop_region` VALUES ('1836', '1828', '宁乡', '宁乡县', '中国,湖南省,长沙市,宁乡县', '3', 'ningxiang', '0731', '410600', 'N', '112.55749', '28.25358'); +INSERT INTO `yoshop_region` VALUES ('1837', '1828', '浏阳', '浏阳市', '中国,湖南省,长沙市,浏阳市', '3', 'liuyang', '0731', '410300', null, '113.64312', '28.16375'); +INSERT INTO `yoshop_region` VALUES ('1838', '1827', '株洲', '株洲市', '中国,湖南省,株洲市', '2', 'zhuzhou', '0731', '412000', 'Z', '113.151737', '27.835806'); +INSERT INTO `yoshop_region` VALUES ('1839', '1838', '荷塘', '荷塘区', '中国,湖南省,株洲市,荷塘区', '3', 'hetang', '0731', '412000', 'H', '113.17315', '27.85569'); +INSERT INTO `yoshop_region` VALUES ('1840', '1838', '芦淞', '芦淞区', '中国,湖南省,株洲市,芦淞区', '3', 'lusong', '0731', '412000', 'L', '113.15562', '27.78525'); +INSERT INTO `yoshop_region` VALUES ('1841', '1838', '石峰', '石峰区', '中国,湖南省,株洲市,石峰区', '3', 'shifeng', '0731', '412005', 'S', '113.11776', '27.87552'); +INSERT INTO `yoshop_region` VALUES ('1842', '1838', '天元', '天元区', '中国,湖南省,株洲市,天元区', '3', 'tianyuan', '0731', '412007', 'T', '113.12335', '27.83103'); +INSERT INTO `yoshop_region` VALUES ('1843', '1838', '株洲', '株洲县', '中国,湖南省,株洲市,株洲县', '3', 'zhuzhou', '0731', '412100', 'Z', '113.14428', '27.69826'); +INSERT INTO `yoshop_region` VALUES ('1844', '1838', '攸县', '攸县', '中国,湖南省,株洲市,攸县', '3', 'youxian', '0731', '412300', null, '113.34365', '27.00352'); +INSERT INTO `yoshop_region` VALUES ('1845', '1838', '茶陵', '茶陵县', '中国,湖南省,株洲市,茶陵县', '3', 'chaling', '0731', '412400', 'C', '113.54364', '26.7915'); +INSERT INTO `yoshop_region` VALUES ('1846', '1838', '炎陵', '炎陵县', '中国,湖南省,株洲市,炎陵县', '3', 'yanling', '0731', '412500', 'Y', '113.77163', '26.48818'); +INSERT INTO `yoshop_region` VALUES ('1847', '1838', '醴陵', '醴陵市', '中国,湖南省,株洲市,醴陵市', '3', 'liling', '0731', '412200', null, '113.49704', '27.64615'); +INSERT INTO `yoshop_region` VALUES ('1848', '1827', '湘潭', '湘潭市', '中国,湖南省,湘潭市', '2', 'xiangtan', '0731', '411100', 'X', '112.925083', '27.846725'); +INSERT INTO `yoshop_region` VALUES ('1849', '1848', '雨湖', '雨湖区', '中国,湖南省,湘潭市,雨湖区', '3', 'yuhu', '0731', '411100', 'Y', '112.90399', '27.86859'); +INSERT INTO `yoshop_region` VALUES ('1850', '1848', '岳塘', '岳塘区', '中国,湖南省,湘潭市,岳塘区', '3', 'yuetang', '0731', '411101', 'Y', '112.9606', '27.85784'); +INSERT INTO `yoshop_region` VALUES ('1851', '1848', '湘潭', '湘潭县', '中国,湖南省,湘潭市,湘潭县', '3', 'xiangtan', '0731', '411228', 'X', '112.9508', '27.77893'); +INSERT INTO `yoshop_region` VALUES ('1852', '1848', '湘乡', '湘乡市', '中国,湖南省,湘潭市,湘乡市', '3', 'xiangxiang', '0731', '411400', 'X', '112.53512', '27.73543'); +INSERT INTO `yoshop_region` VALUES ('1853', '1848', '韶山', '韶山市', '中国,湖南省,湘潭市,韶山市', '3', 'shaoshan', '0731', '411300', 'S', '112.52655', '27.91503'); +INSERT INTO `yoshop_region` VALUES ('1854', '1827', '衡阳', '衡阳市', '中国,湖南省,衡阳市', '2', 'hengyang', '0734', '421001', 'H', '112.607693', '26.900358'); +INSERT INTO `yoshop_region` VALUES ('1855', '1854', '珠晖', '珠晖区', '中国,湖南省,衡阳市,珠晖区', '3', 'zhuhui', '0734', '421002', 'Z', '112.62054', '26.89361'); +INSERT INTO `yoshop_region` VALUES ('1856', '1854', '雁峰', '雁峰区', '中国,湖南省,衡阳市,雁峰区', '3', 'yanfeng', '0734', '421001', 'Y', '112.61654', '26.88866'); +INSERT INTO `yoshop_region` VALUES ('1857', '1854', '石鼓', '石鼓区', '中国,湖南省,衡阳市,石鼓区', '3', 'shigu', '0734', '421005', 'S', '112.61069', '26.90232'); +INSERT INTO `yoshop_region` VALUES ('1858', '1854', '蒸湘', '蒸湘区', '中国,湖南省,衡阳市,蒸湘区', '3', 'zhengxiang', '0734', '421001', 'Z', '112.6033', '26.89651'); +INSERT INTO `yoshop_region` VALUES ('1859', '1854', '南岳', '南岳区', '中国,湖南省,衡阳市,南岳区', '3', 'nanyue', '0734', '421900', 'N', '112.7384', '27.23262'); +INSERT INTO `yoshop_region` VALUES ('1860', '1854', '衡阳', '衡阳县', '中国,湖南省,衡阳市,衡阳县', '3', 'hengyang', '0734', '421200', 'H', '112.37088', '26.9706'); +INSERT INTO `yoshop_region` VALUES ('1861', '1854', '衡南', '衡南县', '中国,湖南省,衡阳市,衡南县', '3', 'hengnan', '0734', '421131', 'H', '112.67788', '26.73828'); +INSERT INTO `yoshop_region` VALUES ('1862', '1854', '衡山', '衡山县', '中国,湖南省,衡阳市,衡山县', '3', 'hengshan', '0734', '421300', 'H', '112.86776', '27.23134'); +INSERT INTO `yoshop_region` VALUES ('1863', '1854', '衡东', '衡东县', '中国,湖南省,衡阳市,衡东县', '3', 'hengdong', '0734', '421400', 'H', '112.94833', '27.08093'); +INSERT INTO `yoshop_region` VALUES ('1864', '1854', '祁东', '祁东县', '中国,湖南省,衡阳市,祁东县', '3', 'qidong', '0734', '421600', 'Q', '112.09039', '26.79964'); +INSERT INTO `yoshop_region` VALUES ('1865', '1854', '耒阳', '耒阳市', '中国,湖南省,衡阳市,耒阳市', '3', 'leiyang', '0734', '421800', null, '112.85998', '26.42132'); +INSERT INTO `yoshop_region` VALUES ('1866', '1854', '常宁', '常宁市', '中国,湖南省,衡阳市,常宁市', '3', 'changning', '0734', '421500', 'C', '112.4009', '26.40692'); +INSERT INTO `yoshop_region` VALUES ('1867', '1827', '邵阳', '邵阳市', '中国,湖南省,邵阳市', '2', 'shaoyang', '0739', '422000', 'S', '111.46923', '27.237842'); +INSERT INTO `yoshop_region` VALUES ('1868', '1867', '双清', '双清区', '中国,湖南省,邵阳市,双清区', '3', 'shuangqing', '0739', '422001', 'S', '111.49715', '27.23291'); +INSERT INTO `yoshop_region` VALUES ('1869', '1867', '大祥', '大祥区', '中国,湖南省,邵阳市,大祥区', '3', 'daxiang', '0739', '422000', 'D', '111.45412', '27.23332'); +INSERT INTO `yoshop_region` VALUES ('1870', '1867', '北塔', '北塔区', '中国,湖南省,邵阳市,北塔区', '3', 'beita', '0739', '422007', 'B', '111.45219', '27.24648'); +INSERT INTO `yoshop_region` VALUES ('1871', '1867', '邵东', '邵东县', '中国,湖南省,邵阳市,邵东县', '3', 'shaodong', '0739', '422800', 'S', '111.74441', '27.2584'); +INSERT INTO `yoshop_region` VALUES ('1872', '1867', '新邵', '新邵县', '中国,湖南省,邵阳市,新邵县', '3', 'xinshao', '0739', '422900', 'X', '111.46066', '27.32169'); +INSERT INTO `yoshop_region` VALUES ('1873', '1867', '邵阳', '邵阳县', '中国,湖南省,邵阳市,邵阳县', '3', 'shaoyang', '0739', '422100', 'S', '111.27459', '26.99143'); +INSERT INTO `yoshop_region` VALUES ('1874', '1867', '隆回', '隆回县', '中国,湖南省,邵阳市,隆回县', '3', 'longhui', '0739', '422200', 'L', '111.03216', '27.10937'); +INSERT INTO `yoshop_region` VALUES ('1875', '1867', '洞口', '洞口县', '中国,湖南省,邵阳市,洞口县', '3', 'dongkou', '0739', '422300', 'D', '110.57388', '27.05462'); +INSERT INTO `yoshop_region` VALUES ('1876', '1867', '绥宁', '绥宁县', '中国,湖南省,邵阳市,绥宁县', '3', 'suining', '0739', '422600', 'S', '110.15576', '26.58636'); +INSERT INTO `yoshop_region` VALUES ('1877', '1867', '新宁', '新宁县', '中国,湖南省,邵阳市,新宁县', '3', 'xinning', '0739', '422700', 'X', '110.85131', '26.42936'); +INSERT INTO `yoshop_region` VALUES ('1878', '1867', '城步', '城步苗族自治县', '中国,湖南省,邵阳市,城步苗族自治县', '3', 'chengbu', '0739', '422500', 'C', '110.3222', '26.39048'); +INSERT INTO `yoshop_region` VALUES ('1879', '1867', '武冈', '武冈市', '中国,湖南省,邵阳市,武冈市', '3', 'wugang', '0739', '422400', 'W', '110.63281', '26.72817'); +INSERT INTO `yoshop_region` VALUES ('1880', '1827', '岳阳', '岳阳市', '中国,湖南省,岳阳市', '2', 'yueyang', '0730', '414000', 'Y', '113.132855', '29.37029'); +INSERT INTO `yoshop_region` VALUES ('1881', '1880', '岳阳楼', '岳阳楼区', '中国,湖南省,岳阳市,岳阳楼区', '3', 'yueyanglou', '0730', '414000', 'Y', '113.12942', '29.3719'); +INSERT INTO `yoshop_region` VALUES ('1882', '1880', '云溪', '云溪区', '中国,湖南省,岳阳市,云溪区', '3', 'yunxi', '0730', '414009', 'Y', '113.27713', '29.47357'); +INSERT INTO `yoshop_region` VALUES ('1883', '1880', '君山', '君山区', '中国,湖南省,岳阳市,君山区', '3', 'junshan', '0730', '414005', 'J', '113.00439', '29.45941'); +INSERT INTO `yoshop_region` VALUES ('1884', '1880', '岳阳', '岳阳县', '中国,湖南省,岳阳市,岳阳县', '3', 'yueyang', '0730', '414100', 'Y', '113.11987', '29.14314'); +INSERT INTO `yoshop_region` VALUES ('1885', '1880', '华容', '华容县', '中国,湖南省,岳阳市,华容县', '3', 'huarong', '0730', '414200', 'H', '112.54089', '29.53019'); +INSERT INTO `yoshop_region` VALUES ('1886', '1880', '湘阴', '湘阴县', '中国,湖南省,岳阳市,湘阴县', '3', 'xiangyin', '0730', '414600', 'X', '112.90911', '28.68922'); +INSERT INTO `yoshop_region` VALUES ('1887', '1880', '平江', '平江县', '中国,湖南省,岳阳市,平江县', '3', 'pingjiang', '0730', '414500', 'P', '113.58105', '28.70664'); +INSERT INTO `yoshop_region` VALUES ('1888', '1880', '汨罗', '汨罗市', '中国,湖南省,岳阳市,汨罗市', '3', 'miluo', '0730', '414400', null, '113.06707', '28.80631'); +INSERT INTO `yoshop_region` VALUES ('1889', '1880', '临湘', '临湘市', '中国,湖南省,岳阳市,临湘市', '3', 'linxiang', '0730', '414300', 'L', '113.4501', '29.47701'); +INSERT INTO `yoshop_region` VALUES ('1890', '1827', '常德', '常德市', '中国,湖南省,常德市', '2', 'changde', '0736', '415000', 'C', '111.691347', '29.040225'); +INSERT INTO `yoshop_region` VALUES ('1891', '1890', '武陵', '武陵区', '中国,湖南省,常德市,武陵区', '3', 'wuling', '0736', '415000', 'W', '111.69791', '29.02876'); +INSERT INTO `yoshop_region` VALUES ('1892', '1890', '鼎城', '鼎城区', '中国,湖南省,常德市,鼎城区', '3', 'dingcheng', '0736', '415101', 'D', '111.68078', '29.01859'); +INSERT INTO `yoshop_region` VALUES ('1893', '1890', '安乡', '安乡县', '中国,湖南省,常德市,安乡县', '3', 'anxiang', '0736', '415600', 'A', '112.16732', '29.41326'); +INSERT INTO `yoshop_region` VALUES ('1894', '1890', '汉寿', '汉寿县', '中国,湖南省,常德市,汉寿县', '3', 'hanshou', '0736', '415900', 'H', '111.96691', '28.90299'); +INSERT INTO `yoshop_region` VALUES ('1895', '1890', '澧县', '澧县', '中国,湖南省,常德市,澧县', '3', 'lixian', '0736', '415500', null, '111.75866', '29.63317'); +INSERT INTO `yoshop_region` VALUES ('1896', '1890', '临澧', '临澧县', '中国,湖南省,常德市,临澧县', '3', 'linli', '0736', '415200', 'L', '111.65161', '29.44163'); +INSERT INTO `yoshop_region` VALUES ('1897', '1890', '桃源', '桃源县', '中国,湖南省,常德市,桃源县', '3', 'taoyuan', '0736', '415700', 'T', '111.48892', '28.90474'); +INSERT INTO `yoshop_region` VALUES ('1898', '1890', '石门', '石门县', '中国,湖南省,常德市,石门县', '3', 'shimen', '0736', '415300', 'S', '111.37966', '29.58424'); +INSERT INTO `yoshop_region` VALUES ('1899', '1890', '津市', '津市市', '中国,湖南省,常德市,津市市', '3', 'jinshi', '0736', '415400', 'J', '111.87756', '29.60563'); +INSERT INTO `yoshop_region` VALUES ('1900', '1827', '张家界', '张家界市', '中国,湖南省,张家界市', '2', 'zhangjiajie', '0744', '427000', 'Z', '110.479921', '29.127401'); +INSERT INTO `yoshop_region` VALUES ('1901', '1900', '永定', '永定区', '中国,湖南省,张家界市,永定区', '3', 'yongding', '0744', '427000', 'Y', '110.47464', '29.13387'); +INSERT INTO `yoshop_region` VALUES ('1902', '1900', '武陵源', '武陵源区', '中国,湖南省,张家界市,武陵源区', '3', 'wulingyuan', '0744', '427400', 'W', '110.55026', '29.34574'); +INSERT INTO `yoshop_region` VALUES ('1903', '1900', '慈利', '慈利县', '中国,湖南省,张家界市,慈利县', '3', 'cili', '0744', '427200', 'C', '111.13946', '29.42989'); +INSERT INTO `yoshop_region` VALUES ('1904', '1900', '桑植', '桑植县', '中国,湖南省,张家界市,桑植县', '3', 'sangzhi', '0744', '427100', 'S', '110.16308', '29.39815'); +INSERT INTO `yoshop_region` VALUES ('1905', '1827', '益阳', '益阳市', '中国,湖南省,益阳市', '2', 'yiyang', '0737', '413000', 'Y', '112.355042', '28.570066'); +INSERT INTO `yoshop_region` VALUES ('1906', '1905', '资阳', '资阳区', '中国,湖南省,益阳市,资阳区', '3', 'ziyang', '0737', '413001', 'Z', '112.32447', '28.59095'); +INSERT INTO `yoshop_region` VALUES ('1907', '1905', '赫山', '赫山区', '中国,湖南省,益阳市,赫山区', '3', 'heshan', '0737', '413002', 'H', '112.37265', '28.57425'); +INSERT INTO `yoshop_region` VALUES ('1908', '1905', '南县', '南县', '中国,湖南省,益阳市,南县', '3', 'nanxian', '0737', '413200', 'N', '112.3963', '29.36159'); +INSERT INTO `yoshop_region` VALUES ('1909', '1905', '桃江', '桃江县', '中国,湖南省,益阳市,桃江县', '3', 'taojiang', '0737', '413400', 'T', '112.1557', '28.51814'); +INSERT INTO `yoshop_region` VALUES ('1910', '1905', '安化', '安化县', '中国,湖南省,益阳市,安化县', '3', 'anhua', '0737', '413500', 'A', '111.21298', '28.37424'); +INSERT INTO `yoshop_region` VALUES ('1911', '1905', '沅江', '沅江市', '中国,湖南省,益阳市,沅江市', '3', 'yuanjiang', '0737', '413100', null, '112.35427', '28.84403'); +INSERT INTO `yoshop_region` VALUES ('1912', '1827', '郴州', '郴州市', '中国,湖南省,郴州市', '2', 'chenzhou', '0735', '423000', 'C', '113.032067', '25.793589'); +INSERT INTO `yoshop_region` VALUES ('1913', '1912', '北湖', '北湖区', '中国,湖南省,郴州市,北湖区', '3', 'beihu', '0735', '423000', 'B', '113.01103', '25.78405'); +INSERT INTO `yoshop_region` VALUES ('1914', '1912', '苏仙', '苏仙区', '中国,湖南省,郴州市,苏仙区', '3', 'suxian', '0735', '423000', 'S', '113.04226', '25.80045'); +INSERT INTO `yoshop_region` VALUES ('1915', '1912', '桂阳', '桂阳县', '中国,湖南省,郴州市,桂阳县', '3', 'guiyang', '0735', '424400', 'G', '112.73364', '25.75406'); +INSERT INTO `yoshop_region` VALUES ('1916', '1912', '宜章', '宜章县', '中国,湖南省,郴州市,宜章县', '3', 'yizhang', '0735', '424200', 'Y', '112.95147', '25.39931'); +INSERT INTO `yoshop_region` VALUES ('1917', '1912', '永兴', '永兴县', '中国,湖南省,郴州市,永兴县', '3', 'yongxing', '0735', '423300', 'Y', '113.11242', '26.12646'); +INSERT INTO `yoshop_region` VALUES ('1918', '1912', '嘉禾', '嘉禾县', '中国,湖南省,郴州市,嘉禾县', '3', 'jiahe', '0735', '424500', 'J', '112.36935', '25.58795'); +INSERT INTO `yoshop_region` VALUES ('1919', '1912', '临武', '临武县', '中国,湖南省,郴州市,临武县', '3', 'linwu', '0735', '424300', 'L', '112.56369', '25.27602'); +INSERT INTO `yoshop_region` VALUES ('1920', '1912', '汝城', '汝城县', '中国,湖南省,郴州市,汝城县', '3', 'rucheng', '0735', '424100', 'R', '113.68582', '25.55204'); +INSERT INTO `yoshop_region` VALUES ('1921', '1912', '桂东', '桂东县', '中国,湖南省,郴州市,桂东县', '3', 'guidong', '0735', '423500', 'G', '113.9468', '26.07987'); +INSERT INTO `yoshop_region` VALUES ('1922', '1912', '安仁', '安仁县', '中国,湖南省,郴州市,安仁县', '3', 'anren', '0735', '423600', 'A', '113.26944', '26.70931'); +INSERT INTO `yoshop_region` VALUES ('1923', '1912', '资兴', '资兴市', '中国,湖南省,郴州市,资兴市', '3', 'zixing', '0735', '423400', 'Z', '113.23724', '25.97668'); +INSERT INTO `yoshop_region` VALUES ('1924', '1827', '永州', '永州市', '中国,湖南省,永州市', '2', 'yongzhou', '0746', '425000', 'Y', '111.608019', '26.434516'); +INSERT INTO `yoshop_region` VALUES ('1925', '1924', '零陵', '零陵区', '中国,湖南省,永州市,零陵区', '3', 'lingling', '0746', '425100', 'L', '111.62103', '26.22109'); +INSERT INTO `yoshop_region` VALUES ('1926', '1924', '冷水滩', '冷水滩区', '中国,湖南省,永州市,冷水滩区', '3', 'lengshuitan', '0746', '425100', 'L', '111.59214', '26.46107'); +INSERT INTO `yoshop_region` VALUES ('1927', '1924', '祁阳', '祁阳县', '中国,湖南省,永州市,祁阳县', '3', 'qiyang', '0746', '426100', 'Q', '111.84011', '26.58009'); +INSERT INTO `yoshop_region` VALUES ('1928', '1924', '东安', '东安县', '中国,湖南省,永州市,东安县', '3', 'dong\'an', '0746', '425900', 'D', '111.3164', '26.39202'); +INSERT INTO `yoshop_region` VALUES ('1929', '1924', '双牌', '双牌县', '中国,湖南省,永州市,双牌县', '3', 'shuangpai', '0746', '425200', 'S', '111.65927', '25.95988'); +INSERT INTO `yoshop_region` VALUES ('1930', '1924', '道县', '道县', '中国,湖南省,永州市,道县', '3', 'daoxian', '0746', '425300', 'D', '111.60195', '25.52766'); +INSERT INTO `yoshop_region` VALUES ('1931', '1924', '江永', '江永县', '中国,湖南省,永州市,江永县', '3', 'jiangyong', '0746', '425400', 'J', '111.34082', '25.27233'); +INSERT INTO `yoshop_region` VALUES ('1932', '1924', '宁远', '宁远县', '中国,湖南省,永州市,宁远县', '3', 'ningyuan', '0746', '425600', 'N', '111.94625', '25.56913'); +INSERT INTO `yoshop_region` VALUES ('1933', '1924', '蓝山', '蓝山县', '中国,湖南省,永州市,蓝山县', '3', 'lanshan', '0746', '425800', 'L', '112.19363', '25.36794'); +INSERT INTO `yoshop_region` VALUES ('1934', '1924', '新田', '新田县', '中国,湖南省,永州市,新田县', '3', 'xintian', '0746', '425700', 'X', '112.22103', '25.9095'); +INSERT INTO `yoshop_region` VALUES ('1935', '1924', '江华', '江华瑶族自治县', '中国,湖南省,永州市,江华瑶族自治县', '3', 'jianghua', '0746', '425500', 'J', '111.58847', '25.1845'); +INSERT INTO `yoshop_region` VALUES ('1936', '1827', '怀化', '怀化市', '中国,湖南省,怀化市', '2', 'huaihua', '0745', '418000', 'H', '109.97824', '27.550082'); +INSERT INTO `yoshop_region` VALUES ('1937', '1936', '鹤城', '鹤城区', '中国,湖南省,怀化市,鹤城区', '3', 'hecheng', '0745', '418000', 'H', '109.96509', '27.54942'); +INSERT INTO `yoshop_region` VALUES ('1938', '1936', '中方', '中方县', '中国,湖南省,怀化市,中方县', '3', 'zhongfang', '0745', '418005', 'Z', '109.94497', '27.43988'); +INSERT INTO `yoshop_region` VALUES ('1939', '1936', '沅陵', '沅陵县', '中国,湖南省,怀化市,沅陵县', '3', 'yuanling', '0745', '419600', null, '110.39633', '28.45548'); +INSERT INTO `yoshop_region` VALUES ('1940', '1936', '辰溪', '辰溪县', '中国,湖南省,怀化市,辰溪县', '3', 'chenxi', '0745', '419500', 'C', '110.18942', '28.00406'); +INSERT INTO `yoshop_region` VALUES ('1941', '1936', '溆浦', '溆浦县', '中国,湖南省,怀化市,溆浦县', '3', 'xupu', '0745', '419300', null, '110.59384', '27.90836'); +INSERT INTO `yoshop_region` VALUES ('1942', '1936', '会同', '会同县', '中国,湖南省,怀化市,会同县', '3', 'huitong', '0745', '418300', 'H', '109.73568', '26.88716'); +INSERT INTO `yoshop_region` VALUES ('1943', '1936', '麻阳', '麻阳苗族自治县', '中国,湖南省,怀化市,麻阳苗族自治县', '3', 'mayang', '0745', '419400', 'M', '109.80194', '27.866'); +INSERT INTO `yoshop_region` VALUES ('1944', '1936', '新晃', '新晃侗族自治县', '中国,湖南省,怀化市,新晃侗族自治县', '3', 'xinhuang', '0745', '419200', 'X', '109.17166', '27.35937'); +INSERT INTO `yoshop_region` VALUES ('1945', '1936', '芷江', '芷江侗族自治县', '中国,湖南省,怀化市,芷江侗族自治县', '3', 'zhijiang', '0745', '419100', null, '109.6849', '27.44297'); +INSERT INTO `yoshop_region` VALUES ('1946', '1936', '靖州', '靖州苗族侗族自治县', '中国,湖南省,怀化市,靖州苗族侗族自治县', '3', 'jingzhou', '0745', '418400', 'J', '109.69821', '26.57651'); +INSERT INTO `yoshop_region` VALUES ('1947', '1936', '通道', '通道侗族自治县', '中国,湖南省,怀化市,通道侗族自治县', '3', 'tongdao', '0745', '418500', 'T', '109.78515', '26.1571'); +INSERT INTO `yoshop_region` VALUES ('1948', '1936', '洪江', '洪江市', '中国,湖南省,怀化市,洪江市', '3', 'hongjiang', '0745', '418100', 'H', '109.83651', '27.20922'); +INSERT INTO `yoshop_region` VALUES ('1949', '1827', '娄底', '娄底市', '中国,湖南省,娄底市', '2', 'loudi', '0738', '417000', 'L', '112.008497', '27.728136'); +INSERT INTO `yoshop_region` VALUES ('1950', '1949', '娄星', '娄星区', '中国,湖南省,娄底市,娄星区', '3', 'louxing', '0738', '417000', 'L', '112.00193', '27.72992'); +INSERT INTO `yoshop_region` VALUES ('1951', '1949', '双峰', '双峰县', '中国,湖南省,娄底市,双峰县', '3', 'shuangfeng', '0738', '417700', 'S', '112.19921', '27.45418'); +INSERT INTO `yoshop_region` VALUES ('1952', '1949', '新化', '新化县', '中国,湖南省,娄底市,新化县', '3', 'xinhua', '0738', '417600', 'X', '111.32739', '27.7266'); +INSERT INTO `yoshop_region` VALUES ('1953', '1949', '冷水江', '冷水江市', '中国,湖南省,娄底市,冷水江市', '3', 'lengshuijiang', '0738', '417500', 'L', '111.43554', '27.68147'); +INSERT INTO `yoshop_region` VALUES ('1954', '1949', '涟源', '涟源市', '中国,湖南省,娄底市,涟源市', '3', 'lianyuan', '0738', '417100', 'L', '111.67233', '27.68831'); +INSERT INTO `yoshop_region` VALUES ('1955', '1827', '湘西', '湘西土家族苗族自治州', '中国,湖南省,湘西土家族苗族自治州', '2', 'xiangxi', '0743', '416000', 'X', '109.739735', '28.314296'); +INSERT INTO `yoshop_region` VALUES ('1956', '1955', '吉首', '吉首市', '中国,湖南省,湘西土家族苗族自治州,吉首市', '3', 'jishou', '0743', '416000', 'J', '109.69799', '28.26247'); +INSERT INTO `yoshop_region` VALUES ('1957', '1955', '泸溪', '泸溪县', '中国,湖南省,湘西土家族苗族自治州,泸溪县', '3', 'luxi', '0743', '416100', null, '110.21682', '28.2205'); +INSERT INTO `yoshop_region` VALUES ('1958', '1955', '凤凰', '凤凰县', '中国,湖南省,湘西土家族苗族自治州,凤凰县', '3', 'fenghuang', '0743', '416200', 'F', '109.60156', '27.94822'); +INSERT INTO `yoshop_region` VALUES ('1959', '1955', '花垣', '花垣县', '中国,湖南省,湘西土家族苗族自治州,花垣县', '3', 'huayuan', '0743', '416400', 'H', '109.48217', '28.5721'); +INSERT INTO `yoshop_region` VALUES ('1960', '1955', '保靖', '保靖县', '中国,湖南省,湘西土家族苗族自治州,保靖县', '3', 'baojing', '0743', '416500', 'B', '109.66049', '28.69997'); +INSERT INTO `yoshop_region` VALUES ('1961', '1955', '古丈', '古丈县', '中国,湖南省,湘西土家族苗族自治州,古丈县', '3', 'guzhang', '0743', '416300', 'G', '109.94812', '28.61944'); +INSERT INTO `yoshop_region` VALUES ('1962', '1955', '永顺', '永顺县', '中国,湖南省,湘西土家族苗族自治州,永顺县', '3', 'yongshun', '0743', '416700', 'Y', '109.85266', '29.00103'); +INSERT INTO `yoshop_region` VALUES ('1963', '1955', '龙山', '龙山县', '中国,湖南省,湘西土家族苗族自治州,龙山县', '3', 'longshan', '0743', '416800', 'L', '109.4432', '29.45693'); +INSERT INTO `yoshop_region` VALUES ('1964', '0', '广东', '广东省', '中国,广东省', '1', 'guangdong', '', '', 'G', '113.280637', '23.125178'); +INSERT INTO `yoshop_region` VALUES ('1965', '1964', '广州', '广州市', '中国,广东省,广州市', '2', 'guangzhou', '020', '510032', 'G', '113.280637', '23.125178'); +INSERT INTO `yoshop_region` VALUES ('1966', '1965', '荔湾', '荔湾区', '中国,广东省,广州市,荔湾区', '3', 'liwan', '020', '510170', 'L', '113.2442', '23.12592'); +INSERT INTO `yoshop_region` VALUES ('1967', '1965', '越秀', '越秀区', '中国,广东省,广州市,越秀区', '3', 'yuexiu', '020', '510030', 'Y', '113.26683', '23.12897'); +INSERT INTO `yoshop_region` VALUES ('1968', '1965', '海珠', '海珠区', '中国,广东省,广州市,海珠区', '3', 'haizhu', '020', '510300', 'H', '113.26197', '23.10379'); +INSERT INTO `yoshop_region` VALUES ('1969', '1965', '天河', '天河区', '中国,广东省,广州市,天河区', '3', 'tianhe', '020', '510665', 'T', '113.36112', '23.12467'); +INSERT INTO `yoshop_region` VALUES ('1970', '1965', '白云', '白云区', '中国,广东省,广州市,白云区', '3', 'baiyun', '020', '510405', 'B', '113.27307', '23.15787'); +INSERT INTO `yoshop_region` VALUES ('1971', '1965', '黄埔', '黄埔区', '中国,广东省,广州市,黄埔区', '3', 'huangpu', '020', '510700', 'H', '113.45895', '23.10642'); +INSERT INTO `yoshop_region` VALUES ('1972', '1965', '番禺', '番禺区', '中国,广东省,广州市,番禺区', '3', 'panyu', '020', '511400', 'F', '113.38397', '22.93599'); +INSERT INTO `yoshop_region` VALUES ('1973', '1965', '花都', '花都区', '中国,广东省,广州市,花都区', '3', 'huadu', '020', '510800', 'H', '113.22033', '23.40358'); +INSERT INTO `yoshop_region` VALUES ('1974', '1965', '南沙', '南沙区', '中国,广东省,广州市,南沙区', '3', 'nansha', '020', '511458', 'N', '113.60845', '22.77144'); +INSERT INTO `yoshop_region` VALUES ('1975', '1965', '从化', '从化区', '中国,广东省,广州市,从化区', '3', 'conghua', '020', '510900', 'C', '113.587386', '23.545283'); +INSERT INTO `yoshop_region` VALUES ('1976', '1965', '增城', '增城区', '中国,广东省,广州市,增城区', '3', 'zengcheng', '020', '511300', 'Z', '113.829579', '23.290497'); +INSERT INTO `yoshop_region` VALUES ('1977', '1964', '韶关', '韶关市', '中国,广东省,韶关市', '2', 'shaoguan', '0751', '512002', 'S', '113.591544', '24.801322'); +INSERT INTO `yoshop_region` VALUES ('1978', '1977', '武江', '武江区', '中国,广东省,韶关市,武江区', '3', 'wujiang', '0751', '512026', 'W', '113.58767', '24.79264'); +INSERT INTO `yoshop_region` VALUES ('1979', '1977', '浈江', '浈江区', '中国,广东省,韶关市,浈江区', '3', 'zhenjiang', '0751', '512023', null, '113.61109', '24.80438'); +INSERT INTO `yoshop_region` VALUES ('1980', '1977', '曲江', '曲江区', '中国,广东省,韶关市,曲江区', '3', 'qujiang', '0751', '512101', 'Q', '113.60165', '24.67915'); +INSERT INTO `yoshop_region` VALUES ('1981', '1977', '始兴', '始兴县', '中国,广东省,韶关市,始兴县', '3', 'shixing', '0751', '512500', 'S', '114.06799', '24.94759'); +INSERT INTO `yoshop_region` VALUES ('1982', '1977', '仁化', '仁化县', '中国,广东省,韶关市,仁化县', '3', 'renhua', '0751', '512300', 'R', '113.74737', '25.08742'); +INSERT INTO `yoshop_region` VALUES ('1983', '1977', '翁源', '翁源县', '中国,广东省,韶关市,翁源县', '3', 'wengyuan', '0751', '512600', 'W', '114.13385', '24.3495'); +INSERT INTO `yoshop_region` VALUES ('1984', '1977', '乳源', '乳源瑶族自治县', '中国,广东省,韶关市,乳源瑶族自治县', '3', 'ruyuan', '0751', '512700', 'R', '113.27734', '24.77803'); +INSERT INTO `yoshop_region` VALUES ('1985', '1977', '新丰', '新丰县', '中国,广东省,韶关市,新丰县', '3', 'xinfeng', '0751', '511100', 'X', '114.20788', '24.05924'); +INSERT INTO `yoshop_region` VALUES ('1986', '1977', '乐昌', '乐昌市', '中国,广东省,韶关市,乐昌市', '3', 'lechang', '0751', '512200', 'L', '113.35653', '25.12799'); +INSERT INTO `yoshop_region` VALUES ('1987', '1977', '南雄', '南雄市', '中国,广东省,韶关市,南雄市', '3', 'nanxiong', '0751', '512400', 'N', '114.30966', '25.11706'); +INSERT INTO `yoshop_region` VALUES ('1988', '1964', '深圳', '深圳市', '中国,广东省,深圳市', '2', 'shenzhen', '0755', '518035', 'S', '114.085947', '22.547'); +INSERT INTO `yoshop_region` VALUES ('1989', '1988', '罗湖', '罗湖区', '中国,广东省,深圳市,罗湖区', '3', 'luohu', '0755', '518021', 'L', '114.13116', '22.54836'); +INSERT INTO `yoshop_region` VALUES ('1990', '1988', '福田', '福田区', '中国,广东省,深圳市,福田区', '3', 'futian', '0755', '518048', 'F', '114.05571', '22.52245'); +INSERT INTO `yoshop_region` VALUES ('1991', '1988', '南山', '南山区', '中国,广东省,深圳市,南山区', '3', 'nanshan', '0755', '518051', 'N', '113.93029', '22.53291'); +INSERT INTO `yoshop_region` VALUES ('1992', '1988', '宝安', '宝安区', '中国,广东省,深圳市,宝安区', '3', 'bao\'an', '0755', '518101', 'B', '113.88311', '22.55371'); +INSERT INTO `yoshop_region` VALUES ('1993', '1988', '龙岗', '龙岗区', '中国,广东省,深圳市,龙岗区', '3', 'longgang', '0755', '518172', 'L', '114.24771', '22.71986'); +INSERT INTO `yoshop_region` VALUES ('1994', '1988', '盐田', '盐田区', '中国,广东省,深圳市,盐田区', '3', 'yantian', '0755', '518081', 'Y', '114.23733', '22.5578'); +INSERT INTO `yoshop_region` VALUES ('1995', '1988', '光明新区', '光明新区', '中国,广东省,深圳市,光明新区', '3', 'guangmingxinqu', '0755', '518100', 'G', '113.896026', '22.777292'); +INSERT INTO `yoshop_region` VALUES ('1996', '1988', '坪山新区', '坪山新区', '中国,广东省,深圳市,坪山新区', '3', 'pingshanxinqu', '0755', '518000', 'P', '114.34637', '22.690529'); +INSERT INTO `yoshop_region` VALUES ('1997', '1988', '大鹏新区', '大鹏新区', '中国,广东省,深圳市,大鹏新区', '3', 'dapengxinqu', '0755', '518000', 'D', '114.479901', '22.587862'); +INSERT INTO `yoshop_region` VALUES ('1998', '1988', '龙华新区', '龙华新区', '中国,广东省,深圳市,龙华新区', '3', 'longhuaxinqu', '0755', '518100', 'L', '114.036585', '22.68695'); +INSERT INTO `yoshop_region` VALUES ('1999', '1964', '珠海', '珠海市', '中国,广东省,珠海市', '2', 'zhuhai', '0756', '519000', 'Z', '113.552724', '22.255899'); +INSERT INTO `yoshop_region` VALUES ('2000', '1999', '香洲', '香洲区', '中国,广东省,珠海市,香洲区', '3', 'xiangzhou', '0756', '519000', 'X', '113.5435', '22.26654'); +INSERT INTO `yoshop_region` VALUES ('2001', '1999', '斗门', '斗门区', '中国,广东省,珠海市,斗门区', '3', 'doumen', '0756', '519110', 'D', '113.29644', '22.20898'); +INSERT INTO `yoshop_region` VALUES ('2002', '1999', '金湾', '金湾区', '中国,广东省,珠海市,金湾区', '3', 'jinwan', '0756', '519040', 'J', '113.36361', '22.14691'); +INSERT INTO `yoshop_region` VALUES ('2003', '1964', '汕头', '汕头市', '中国,广东省,汕头市', '2', 'shantou', '0754', '515041', 'S', '116.708463', '23.37102'); +INSERT INTO `yoshop_region` VALUES ('2004', '2003', '龙湖', '龙湖区', '中国,广东省,汕头市,龙湖区', '3', 'longhu', '0754', '515041', 'L', '116.71641', '23.37166'); +INSERT INTO `yoshop_region` VALUES ('2005', '2003', '金平', '金平区', '中国,广东省,汕头市,金平区', '3', 'jinping', '0754', '515041', 'J', '116.70364', '23.36637'); +INSERT INTO `yoshop_region` VALUES ('2006', '2003', '濠江', '濠江区', '中国,广东省,汕头市,濠江区', '3', 'haojiang', '0754', '515071', null, '116.72659', '23.28588'); +INSERT INTO `yoshop_region` VALUES ('2007', '2003', '潮阳', '潮阳区', '中国,广东省,汕头市,潮阳区', '3', 'chaoyang', '0754', '515100', 'C', '116.6015', '23.26485'); +INSERT INTO `yoshop_region` VALUES ('2008', '2003', '潮南', '潮南区', '中国,广东省,汕头市,潮南区', '3', 'chaonan', '0754', '515144', 'C', '116.43188', '23.25'); +INSERT INTO `yoshop_region` VALUES ('2009', '2003', '澄海', '澄海区', '中国,广东省,汕头市,澄海区', '3', 'chenghai', '0754', '515800', 'C', '116.75589', '23.46728'); +INSERT INTO `yoshop_region` VALUES ('2010', '2003', '南澳', '南澳县', '中国,广东省,汕头市,南澳县', '3', 'nanao', '0754', '515900', 'N', '117.01889', '23.4223'); +INSERT INTO `yoshop_region` VALUES ('2011', '1964', '佛山', '佛山市', '中国,广东省,佛山市', '2', 'foshan', '0757', '528000', 'F', '113.122717', '23.028762'); +INSERT INTO `yoshop_region` VALUES ('2012', '2011', '禅城', '禅城区', '中国,广东省,佛山市,禅城区', '3', 'chancheng', '0757', '528000', null, '113.1228', '23.00842'); +INSERT INTO `yoshop_region` VALUES ('2013', '2011', '南海', '南海区', '中国,广东省,佛山市,南海区', '3', 'nanhai', '0757', '528251', 'N', '113.14299', '23.02877'); +INSERT INTO `yoshop_region` VALUES ('2014', '2011', '顺德', '顺德区', '中国,广东省,佛山市,顺德区', '3', 'shunde', '0757', '528300', 'S', '113.29394', '22.80452'); +INSERT INTO `yoshop_region` VALUES ('2015', '2011', '三水', '三水区', '中国,广东省,佛山市,三水区', '3', 'sanshui', '0757', '528133', 'S', '112.89703', '23.15564'); +INSERT INTO `yoshop_region` VALUES ('2016', '2011', '高明', '高明区', '中国,广东省,佛山市,高明区', '3', 'gaoming', '0757', '528500', 'G', '112.89254', '22.90022'); +INSERT INTO `yoshop_region` VALUES ('2017', '1964', '江门', '江门市', '中国,广东省,江门市', '2', 'jiangmen', '0750', '529000', 'J', '113.094942', '22.590431'); +INSERT INTO `yoshop_region` VALUES ('2018', '2017', '蓬江', '蓬江区', '中国,广东省,江门市,蓬江区', '3', 'pengjiang', '0750', '529000', 'P', '113.07849', '22.59515'); +INSERT INTO `yoshop_region` VALUES ('2019', '2017', '江海', '江海区', '中国,广东省,江门市,江海区', '3', 'jianghai', '0750', '529040', 'J', '113.11099', '22.56024'); +INSERT INTO `yoshop_region` VALUES ('2020', '2017', '新会', '新会区', '中国,广东省,江门市,新会区', '3', 'xinhui', '0750', '529100', 'X', '113.03225', '22.45876'); +INSERT INTO `yoshop_region` VALUES ('2021', '2017', '台山', '台山市', '中国,广东省,江门市,台山市', '3', 'taishan', '0750', '529200', 'T', '112.79382', '22.2515'); +INSERT INTO `yoshop_region` VALUES ('2022', '2017', '开平', '开平市', '中国,广东省,江门市,开平市', '3', 'kaiping', '0750', '529337', 'K', '112.69842', '22.37622'); +INSERT INTO `yoshop_region` VALUES ('2023', '2017', '鹤山', '鹤山市', '中国,广东省,江门市,鹤山市', '3', 'heshan', '0750', '529700', 'H', '112.96429', '22.76523'); +INSERT INTO `yoshop_region` VALUES ('2024', '2017', '恩平', '恩平市', '中国,广东省,江门市,恩平市', '3', 'enping', '0750', '529400', 'E', '112.30496', '22.18288'); +INSERT INTO `yoshop_region` VALUES ('2025', '1964', '湛江', '湛江市', '中国,广东省,湛江市', '2', 'zhanjiang', '0759', '524047', 'Z', '110.405529', '21.195338'); +INSERT INTO `yoshop_region` VALUES ('2026', '2025', '赤坎', '赤坎区', '中国,广东省,湛江市,赤坎区', '3', 'chikan', '0759', '524033', 'C', '110.36592', '21.26606'); +INSERT INTO `yoshop_region` VALUES ('2027', '2025', '霞山', '霞山区', '中国,广东省,湛江市,霞山区', '3', 'xiashan', '0759', '524011', 'X', '110.39822', '21.19181'); +INSERT INTO `yoshop_region` VALUES ('2028', '2025', '坡头', '坡头区', '中国,广东省,湛江市,坡头区', '3', 'potou', '0759', '524057', 'P', '110.45533', '21.24472'); +INSERT INTO `yoshop_region` VALUES ('2029', '2025', '麻章', '麻章区', '中国,广东省,湛江市,麻章区', '3', 'mazhang', '0759', '524094', 'M', '110.3342', '21.26333'); +INSERT INTO `yoshop_region` VALUES ('2030', '2025', '遂溪', '遂溪县', '中国,广东省,湛江市,遂溪县', '3', 'suixi', '0759', '524300', 'S', '110.25003', '21.37721'); +INSERT INTO `yoshop_region` VALUES ('2031', '2025', '徐闻', '徐闻县', '中国,广东省,湛江市,徐闻县', '3', 'xuwen', '0759', '524100', 'X', '110.17379', '20.32812'); +INSERT INTO `yoshop_region` VALUES ('2032', '2025', '廉江', '廉江市', '中国,广东省,湛江市,廉江市', '3', 'lianjiang', '0759', '524400', 'L', '110.28442', '21.60917'); +INSERT INTO `yoshop_region` VALUES ('2033', '2025', '雷州', '雷州市', '中国,广东省,湛江市,雷州市', '3', 'leizhou', '0759', '524200', 'L', '110.10092', '20.91428'); +INSERT INTO `yoshop_region` VALUES ('2034', '2025', '吴川', '吴川市', '中国,广东省,湛江市,吴川市', '3', 'wuchuan', '0759', '524500', 'W', '110.77703', '21.44584'); +INSERT INTO `yoshop_region` VALUES ('2035', '1964', '茂名', '茂名市', '中国,广东省,茂名市', '2', 'maoming', '0668', '525000', 'M', '110.919229', '21.659751'); +INSERT INTO `yoshop_region` VALUES ('2036', '2035', '茂南', '茂南区', '中国,广东省,茂名市,茂南区', '3', 'maonan', '0668', '525000', 'M', '110.9187', '21.64103'); +INSERT INTO `yoshop_region` VALUES ('2037', '2035', '电白', '电白区', '中国,广东省,茂名市,电白区', '3', 'dianbai', '0668', '525400', 'D', '111.007264', '21.507219'); +INSERT INTO `yoshop_region` VALUES ('2038', '2035', '高州', '高州市', '中国,广东省,茂名市,高州市', '3', 'gaozhou', '0668', '525200', 'G', '110.85519', '21.92057'); +INSERT INTO `yoshop_region` VALUES ('2039', '2035', '化州', '化州市', '中国,广东省,茂名市,化州市', '3', 'huazhou', '0668', '525100', 'H', '110.63949', '21.66394'); +INSERT INTO `yoshop_region` VALUES ('2040', '2035', '信宜', '信宜市', '中国,广东省,茂名市,信宜市', '3', 'xinyi', '0668', '525300', 'X', '110.94647', '22.35351'); +INSERT INTO `yoshop_region` VALUES ('2041', '1964', '肇庆', '肇庆市', '中国,广东省,肇庆市', '2', 'zhaoqing', '0758', '526040', 'Z', '112.472529', '23.051546'); +INSERT INTO `yoshop_region` VALUES ('2042', '2041', '端州', '端州区', '中国,广东省,肇庆市,端州区', '3', 'duanzhou', '0758', '526060', 'D', '112.48495', '23.0519'); +INSERT INTO `yoshop_region` VALUES ('2043', '2041', '鼎湖', '鼎湖区', '中国,广东省,肇庆市,鼎湖区', '3', 'dinghu', '0758', '526070', 'D', '112.56643', '23.15846'); +INSERT INTO `yoshop_region` VALUES ('2044', '2041', '广宁', '广宁县', '中国,广东省,肇庆市,广宁县', '3', 'guangning', '0758', '526300', 'G', '112.44064', '23.6346'); +INSERT INTO `yoshop_region` VALUES ('2045', '2041', '怀集', '怀集县', '中国,广东省,肇庆市,怀集县', '3', 'huaiji', '0758', '526400', 'H', '112.18396', '23.90918'); +INSERT INTO `yoshop_region` VALUES ('2046', '2041', '封开', '封开县', '中国,广东省,肇庆市,封开县', '3', 'fengkai', '0758', '526500', 'F', '111.50332', '23.43571'); +INSERT INTO `yoshop_region` VALUES ('2047', '2041', '德庆', '德庆县', '中国,广东省,肇庆市,德庆县', '3', 'deqing', '0758', '526600', 'D', '111.78555', '23.14371'); +INSERT INTO `yoshop_region` VALUES ('2048', '2041', '高要', '高要市', '中国,广东省,肇庆市,高要市', '3', 'gaoyao', '0758', '526100', 'G', '112.45834', '23.02577'); +INSERT INTO `yoshop_region` VALUES ('2049', '2041', '四会', '四会市', '中国,广东省,肇庆市,四会市', '3', 'sihui', '0758', '526200', 'S', '112.73416', '23.32686'); +INSERT INTO `yoshop_region` VALUES ('2050', '1964', '惠州', '惠州市', '中国,广东省,惠州市', '2', 'huizhou', '0752', '516000', 'H', '114.412599', '23.079404'); +INSERT INTO `yoshop_region` VALUES ('2051', '2050', '惠城', '惠城区', '中国,广东省,惠州市,惠城区', '3', 'huicheng', '0752', '516008', 'H', '114.3828', '23.08377'); +INSERT INTO `yoshop_region` VALUES ('2052', '2050', '惠阳', '惠阳区', '中国,广东省,惠州市,惠阳区', '3', 'huiyang', '0752', '516211', 'H', '114.45639', '22.78845'); +INSERT INTO `yoshop_region` VALUES ('2053', '2050', '博罗', '博罗县', '中国,广东省,惠州市,博罗县', '3', 'boluo', '0752', '516100', 'B', '114.28964', '23.17307'); +INSERT INTO `yoshop_region` VALUES ('2054', '2050', '惠东', '惠东县', '中国,广东省,惠州市,惠东县', '3', 'huidong', '0752', '516300', 'H', '114.72009', '22.98484'); +INSERT INTO `yoshop_region` VALUES ('2055', '2050', '龙门', '龙门县', '中国,广东省,惠州市,龙门县', '3', 'longmen', '0752', '516800', 'L', '114.25479', '23.72758'); +INSERT INTO `yoshop_region` VALUES ('2056', '1964', '梅州', '梅州市', '中国,广东省,梅州市', '2', 'meizhou', '0753', '514021', 'M', '116.117582', '24.299112'); +INSERT INTO `yoshop_region` VALUES ('2057', '2056', '梅江', '梅江区', '中国,广东省,梅州市,梅江区', '3', 'meijiang', '0753', '514000', 'M', '116.11663', '24.31062'); +INSERT INTO `yoshop_region` VALUES ('2058', '2056', '梅县', '梅县区', '中国,广东省,梅州市,梅县区', '3', 'meixian', '0753', '514787', 'M', '116.097753', '24.286739'); +INSERT INTO `yoshop_region` VALUES ('2059', '2056', '大埔', '大埔县', '中国,广东省,梅州市,大埔县', '3', 'dabu', '0753', '514200', 'D', '116.69662', '24.35325'); +INSERT INTO `yoshop_region` VALUES ('2060', '2056', '丰顺', '丰顺县', '中国,广东省,梅州市,丰顺县', '3', 'fengshun', '0753', '514300', 'F', '116.18219', '23.74094'); +INSERT INTO `yoshop_region` VALUES ('2061', '2056', '五华', '五华县', '中国,广东省,梅州市,五华县', '3', 'wuhua', '0753', '514400', 'W', '115.77893', '23.92417'); +INSERT INTO `yoshop_region` VALUES ('2062', '2056', '平远', '平远县', '中国,广东省,梅州市,平远县', '3', 'pingyuan', '0753', '514600', 'P', '115.89556', '24.57116'); +INSERT INTO `yoshop_region` VALUES ('2063', '2056', '蕉岭', '蕉岭县', '中国,广东省,梅州市,蕉岭县', '3', 'jiaoling', '0753', '514100', 'J', '116.17089', '24.65732'); +INSERT INTO `yoshop_region` VALUES ('2064', '2056', '兴宁', '兴宁市', '中国,广东省,梅州市,兴宁市', '3', 'xingning', '0753', '514500', 'X', '115.73141', '24.14001'); +INSERT INTO `yoshop_region` VALUES ('2065', '1964', '汕尾', '汕尾市', '中国,广东省,汕尾市', '2', 'shanwei', '0660', '516600', 'S', '115.364238', '22.774485'); +INSERT INTO `yoshop_region` VALUES ('2066', '2065', '城区', '城区', '中国,广东省,汕尾市,城区', '3', 'chengqu', '0660', '516600', 'C', '115.36503', '22.7789'); +INSERT INTO `yoshop_region` VALUES ('2067', '2065', '海丰', '海丰县', '中国,广东省,汕尾市,海丰县', '3', 'haifeng', '0660', '516400', 'H', '115.32336', '22.96653'); +INSERT INTO `yoshop_region` VALUES ('2068', '2065', '陆河', '陆河县', '中国,广东省,汕尾市,陆河县', '3', 'luhe', '0660', '516700', 'L', '115.65597', '23.30365'); +INSERT INTO `yoshop_region` VALUES ('2069', '2065', '陆丰', '陆丰市', '中国,广东省,汕尾市,陆丰市', '3', 'lufeng', '0660', '516500', 'L', '115.64813', '22.94335'); +INSERT INTO `yoshop_region` VALUES ('2070', '1964', '河源', '河源市', '中国,广东省,河源市', '2', 'heyuan', '0762', '517000', 'H', '114.697802', '23.746266'); +INSERT INTO `yoshop_region` VALUES ('2071', '2070', '源城', '源城区', '中国,广东省,河源市,源城区', '3', 'yuancheng', '0762', '517000', 'Y', '114.70242', '23.7341'); +INSERT INTO `yoshop_region` VALUES ('2072', '2070', '紫金', '紫金县', '中国,广东省,河源市,紫金县', '3', 'zijin', '0762', '517400', 'Z', '115.18365', '23.63867'); +INSERT INTO `yoshop_region` VALUES ('2073', '2070', '龙川', '龙川县', '中国,广东省,河源市,龙川县', '3', 'longchuan', '0762', '517300', 'L', '115.26025', '24.10142'); +INSERT INTO `yoshop_region` VALUES ('2074', '2070', '连平', '连平县', '中国,广东省,河源市,连平县', '3', 'lianping', '0762', '517100', 'L', '114.49026', '24.37156'); +INSERT INTO `yoshop_region` VALUES ('2075', '2070', '和平', '和平县', '中国,广东省,河源市,和平县', '3', 'heping', '0762', '517200', 'H', '114.93841', '24.44319'); +INSERT INTO `yoshop_region` VALUES ('2076', '2070', '东源', '东源县', '中国,广东省,河源市,东源县', '3', 'dongyuan', '0762', '517583', 'D', '114.74633', '23.78835'); +INSERT INTO `yoshop_region` VALUES ('2077', '1964', '阳江', '阳江市', '中国,广东省,阳江市', '2', 'yangjiang', '0662', '529500', 'Y', '111.975107', '21.859222'); +INSERT INTO `yoshop_region` VALUES ('2078', '2077', '江城', '江城区', '中国,广东省,阳江市,江城区', '3', 'jiangcheng', '0662', '529500', 'J', '111.95488', '21.86193'); +INSERT INTO `yoshop_region` VALUES ('2079', '2077', '阳东', '阳东区', '中国,广东省,阳江市,阳东区', '3', 'yangdong', '0662', '529900', 'Y', '112.01467', '21.87398'); +INSERT INTO `yoshop_region` VALUES ('2080', '2077', '阳西', '阳西县', '中国,广东省,阳江市,阳西县', '3', 'yangxi', '0662', '529800', 'Y', '111.61785', '21.75234'); +INSERT INTO `yoshop_region` VALUES ('2081', '2077', '阳春', '阳春市', '中国,广东省,阳江市,阳春市', '3', 'yangchun', '0662', '529600', 'Y', '111.78854', '22.17232'); +INSERT INTO `yoshop_region` VALUES ('2082', '1964', '清远', '清远市', '中国,广东省,清远市', '2', 'qingyuan', '0763', '511500', 'Q', '113.036779', '23.704188'); +INSERT INTO `yoshop_region` VALUES ('2083', '2082', '清城', '清城区', '中国,广东省,清远市,清城区', '3', 'qingcheng', '0763', '511515', 'Q', '113.06265', '23.69784'); +INSERT INTO `yoshop_region` VALUES ('2084', '2082', '清新', '清新区', '中国,广东省,清远市,清新区', '3', 'qingxin', '0763', '511810', 'Q', '113.015203', '23.736949'); +INSERT INTO `yoshop_region` VALUES ('2085', '2082', '佛冈', '佛冈县', '中国,广东省,清远市,佛冈县', '3', 'fogang', '0763', '511600', 'F', '113.53286', '23.87231'); +INSERT INTO `yoshop_region` VALUES ('2086', '2082', '阳山', '阳山县', '中国,广东省,清远市,阳山县', '3', 'yangshan', '0763', '513100', 'Y', '112.64129', '24.46516'); +INSERT INTO `yoshop_region` VALUES ('2087', '2082', '连山', '连山壮族瑶族自治县', '中国,广东省,清远市,连山壮族瑶族自治县', '3', 'lianshan', '0763', '513200', 'L', '112.0802', '24.56807'); +INSERT INTO `yoshop_region` VALUES ('2088', '2082', '连南', '连南瑶族自治县', '中国,广东省,清远市,连南瑶族自治县', '3', 'liannan', '0763', '513300', 'L', '112.28842', '24.71726'); +INSERT INTO `yoshop_region` VALUES ('2089', '2082', '英德', '英德市', '中国,广东省,清远市,英德市', '3', 'yingde', '0763', '513000', 'Y', '113.415', '24.18571'); +INSERT INTO `yoshop_region` VALUES ('2090', '2082', '连州', '连州市', '中国,广东省,清远市,连州市', '3', 'lianzhou', '0763', '513400', 'L', '112.38153', '24.77913'); +INSERT INTO `yoshop_region` VALUES ('2091', '1964', '东莞', '东莞市', '中国,广东省,东莞市', '2', 'dongguan', '0769', '523888', 'D', '113.760234', '23.048884'); +INSERT INTO `yoshop_region` VALUES ('2092', '2091', '莞城', '莞城区', '中国,广东省,东莞市,莞城区', '3', 'guancheng', '0769', '523128', null, '113.751043', '23.053412'); +INSERT INTO `yoshop_region` VALUES ('2093', '2091', '南城', '南城区', '中国,广东省,东莞市,南城区', '3', 'nancheng', '0769', '523617', 'N', '113.752125', '23.02018'); +INSERT INTO `yoshop_region` VALUES ('2094', '2091', '万江', '万江区', '中国,广东省,东莞市,万江区', '3', 'wanjiang', '0769', '523039', 'W', '113.739053', '23.043842'); +INSERT INTO `yoshop_region` VALUES ('2095', '2091', '石碣', '石碣镇', '中国,广东省,东莞市,石碣镇', '3', 'shijie', '0769', '523290', 'S', '113.80217', '23.09899'); +INSERT INTO `yoshop_region` VALUES ('2096', '2091', '石龙', '石龙镇', '中国,广东省,东莞市,石龙镇', '3', 'shilong', '0769', '523326', 'S', '113.876381', '23.107444'); +INSERT INTO `yoshop_region` VALUES ('2097', '2091', '茶山', '茶山镇', '中国,广东省,东莞市,茶山镇', '3', 'chashan', '0769', '523380', 'C', '113.883526', '23.062375'); +INSERT INTO `yoshop_region` VALUES ('2098', '2091', '石排', '石排镇', '中国,广东省,东莞市,石排镇', '3', 'shipai', '0769', '523346', 'S', '113.919859', '23.0863'); +INSERT INTO `yoshop_region` VALUES ('2099', '2091', '企石', '企石镇', '中国,广东省,东莞市,企石镇', '3', 'qishi', '0769', '523507', 'Q', '114.013233', '23.066044'); +INSERT INTO `yoshop_region` VALUES ('2100', '2091', '横沥', '横沥镇', '中国,广东省,东莞市,横沥镇', '3', 'hengli', '0769', '523471', 'H', '113.957436', '23.025732'); +INSERT INTO `yoshop_region` VALUES ('2101', '2091', '桥头', '桥头镇', '中国,广东省,东莞市,桥头镇', '3', 'qiaotou', '0769', '523520', 'Q', '114.01385', '22.939727'); +INSERT INTO `yoshop_region` VALUES ('2102', '2091', '谢岗', '谢岗镇', '中国,广东省,东莞市,谢岗镇', '3', 'xiegang', '0769', '523592', 'X', '114.141396', '22.959664'); +INSERT INTO `yoshop_region` VALUES ('2103', '2091', '东坑', '东坑镇', '中国,广东省,东莞市,东坑镇', '3', 'dongkeng', '0769', '523451', 'D', '113.939835', '22.992804'); +INSERT INTO `yoshop_region` VALUES ('2104', '2091', '常平', '常平镇', '中国,广东省,东莞市,常平镇', '3', 'changping', '0769', '523560', 'C', '114.029627', '23.016116'); +INSERT INTO `yoshop_region` VALUES ('2105', '2091', '寮步', '寮步镇', '中国,广东省,东莞市,寮步镇', '3', 'liaobu', '0769', '523411', null, '113.884745', '22.991738'); +INSERT INTO `yoshop_region` VALUES ('2106', '2091', '大朗', '大朗镇', '中国,广东省,东莞市,大朗镇', '3', 'dalang', '0769', '523770', 'D', '113.9271', '22.965748'); +INSERT INTO `yoshop_region` VALUES ('2107', '2091', '麻涌', '麻涌镇', '中国,广东省,东莞市,麻涌镇', '3', 'machong', '0769', '523143', 'M', '113.546177', '23.045315'); +INSERT INTO `yoshop_region` VALUES ('2108', '2091', '中堂', '中堂镇', '中国,广东省,东莞市,中堂镇', '3', 'zhongtang', '0769', '523233', 'Z', '113.654422', '23.090164'); +INSERT INTO `yoshop_region` VALUES ('2109', '2091', '高埗', '高埗镇', '中国,广东省,东莞市,高埗镇', '3', 'gaobu', '0769', '523282', null, '113.735917', '23.068415'); +INSERT INTO `yoshop_region` VALUES ('2110', '2091', '樟木头', '樟木头镇', '中国,广东省,东莞市,樟木头镇', '3', 'zhangmutou', '0769', '523619', 'Z', '114.066298', '22.956682'); +INSERT INTO `yoshop_region` VALUES ('2111', '2091', '大岭山', '大岭山镇', '中国,广东省,东莞市,大岭山镇', '3', 'dalingshan', '0769', '523835', 'D', '113.782955', '22.885366'); +INSERT INTO `yoshop_region` VALUES ('2112', '2091', '望牛墩', '望牛墩镇', '中国,广东省,东莞市,望牛墩镇', '3', 'wangniudun', '0769', '523203', 'W', '113.658847', '23.055018'); +INSERT INTO `yoshop_region` VALUES ('2113', '2091', '黄江', '黄江镇', '中国,广东省,东莞市,黄江镇', '3', 'huangjiang', '0769', '523755', 'H', '113.992635', '22.877536'); +INSERT INTO `yoshop_region` VALUES ('2114', '2091', '洪梅', '洪梅镇', '中国,广东省,东莞市,洪梅镇', '3', 'hongmei', '0769', '523163', 'H', '113.613081', '22.992675'); +INSERT INTO `yoshop_region` VALUES ('2115', '2091', '清溪', '清溪镇', '中国,广东省,东莞市,清溪镇', '3', 'qingxi', '0769', '523660', 'Q', '114.155796', '22.844456'); +INSERT INTO `yoshop_region` VALUES ('2116', '2091', '沙田', '沙田镇', '中国,广东省,东莞市,沙田镇', '3', 'shatian', '0769', '523988', 'S', '113.760234', '23.048884'); +INSERT INTO `yoshop_region` VALUES ('2117', '2091', '道滘', '道滘镇', '中国,广东省,东莞市,道滘镇', '3', 'daojiao', '0769', '523171', null, '113.760234', '23.048884'); +INSERT INTO `yoshop_region` VALUES ('2118', '2091', '塘厦', '塘厦镇', '中国,广东省,东莞市,塘厦镇', '3', 'tangxia', '0769', '523713', 'T', '114.10765', '22.822862'); +INSERT INTO `yoshop_region` VALUES ('2119', '2091', '虎门', '虎门镇', '中国,广东省,东莞市,虎门镇', '3', 'humen', '0769', '523932', 'H', '113.71118', '22.82615'); +INSERT INTO `yoshop_region` VALUES ('2120', '2091', '厚街', '厚街镇', '中国,广东省,东莞市,厚街镇', '3', 'houjie', '0769', '523960', 'H', '113.67301', '22.940815'); +INSERT INTO `yoshop_region` VALUES ('2121', '2091', '凤岗', '凤岗镇', '中国,广东省,东莞市,凤岗镇', '3', 'fenggang', '0769', '523690', 'F', '114.141194', '22.744598'); +INSERT INTO `yoshop_region` VALUES ('2122', '2091', '长安', '长安镇', '中国,广东省,东莞市,长安镇', '3', 'chang\'an', '0769', '523850', 'C', '113.803939', '22.816644'); +INSERT INTO `yoshop_region` VALUES ('2123', '1964', '中山', '中山市', '中国,广东省,中山市', '2', 'zhongshan', '0760', '528403', 'Z', '113.382391', '22.521113'); +INSERT INTO `yoshop_region` VALUES ('2124', '2123', '石岐', '石岐区', '中国,广东省,中山市,石岐区', '3', 'shiqi', '0760', '528400', 'S', '113.378835', '22.52522'); +INSERT INTO `yoshop_region` VALUES ('2125', '2123', '南区', '南区', '中国,广东省,中山市,南区', '3', 'nanqu', '0760', '528400', 'N', '113.355896', '22.486568'); +INSERT INTO `yoshop_region` VALUES ('2126', '2123', '五桂山', '五桂山区', '中国,广东省,中山市,五桂山区', '3', 'wuguishan', '0760', '528458', 'W', '113.41079', '22.51968'); +INSERT INTO `yoshop_region` VALUES ('2127', '2123', '火炬', '火炬开发区', '中国,广东省,中山市,火炬开发区', '3', 'huoju', '0760', '528437', 'H', '113.480523', '22.566082'); +INSERT INTO `yoshop_region` VALUES ('2128', '2123', '黄圃', '黄圃镇', '中国,广东省,中山市,黄圃镇', '3', 'huangpu', '0760', '528429', 'H', '113.342359', '22.715116'); +INSERT INTO `yoshop_region` VALUES ('2129', '2123', '南头', '南头镇', '中国,广东省,中山市,南头镇', '3', 'nantou', '0760', '528421', 'N', '113.296358', '22.713907'); +INSERT INTO `yoshop_region` VALUES ('2130', '2123', '东凤', '东凤镇', '中国,广东省,中山市,东凤镇', '3', 'dongfeng', '0760', '528425', 'D', '113.26114', '22.68775'); +INSERT INTO `yoshop_region` VALUES ('2131', '2123', '阜沙', '阜沙镇', '中国,广东省,中山市,阜沙镇', '3', 'fusha', '0760', '528434', 'F', '113.353024', '22.666364'); +INSERT INTO `yoshop_region` VALUES ('2132', '2123', '小榄', '小榄镇', '中国,广东省,中山市,小榄镇', '3', 'xiaolan', '0760', '528415', 'X', '113.244235', '22.666951'); +INSERT INTO `yoshop_region` VALUES ('2133', '2123', '东升', '东升镇', '中国,广东省,中山市,东升镇', '3', 'dongsheng', '0760', '528400', 'D', '113.296298', '22.614003'); +INSERT INTO `yoshop_region` VALUES ('2134', '2123', '古镇', '古镇镇', '中国,广东省,中山市,古镇镇', '3', 'guzhen', '0760', '528422', 'G', '113.179745', '22.611019'); +INSERT INTO `yoshop_region` VALUES ('2135', '2123', '横栏', '横栏镇', '中国,广东省,中山市,横栏镇', '3', 'henglan', '0760', '528478', 'H', '113.265845', '22.523202'); +INSERT INTO `yoshop_region` VALUES ('2136', '2123', '三角', '三角镇', '中国,广东省,中山市,三角镇', '3', 'sanjiao', '0760', '528422', 'S', '113.423624', '22.677033'); +INSERT INTO `yoshop_region` VALUES ('2137', '2123', '民众', '民众镇', '中国,广东省,中山市,民众镇', '3', 'minzhong', '0760', '528441', 'M', '113.486025', '22.623468'); +INSERT INTO `yoshop_region` VALUES ('2138', '2123', '南朗', '南朗镇', '中国,广东省,中山市,南朗镇', '3', 'nanlang', '0760', '528454', 'N', '113.533939', '22.492378'); +INSERT INTO `yoshop_region` VALUES ('2139', '2123', '港口', '港口镇', '中国,广东省,中山市,港口镇', '3', 'gangkou', '0760', '528447', 'G', '113.382391', '22.521113'); +INSERT INTO `yoshop_region` VALUES ('2140', '2123', '大涌', '大涌镇', '中国,广东省,中山市,大涌镇', '3', 'dayong', '0760', '528476', 'D', '113.291708', '22.467712'); +INSERT INTO `yoshop_region` VALUES ('2141', '2123', '沙溪', '沙溪镇', '中国,广东省,中山市,沙溪镇', '3', 'shaxi', '0760', '528471', 'S', '113.328369', '22.526325'); +INSERT INTO `yoshop_region` VALUES ('2142', '2123', '三乡', '三乡镇', '中国,广东省,中山市,三乡镇', '3', 'sanxiang', '0760', '528463', 'S', '113.4334', '22.352494'); +INSERT INTO `yoshop_region` VALUES ('2143', '2123', '板芙', '板芙镇', '中国,广东省,中山市,板芙镇', '3', 'banfu', '0760', '528459', 'B', '113.320346', '22.415674'); +INSERT INTO `yoshop_region` VALUES ('2144', '2123', '神湾', '神湾镇', '中国,广东省,中山市,神湾镇', '3', 'shenwan', '0760', '528462', 'S', '113.359387', '22.312476'); +INSERT INTO `yoshop_region` VALUES ('2145', '2123', '坦洲', '坦洲镇', '中国,广东省,中山市,坦洲镇', '3', 'tanzhou', '0760', '528467', 'T', '113.485677', '22.261269'); +INSERT INTO `yoshop_region` VALUES ('2146', '1964', '潮州', '潮州市', '中国,广东省,潮州市', '2', 'chaozhou', '0768', '521000', 'C', '116.632301', '23.661701'); +INSERT INTO `yoshop_region` VALUES ('2147', '2146', '湘桥', '湘桥区', '中国,广东省,潮州市,湘桥区', '3', 'xiangqiao', '0768', '521000', 'X', '116.62805', '23.67451'); +INSERT INTO `yoshop_region` VALUES ('2148', '2146', '潮安', '潮安区', '中国,广东省,潮州市,潮安区', '3', 'chao\'an', '0768', '515638', 'C', '116.592895', '23.643656'); +INSERT INTO `yoshop_region` VALUES ('2149', '2146', '饶平', '饶平县', '中国,广东省,潮州市,饶平县', '3', 'raoping', '0768', '515700', 'R', '117.00692', '23.66994'); +INSERT INTO `yoshop_region` VALUES ('2150', '1964', '揭阳', '揭阳市', '中国,广东省,揭阳市', '2', 'jieyang', '0633', '522000', 'J', '116.355733', '23.543778'); +INSERT INTO `yoshop_region` VALUES ('2151', '2150', '榕城', '榕城区', '中国,广东省,揭阳市,榕城区', '3', 'rongcheng', '0633', '522000', null, '116.3671', '23.52508'); +INSERT INTO `yoshop_region` VALUES ('2152', '2150', '揭东', '揭东区', '中国,广东省,揭阳市,揭东区', '3', 'jiedong', '0633', '515500', 'J', '116.412947', '23.569887'); +INSERT INTO `yoshop_region` VALUES ('2153', '2150', '揭西', '揭西县', '中国,广东省,揭阳市,揭西县', '3', 'jiexi', '0633', '515400', 'J', '115.83883', '23.42714'); +INSERT INTO `yoshop_region` VALUES ('2154', '2150', '惠来', '惠来县', '中国,广东省,揭阳市,惠来县', '3', 'huilai', '0633', '515200', 'H', '116.29599', '23.03289'); +INSERT INTO `yoshop_region` VALUES ('2155', '2150', '普宁', '普宁市', '中国,广东省,揭阳市,普宁市', '3', 'puning', '0633', '515300', 'P', '116.16564', '23.29732'); +INSERT INTO `yoshop_region` VALUES ('2156', '1964', '云浮', '云浮市', '中国,广东省,云浮市', '2', 'yunfu', '0766', '527300', 'Y', '112.044439', '22.929801'); +INSERT INTO `yoshop_region` VALUES ('2157', '2156', '云城', '云城区', '中国,广东省,云浮市,云城区', '3', 'yuncheng', '0766', '527300', 'Y', '112.03908', '22.92996'); +INSERT INTO `yoshop_region` VALUES ('2158', '2156', '云安', '云安区', '中国,广东省,云浮市,云安区', '3', 'yun\'an', '0766', '527500', 'Y', '112.00936', '23.07779'); +INSERT INTO `yoshop_region` VALUES ('2159', '2156', '新兴', '新兴县', '中国,广东省,云浮市,新兴县', '3', 'xinxing', '0766', '527400', 'X', '112.23019', '22.69734'); +INSERT INTO `yoshop_region` VALUES ('2160', '2156', '郁南', '郁南县', '中国,广东省,云浮市,郁南县', '3', 'yunan', '0766', '527100', 'Y', '111.53387', '23.23307'); +INSERT INTO `yoshop_region` VALUES ('2161', '2156', '罗定', '罗定市', '中国,广东省,云浮市,罗定市', '3', 'luoding', '0766', '527200', 'L', '111.56979', '22.76967'); +INSERT INTO `yoshop_region` VALUES ('2162', '0', '广西', '广西壮族自治区', '中国,广西壮族自治区', '1', 'guangxi', '', '', 'G', '108.320004', '22.82402'); +INSERT INTO `yoshop_region` VALUES ('2163', '2162', '南宁', '南宁市', '中国,广西壮族自治区,南宁市', '2', 'nanning', '0771', '530028', 'N', '108.320004', '22.82402'); +INSERT INTO `yoshop_region` VALUES ('2164', '2163', '兴宁', '兴宁区', '中国,广西壮族自治区,南宁市,兴宁区', '3', 'xingning', '0771', '530023', 'X', '108.36694', '22.85355'); +INSERT INTO `yoshop_region` VALUES ('2165', '2163', '青秀', '青秀区', '中国,广西壮族自治区,南宁市,青秀区', '3', 'qingxiu', '0771', '530213', 'Q', '108.49545', '22.78511'); +INSERT INTO `yoshop_region` VALUES ('2166', '2163', '江南', '江南区', '中国,广西壮族自治区,南宁市,江南区', '3', 'jiangnan', '0771', '530031', 'J', '108.27325', '22.78127'); +INSERT INTO `yoshop_region` VALUES ('2167', '2163', '西乡塘', '西乡塘区', '中国,广西壮族自治区,南宁市,西乡塘区', '3', 'xixiangtang', '0771', '530001', 'X', '108.31347', '22.83386'); +INSERT INTO `yoshop_region` VALUES ('2168', '2163', '良庆', '良庆区', '中国,广西壮族自治区,南宁市,良庆区', '3', 'liangqing', '0771', '530219', 'L', '108.41284', '22.74914'); +INSERT INTO `yoshop_region` VALUES ('2169', '2163', '邕宁', '邕宁区', '中国,广西壮族自治区,南宁市,邕宁区', '3', 'yongning', '0771', '530200', null, '108.48684', '22.75628'); +INSERT INTO `yoshop_region` VALUES ('2170', '2163', '武鸣', '武鸣县', '中国,广西壮族自治区,南宁市,武鸣县', '3', 'wuming', '0771', '530100', 'W', '108.27719', '23.15643'); +INSERT INTO `yoshop_region` VALUES ('2171', '2163', '隆安', '隆安县', '中国,广西壮族自治区,南宁市,隆安县', '3', 'long\'an', '0771', '532700', 'L', '107.69192', '23.17336'); +INSERT INTO `yoshop_region` VALUES ('2172', '2163', '马山', '马山县', '中国,广西壮族自治区,南宁市,马山县', '3', 'mashan', '0771', '530600', 'M', '108.17697', '23.70931'); +INSERT INTO `yoshop_region` VALUES ('2173', '2163', '上林', '上林县', '中国,广西壮族自治区,南宁市,上林县', '3', 'shanglin', '0771', '530500', 'S', '108.60522', '23.432'); +INSERT INTO `yoshop_region` VALUES ('2174', '2163', '宾阳', '宾阳县', '中国,广西壮族自治区,南宁市,宾阳县', '3', 'binyang', '0771', '530400', 'B', '108.81185', '23.2196'); +INSERT INTO `yoshop_region` VALUES ('2175', '2163', '横县', '横县', '中国,广西壮族自治区,南宁市,横县', '3', 'hengxian', '0771', '530300', 'H', '109.26608', '22.68448'); +INSERT INTO `yoshop_region` VALUES ('2176', '2163', '埌东', '埌东新区', '中国,广西壮族自治区,南宁市,埌东新区', '3', 'langdong', '0771', '530000', null, '108.419094', '22.812976'); +INSERT INTO `yoshop_region` VALUES ('2177', '2162', '柳州', '柳州市', '中国,广西壮族自治区,柳州市', '2', 'liuzhou', '0772', '545001', 'L', '109.411703', '24.314617'); +INSERT INTO `yoshop_region` VALUES ('2178', '2177', '城中', '城中区', '中国,广西壮族自治区,柳州市,城中区', '3', 'chengzhong', '0772', '545001', 'C', '109.41082', '24.31543'); +INSERT INTO `yoshop_region` VALUES ('2179', '2177', '鱼峰', '鱼峰区', '中国,广西壮族自治区,柳州市,鱼峰区', '3', 'yufeng', '0772', '545005', 'Y', '109.4533', '24.31868'); +INSERT INTO `yoshop_region` VALUES ('2180', '2177', '柳南', '柳南区', '中国,广西壮族自治区,柳州市,柳南区', '3', 'liunan', '0772', '545007', 'L', '109.38548', '24.33599'); +INSERT INTO `yoshop_region` VALUES ('2181', '2177', '柳北', '柳北区', '中国,广西壮族自治区,柳州市,柳北区', '3', 'liubei', '0772', '545002', 'L', '109.40202', '24.36267'); +INSERT INTO `yoshop_region` VALUES ('2182', '2177', '柳江', '柳江县', '中国,广西壮族自治区,柳州市,柳江县', '3', 'liujiang', '0772', '545100', 'L', '109.33273', '24.25596'); +INSERT INTO `yoshop_region` VALUES ('2183', '2177', '柳城', '柳城县', '中国,广西壮族自治区,柳州市,柳城县', '3', 'liucheng', '0772', '545200', 'L', '109.23877', '24.64951'); +INSERT INTO `yoshop_region` VALUES ('2184', '2177', '鹿寨', '鹿寨县', '中国,广西壮族自治区,柳州市,鹿寨县', '3', 'luzhai', '0772', '545600', 'L', '109.75177', '24.47306'); +INSERT INTO `yoshop_region` VALUES ('2185', '2177', '融安', '融安县', '中国,广西壮族自治区,柳州市,融安县', '3', 'rong\'an', '0772', '545400', 'R', '109.39761', '25.22465'); +INSERT INTO `yoshop_region` VALUES ('2186', '2177', '融水', '融水苗族自治县', '中国,广西壮族自治区,柳州市,融水苗族自治县', '3', 'rongshui', '0772', '545300', 'R', '109.25634', '25.06628'); +INSERT INTO `yoshop_region` VALUES ('2187', '2177', '三江', '三江侗族自治县', '中国,广西壮族自治区,柳州市,三江侗族自治县', '3', 'sanjiang', '0772', '545500', 'S', '109.60446', '25.78428'); +INSERT INTO `yoshop_region` VALUES ('2188', '2177', '柳东', '柳东新区', '中国,广西壮族自治区,柳州市,柳东新区', '3', 'liudong', '0772', '545000', 'L', '109.437053', '24.329204'); +INSERT INTO `yoshop_region` VALUES ('2189', '2162', '桂林', '桂林市', '中国,广西壮族自治区,桂林市', '2', 'guilin', '0773', '541100', 'G', '110.299121', '25.274215'); +INSERT INTO `yoshop_region` VALUES ('2190', '2189', '秀峰', '秀峰区', '中国,广西壮族自治区,桂林市,秀峰区', '3', 'xiufeng', '0773', '541001', 'X', '110.28915', '25.28249'); +INSERT INTO `yoshop_region` VALUES ('2191', '2189', '叠彩', '叠彩区', '中国,广西壮族自治区,桂林市,叠彩区', '3', 'diecai', '0773', '541001', 'D', '110.30195', '25.31381'); +INSERT INTO `yoshop_region` VALUES ('2192', '2189', '象山', '象山区', '中国,广西壮族自治区,桂林市,象山区', '3', 'xiangshan', '0773', '541002', 'X', '110.28108', '25.26168'); +INSERT INTO `yoshop_region` VALUES ('2193', '2189', '七星', '七星区', '中国,广西壮族自治区,桂林市,七星区', '3', 'qixing', '0773', '541004', 'Q', '110.31793', '25.2525'); +INSERT INTO `yoshop_region` VALUES ('2194', '2189', '雁山', '雁山区', '中国,广西壮族自治区,桂林市,雁山区', '3', 'yanshan', '0773', '541006', 'Y', '110.30911', '25.06038'); +INSERT INTO `yoshop_region` VALUES ('2195', '2189', '临桂', '临桂区', '中国,广西壮族自治区,桂林市,临桂区', '3', 'lingui', '0773', '541100', 'L', '110.205487', '25.246257'); +INSERT INTO `yoshop_region` VALUES ('2196', '2189', '阳朔', '阳朔县', '中国,广西壮族自治区,桂林市,阳朔县', '3', 'yangshuo', '0773', '541900', 'Y', '110.49475', '24.77579'); +INSERT INTO `yoshop_region` VALUES ('2197', '2189', '灵川', '灵川县', '中国,广西壮族自治区,桂林市,灵川县', '3', 'lingchuan', '0773', '541200', 'L', '110.32949', '25.41292'); +INSERT INTO `yoshop_region` VALUES ('2198', '2189', '全州', '全州县', '中国,广西壮族自治区,桂林市,全州县', '3', 'quanzhou', '0773', '541503', 'Q', '111.07211', '25.92799'); +INSERT INTO `yoshop_region` VALUES ('2199', '2189', '兴安', '兴安县', '中国,广西壮族自治区,桂林市,兴安县', '3', 'xing\'an', '0773', '541300', 'X', '110.67144', '25.61167'); +INSERT INTO `yoshop_region` VALUES ('2200', '2189', '永福', '永福县', '中国,广西壮族自治区,桂林市,永福县', '3', 'yongfu', '0773', '541800', 'Y', '109.98333', '24.98004'); +INSERT INTO `yoshop_region` VALUES ('2201', '2189', '灌阳', '灌阳县', '中国,广西壮族自治区,桂林市,灌阳县', '3', 'guanyang', '0773', '541600', 'G', '111.15954', '25.48803'); +INSERT INTO `yoshop_region` VALUES ('2202', '2189', '龙胜', '龙胜各族自治县', '中国,广西壮族自治区,桂林市,龙胜各族自治县', '3', 'longsheng', '0773', '541700', 'L', '110.01226', '25.79614'); +INSERT INTO `yoshop_region` VALUES ('2203', '2189', '资源', '资源县', '中国,广西壮族自治区,桂林市,资源县', '3', 'ziyuan', '0773', '541400', 'Z', '110.65255', '26.04237'); +INSERT INTO `yoshop_region` VALUES ('2204', '2189', '平乐', '平乐县', '中国,广西壮族自治区,桂林市,平乐县', '3', 'pingle', '0773', '542400', 'P', '110.64175', '24.63242'); +INSERT INTO `yoshop_region` VALUES ('2205', '2189', '荔浦', '荔浦县', '中国,广西壮族自治区,桂林市,荔浦县', '3', 'lipu', '0773', '546600', 'L', '110.3971', '24.49589'); +INSERT INTO `yoshop_region` VALUES ('2206', '2189', '恭城', '恭城瑶族自治县', '中国,广西壮族自治区,桂林市,恭城瑶族自治县', '3', 'gongcheng', '0773', '542500', 'G', '110.83035', '24.83286'); +INSERT INTO `yoshop_region` VALUES ('2207', '2162', '梧州', '梧州市', '中国,广西壮族自治区,梧州市', '2', 'wuzhou', '0774', '543002', 'W', '111.316229', '23.472309'); +INSERT INTO `yoshop_region` VALUES ('2208', '2207', '万秀', '万秀区', '中国,广西壮族自治区,梧州市,万秀区', '3', 'wanxiu', '0774', '543000', 'W', '111.32052', '23.47298'); +INSERT INTO `yoshop_region` VALUES ('2209', '2207', '长洲', '长洲区', '中国,广西壮族自治区,梧州市,长洲区', '3', 'changzhou', '0774', '543003', 'C', '111.27494', '23.48573'); +INSERT INTO `yoshop_region` VALUES ('2210', '2207', '龙圩', '龙圩区', '中国,广西壮族自治区,梧州市,龙圩区', '3', 'longxu', '0774', '543002', 'L', '111.316229', '23.472309'); +INSERT INTO `yoshop_region` VALUES ('2211', '2207', '苍梧', '苍梧县', '中国,广西壮族自治区,梧州市,苍梧县', '3', 'cangwu', '0774', '543100', 'C', '111.24533', '23.42049'); +INSERT INTO `yoshop_region` VALUES ('2212', '2207', '藤县', '藤县', '中国,广西壮族自治区,梧州市,藤县', '3', 'tengxian', '0774', '543300', 'T', '110.91418', '23.37605'); +INSERT INTO `yoshop_region` VALUES ('2213', '2207', '蒙山', '蒙山县', '中国,广西壮族自治区,梧州市,蒙山县', '3', 'mengshan', '0774', '546700', 'M', '110.52221', '24.20168'); +INSERT INTO `yoshop_region` VALUES ('2214', '2207', '岑溪', '岑溪市', '中国,广西壮族自治区,梧州市,岑溪市', '3', 'cenxi', '0774', '543200', null, '110.99594', '22.9191'); +INSERT INTO `yoshop_region` VALUES ('2215', '2162', '北海', '北海市', '中国,广西壮族自治区,北海市', '2', 'beihai', '0779', '536000', 'B', '109.119254', '21.473343'); +INSERT INTO `yoshop_region` VALUES ('2216', '2215', '海城', '海城区', '中国,广西壮族自治区,北海市,海城区', '3', 'haicheng', '0779', '536000', 'H', '109.11744', '21.47501'); +INSERT INTO `yoshop_region` VALUES ('2217', '2215', '银海', '银海区', '中国,广西壮族自治区,北海市,银海区', '3', 'yinhai', '0779', '536000', 'Y', '109.13029', '21.4783'); +INSERT INTO `yoshop_region` VALUES ('2218', '2215', '铁山港', '铁山港区', '中国,广西壮族自治区,北海市,铁山港区', '3', 'tieshangang', '0779', '536017', 'T', '109.45578', '21.59661'); +INSERT INTO `yoshop_region` VALUES ('2219', '2215', '合浦', '合浦县', '中国,广西壮族自治区,北海市,合浦县', '3', 'hepu', '0779', '536100', 'H', '109.20068', '21.66601'); +INSERT INTO `yoshop_region` VALUES ('2220', '2162', '防城港', '防城港市', '中国,广西壮族自治区,防城港市', '2', 'fangchenggang', '0770', '538001', 'F', '108.345478', '21.614631'); +INSERT INTO `yoshop_region` VALUES ('2221', '2220', '港口', '港口区', '中国,广西壮族自治区,防城港市,港口区', '3', 'gangkou', '0770', '538001', 'G', '108.38022', '21.64342'); +INSERT INTO `yoshop_region` VALUES ('2222', '2220', '防城', '防城区', '中国,广西壮族自治区,防城港市,防城区', '3', 'fangcheng', '0770', '538021', 'F', '108.35726', '21.76464'); +INSERT INTO `yoshop_region` VALUES ('2223', '2220', '上思', '上思县', '中国,广西壮族自治区,防城港市,上思县', '3', 'shangsi', '0770', '535500', 'S', '107.9823', '22.14957'); +INSERT INTO `yoshop_region` VALUES ('2224', '2220', '东兴', '东兴市', '中国,广西壮族自治区,防城港市,东兴市', '3', 'dongxing', '0770', '538100', 'D', '107.97204', '21.54713'); +INSERT INTO `yoshop_region` VALUES ('2225', '2162', '钦州', '钦州市', '中国,广西壮族自治区,钦州市', '2', 'qinzhou', '0777', '535099', 'Q', '108.624175', '21.967127'); +INSERT INTO `yoshop_region` VALUES ('2226', '2225', '钦南', '钦南区', '中国,广西壮族自治区,钦州市,钦南区', '3', 'qinnan', '0777', '535099', 'Q', '108.61775', '21.95137'); +INSERT INTO `yoshop_region` VALUES ('2227', '2225', '钦北', '钦北区', '中国,广西壮族自治区,钦州市,钦北区', '3', 'qinbei', '0777', '535099', 'Q', '108.63037', '21.95127'); +INSERT INTO `yoshop_region` VALUES ('2228', '2225', '灵山', '灵山县', '中国,广西壮族自治区,钦州市,灵山县', '3', 'lingshan', '0777', '535099', 'L', '109.29153', '22.4165'); +INSERT INTO `yoshop_region` VALUES ('2229', '2225', '浦北', '浦北县', '中国,广西壮族自治区,钦州市,浦北县', '3', 'pubei', '0777', '535099', 'P', '109.55572', '22.26888'); +INSERT INTO `yoshop_region` VALUES ('2230', '2162', '贵港', '贵港市', '中国,广西壮族自治区,贵港市', '2', 'guigang', '0775', '537100', 'G', '109.602146', '23.0936'); +INSERT INTO `yoshop_region` VALUES ('2231', '2230', '港北', '港北区', '中国,广西壮族自治区,贵港市,港北区', '3', 'gangbei', '0775', '537100', 'G', '109.57224', '23.11153'); +INSERT INTO `yoshop_region` VALUES ('2232', '2230', '港南', '港南区', '中国,广西壮族自治区,贵港市,港南区', '3', 'gangnan', '0775', '537100', 'G', '109.60617', '23.07226'); +INSERT INTO `yoshop_region` VALUES ('2233', '2230', '覃塘', '覃塘区', '中国,广西壮族自治区,贵港市,覃塘区', '3', 'qintang', '0775', '537121', null, '109.44293', '23.12677'); +INSERT INTO `yoshop_region` VALUES ('2234', '2230', '平南', '平南县', '中国,广西壮族自治区,贵港市,平南县', '3', 'pingnan', '0775', '537300', 'P', '110.39062', '23.54201'); +INSERT INTO `yoshop_region` VALUES ('2235', '2230', '桂平', '桂平市', '中国,广西壮族自治区,贵港市,桂平市', '3', 'guiping', '0775', '537200', 'G', '110.08105', '23.39339'); +INSERT INTO `yoshop_region` VALUES ('2236', '2162', '玉林', '玉林市', '中国,广西壮族自治区,玉林市', '2', 'yulin', '0775', '537000', 'Y', '110.154393', '22.63136'); +INSERT INTO `yoshop_region` VALUES ('2237', '2236', '玉州', '玉州区', '中国,广西壮族自治区,玉林市,玉州区', '3', 'yuzhou', '0775', '537000', 'Y', '110.15114', '22.6281'); +INSERT INTO `yoshop_region` VALUES ('2238', '2236', '福绵', '福绵区', '中国,广西壮族自治区,玉林市,福绵区', '3', 'fumian', '0775', '537023', 'F', '110.064816', '22.583057'); +INSERT INTO `yoshop_region` VALUES ('2239', '2236', '玉东', '玉东新区', '中国,广西壮族自治区,玉林市,玉东新区', '3', 'yudong', '0775', '537000', 'Y', '110.154393', '22.63136'); +INSERT INTO `yoshop_region` VALUES ('2240', '2236', '容县', '容县', '中国,广西壮族自治区,玉林市,容县', '3', 'rongxian', '0775', '537500', 'R', '110.55593', '22.85701'); +INSERT INTO `yoshop_region` VALUES ('2241', '2236', '陆川', '陆川县', '中国,广西壮族自治区,玉林市,陆川县', '3', 'luchuan', '0775', '537700', 'L', '110.26413', '22.32454'); +INSERT INTO `yoshop_region` VALUES ('2242', '2236', '博白', '博白县', '中国,广西壮族自治区,玉林市,博白县', '3', 'bobai', '0775', '537600', 'B', '109.97744', '22.27286'); +INSERT INTO `yoshop_region` VALUES ('2243', '2236', '兴业', '兴业县', '中国,广西壮族自治区,玉林市,兴业县', '3', 'xingye', '0775', '537800', 'X', '109.87612', '22.74237'); +INSERT INTO `yoshop_region` VALUES ('2244', '2236', '北流', '北流市', '中国,广西壮族自治区,玉林市,北流市', '3', 'beiliu', '0775', '537400', 'B', '110.35302', '22.70817'); +INSERT INTO `yoshop_region` VALUES ('2245', '2162', '百色', '百色市', '中国,广西壮族自治区,百色市', '2', 'baise', '0776', '533000', 'B', '106.616285', '23.897742'); +INSERT INTO `yoshop_region` VALUES ('2246', '2245', '右江', '右江区', '中国,广西壮族自治区,百色市,右江区', '3', 'youjiang', '0776', '533000', 'Y', '106.61764', '23.9009'); +INSERT INTO `yoshop_region` VALUES ('2247', '2245', '田阳', '田阳县', '中国,广西壮族自治区,百色市,田阳县', '3', 'tianyang', '0776', '533600', 'T', '106.91558', '23.73535'); +INSERT INTO `yoshop_region` VALUES ('2248', '2245', '田东', '田东县', '中国,广西壮族自治区,百色市,田东县', '3', 'tiandong', '0776', '531500', 'T', '107.12432', '23.60003'); +INSERT INTO `yoshop_region` VALUES ('2249', '2245', '平果', '平果县', '中国,广西壮族自治区,百色市,平果县', '3', 'pingguo', '0776', '531400', 'P', '107.59045', '23.32969'); +INSERT INTO `yoshop_region` VALUES ('2250', '2245', '德保', '德保县', '中国,广西壮族自治区,百色市,德保县', '3', 'debao', '0776', '533700', 'D', '106.61917', '23.32515'); +INSERT INTO `yoshop_region` VALUES ('2251', '2245', '靖西', '靖西县', '中国,广西壮族自治区,百色市,靖西县', '3', 'jingxi', '0776', '533800', 'J', '106.41766', '23.13425'); +INSERT INTO `yoshop_region` VALUES ('2252', '2245', '那坡', '那坡县', '中国,广西壮族自治区,百色市,那坡县', '3', 'napo', '0776', '533900', 'N', '105.84191', '23.40649'); +INSERT INTO `yoshop_region` VALUES ('2253', '2245', '凌云', '凌云县', '中国,广西壮族自治区,百色市,凌云县', '3', 'lingyun', '0776', '533100', 'L', '106.56155', '24.34747'); +INSERT INTO `yoshop_region` VALUES ('2254', '2245', '乐业', '乐业县', '中国,广西壮族自治区,百色市,乐业县', '3', 'leye', '0776', '533200', 'L', '106.56124', '24.78295'); +INSERT INTO `yoshop_region` VALUES ('2255', '2245', '田林', '田林县', '中国,广西壮族自治区,百色市,田林县', '3', 'tianlin', '0776', '533300', 'T', '106.22882', '24.29207'); +INSERT INTO `yoshop_region` VALUES ('2256', '2245', '西林', '西林县', '中国,广西壮族自治区,百色市,西林县', '3', 'xilin', '0776', '533500', 'X', '105.09722', '24.48966'); +INSERT INTO `yoshop_region` VALUES ('2257', '2245', '隆林', '隆林各族自治县', '中国,广西壮族自治区,百色市,隆林各族自治县', '3', 'longlin', '0776', '533400', 'L', '105.34295', '24.77036'); +INSERT INTO `yoshop_region` VALUES ('2258', '2162', '贺州', '贺州市', '中国,广西壮族自治区,贺州市', '2', 'hezhou', '0774', '542800', 'H', '111.552056', '24.414141'); +INSERT INTO `yoshop_region` VALUES ('2259', '2258', '八步', '八步区', '中国,广西壮族自治区,贺州市,八步区', '3', 'babu', '0774', '542800', 'B', '111.55225', '24.41179'); +INSERT INTO `yoshop_region` VALUES ('2260', '2258', '昭平', '昭平县', '中国,广西壮族自治区,贺州市,昭平县', '3', 'zhaoping', '0774', '546800', 'Z', '110.81082', '24.1701'); +INSERT INTO `yoshop_region` VALUES ('2261', '2258', '钟山', '钟山县', '中国,广西壮族自治区,贺州市,钟山县', '3', 'zhongshan', '0774', '542600', 'Z', '111.30459', '24.52482'); +INSERT INTO `yoshop_region` VALUES ('2262', '2258', '富川', '富川瑶族自治县', '中国,广西壮族自治区,贺州市,富川瑶族自治县', '3', 'fuchuan', '0774', '542700', 'F', '111.27767', '24.81431'); +INSERT INTO `yoshop_region` VALUES ('2263', '2258', '平桂', '平桂管理区', '中国,广西壮族自治区,贺州市,平桂管理区', '3', 'pingui', '0774', '542800', 'P', '111.485651', '24.458041'); +INSERT INTO `yoshop_region` VALUES ('2264', '2162', '河池', '河池市', '中国,广西壮族自治区,河池市', '2', 'hechi', '0778', '547000', 'H', '108.062105', '24.695899'); +INSERT INTO `yoshop_region` VALUES ('2265', '2264', '金城江', '金城江区', '中国,广西壮族自治区,河池市,金城江区', '3', 'jinchengjiang', '0779', '547000', 'J', '108.03727', '24.6897'); +INSERT INTO `yoshop_region` VALUES ('2266', '2264', '南丹', '南丹县', '中国,广西壮族自治区,河池市,南丹县', '3', 'nandan', '0781', '547200', 'N', '107.54562', '24.9776'); +INSERT INTO `yoshop_region` VALUES ('2267', '2264', '天峨', '天峨县', '中国,广西壮族自治区,河池市,天峨县', '3', 'tiane', '0782', '547300', 'T', '107.17205', '24.99593'); +INSERT INTO `yoshop_region` VALUES ('2268', '2264', '凤山', '凤山县', '中国,广西壮族自治区,河池市,凤山县', '3', 'fengshan', '0783', '547600', 'F', '107.04892', '24.54215'); +INSERT INTO `yoshop_region` VALUES ('2269', '2264', '东兰', '东兰县', '中国,广西壮族自治区,河池市,东兰县', '3', 'donglan', '0784', '547400', 'D', '107.37527', '24.51053'); +INSERT INTO `yoshop_region` VALUES ('2270', '2264', '罗城', '罗城仫佬族自治县', '中国,广西壮族自治区,河池市,罗城仫佬族自治县', '3', 'luocheng', '0785', '546400', 'L', '108.90777', '24.77923'); +INSERT INTO `yoshop_region` VALUES ('2271', '2264', '环江', '环江毛南族自治县', '中国,广西壮族自治区,河池市,环江毛南族自治县', '3', 'huanjiang', '0786', '547100', 'H', '108.26055', '24.82916'); +INSERT INTO `yoshop_region` VALUES ('2272', '2264', '巴马', '巴马瑶族自治县', '中国,广西壮族自治区,河池市,巴马瑶族自治县', '3', 'bama', '0787', '547500', 'B', '107.25308', '24.14135'); +INSERT INTO `yoshop_region` VALUES ('2273', '2264', '都安', '都安瑶族自治县', '中国,广西壮族自治区,河池市,都安瑶族自治县', '3', 'du\'an', '0788', '530700', 'D', '108.10116', '23.93245'); +INSERT INTO `yoshop_region` VALUES ('2274', '2264', '大化', '大化瑶族自治县', '中国,广西壮族自治区,河池市,大化瑶族自治县', '3', 'dahua', '0789', '530800', 'D', '107.9985', '23.74487'); +INSERT INTO `yoshop_region` VALUES ('2275', '2264', '宜州', '宜州市', '中国,广西壮族自治区,河池市,宜州市', '3', 'yizhou', '0780', '546300', 'Y', '108.65304', '24.49391'); +INSERT INTO `yoshop_region` VALUES ('2276', '2162', '来宾', '来宾市', '中国,广西壮族自治区,来宾市', '2', 'laibin', '0772', '546100', 'L', '109.229772', '23.733766'); +INSERT INTO `yoshop_region` VALUES ('2277', '2276', '兴宾', '兴宾区', '中国,广西壮族自治区,来宾市,兴宾区', '3', 'xingbin', '0772', '546100', 'X', '109.23471', '23.72731'); +INSERT INTO `yoshop_region` VALUES ('2278', '2276', '忻城', '忻城县', '中国,广西壮族自治区,来宾市,忻城县', '3', 'xincheng', '0772', '546200', 'X', '108.66357', '24.06862'); +INSERT INTO `yoshop_region` VALUES ('2279', '2276', '象州', '象州县', '中国,广西壮族自治区,来宾市,象州县', '3', 'xiangzhou', '0772', '545800', 'X', '109.6994', '23.97355'); +INSERT INTO `yoshop_region` VALUES ('2280', '2276', '武宣', '武宣县', '中国,广西壮族自治区,来宾市,武宣县', '3', 'wuxuan', '0772', '545900', 'W', '109.66284', '23.59474'); +INSERT INTO `yoshop_region` VALUES ('2281', '2276', '金秀', '金秀瑶族自治县', '中国,广西壮族自治区,来宾市,金秀瑶族自治县', '3', 'jinxiu', '0772', '545799', 'J', '110.19079', '24.12929'); +INSERT INTO `yoshop_region` VALUES ('2282', '2276', '合山', '合山市', '中国,广西壮族自治区,来宾市,合山市', '3', 'heshan', '0772', '546500', 'H', '108.88586', '23.80619'); +INSERT INTO `yoshop_region` VALUES ('2283', '2162', '崇左', '崇左市', '中国,广西壮族自治区,崇左市', '2', 'chongzuo', '0771', '532299', 'C', '107.353926', '22.404108'); +INSERT INTO `yoshop_region` VALUES ('2284', '2283', '江州', '江州区', '中国,广西壮族自治区,崇左市,江州区', '3', 'jiangzhou', '0771', '532299', 'J', '107.34747', '22.41135'); +INSERT INTO `yoshop_region` VALUES ('2285', '2283', '扶绥', '扶绥县', '中国,广西壮族自治区,崇左市,扶绥县', '3', 'fusui', '0771', '532199', 'F', '107.90405', '22.63413'); +INSERT INTO `yoshop_region` VALUES ('2286', '2283', '宁明', '宁明县', '中国,广西壮族自治区,崇左市,宁明县', '3', 'ningming', '0771', '532599', 'N', '107.07299', '22.13655'); +INSERT INTO `yoshop_region` VALUES ('2287', '2283', '龙州', '龙州县', '中国,广西壮族自治区,崇左市,龙州县', '3', 'longzhou', '0771', '532499', 'L', '106.85415', '22.33937'); +INSERT INTO `yoshop_region` VALUES ('2288', '2283', '大新', '大新县', '中国,广西壮族自治区,崇左市,大新县', '3', 'daxin', '0771', '532399', 'D', '107.19821', '22.83412'); +INSERT INTO `yoshop_region` VALUES ('2289', '2283', '天等', '天等县', '中国,广西壮族自治区,崇左市,天等县', '3', 'tiandeng', '0771', '532899', 'T', '107.13998', '23.077'); +INSERT INTO `yoshop_region` VALUES ('2290', '2283', '凭祥', '凭祥市', '中国,广西壮族自治区,崇左市,凭祥市', '3', 'pingxiang', '0771', '532699', 'P', '106.75534', '22.10573'); +INSERT INTO `yoshop_region` VALUES ('2291', '0', '海南', '海南省', '中国,海南省', '1', 'hainan', '', '', 'H', '110.33119', '20.031971'); +INSERT INTO `yoshop_region` VALUES ('2292', '2291', '海口', '海口市', '中国,海南省,海口市', '2', 'haikou', '0898', '570000', 'H', '110.33119', '20.031971'); +INSERT INTO `yoshop_region` VALUES ('2293', '2292', '秀英', '秀英区', '中国,海南省,海口市,秀英区', '3', 'xiuying', '0898', '570311', 'X', '110.29345', '20.00752'); +INSERT INTO `yoshop_region` VALUES ('2294', '2292', '龙华', '龙华区', '中国,海南省,海口市,龙华区', '3', 'longhua', '0898', '570145', 'L', '110.30194', '20.02866'); +INSERT INTO `yoshop_region` VALUES ('2295', '2292', '琼山', '琼山区', '中国,海南省,海口市,琼山区', '3', 'qiongshan', '0898', '571100', 'Q', '110.35418', '20.00321'); +INSERT INTO `yoshop_region` VALUES ('2296', '2292', '美兰', '美兰区', '中国,海南省,海口市,美兰区', '3', 'meilan', '0898', '570203', 'M', '110.36908', '20.02864'); +INSERT INTO `yoshop_region` VALUES ('2297', '2291', '三亚', '三亚市', '中国,海南省,三亚市', '2', 'sanya', '0898', '572000', 'S', '109.508268', '18.247872'); +INSERT INTO `yoshop_region` VALUES ('2298', '2297', '海棠', '海棠区', '中国,海南省,三亚市,海棠区', '3', 'haitang', '0898', '572000', 'H', '109.508268', '18.247872'); +INSERT INTO `yoshop_region` VALUES ('2299', '2297', '吉阳', '吉阳区', '中国,海南省,三亚市,吉阳区', '3', 'jiyang', '0898', '572000', 'J', '109.508268', '18.247872'); +INSERT INTO `yoshop_region` VALUES ('2300', '2297', '天涯', '天涯区', '中国,海南省,三亚市,天涯区', '3', 'tianya', '0898', '572000', 'T', '109.508268', '18.247872'); +INSERT INTO `yoshop_region` VALUES ('2301', '2297', '崖州', '崖州区', '中国,海南省,三亚市,崖州区', '3', 'yazhou', '0898', '572000', 'Y', '109.508268', '18.247872'); +INSERT INTO `yoshop_region` VALUES ('2302', '2291', '三沙', '三沙市', '中国,海南省,三沙市', '2', 'sansha', '0898', '573199', 'S', '112.34882', '16.831039'); +INSERT INTO `yoshop_region` VALUES ('2303', '2302', '西沙', '西沙群岛', '中国,海南省,三沙市,西沙群岛', '3', 'xishaislands', '0898', '572000', 'X', '112.025528', '16.331342'); +INSERT INTO `yoshop_region` VALUES ('2304', '2302', '南沙', '南沙群岛', '中国,海南省,三沙市,南沙群岛', '3', 'nanshaislands', '0898', '573100', 'N', '116.749998', '11.471888'); +INSERT INTO `yoshop_region` VALUES ('2305', '2302', '中沙', '中沙群岛', '中国,海南省,三沙市,中沙群岛', '3', 'zhongshaislands', '0898', '573100', 'Z', '117.740071', '15.112856'); +INSERT INTO `yoshop_region` VALUES ('2306', '2291', ' ', '直辖县级', '中国,海南省,直辖县级', '2', '', '', '', 'Z', '109.503479', '18.739906'); +INSERT INTO `yoshop_region` VALUES ('2307', '2306', '五指山', '五指山市', '中国,海南省,直辖县级,五指山市', '3', 'wuzhishan', '0898', '572200', 'W', '109.516662', '18.776921'); +INSERT INTO `yoshop_region` VALUES ('2308', '2306', '琼海', '琼海市', '中国,海南省,直辖县级,琼海市', '3', 'qionghai', '0898', '571400', 'Q', '110.466785', '19.246011'); +INSERT INTO `yoshop_region` VALUES ('2309', '2306', '儋州', '儋州市', '中国,海南省,直辖县级,儋州市', '3', 'danzhou', '0898', '571700', null, '109.576782', '19.517486'); +INSERT INTO `yoshop_region` VALUES ('2310', '2306', '文昌', '文昌市', '中国,海南省,直辖县级,文昌市', '3', 'wenchang', '0898', '571339', 'W', '110.753975', '19.612986'); +INSERT INTO `yoshop_region` VALUES ('2311', '2306', '万宁', '万宁市', '中国,海南省,直辖县级,万宁市', '3', 'wanning', '0898', '571500', 'W', '110.388793', '18.796216'); +INSERT INTO `yoshop_region` VALUES ('2312', '2306', '东方', '东方市', '中国,海南省,直辖县级,东方市', '3', 'dongfang', '0898', '572600', 'D', '108.653789', '19.10198'); +INSERT INTO `yoshop_region` VALUES ('2313', '2306', '定安', '定安县', '中国,海南省,直辖县级,定安县', '3', 'ding\'an', '0898', '571200', 'D', '110.323959', '19.699211'); +INSERT INTO `yoshop_region` VALUES ('2314', '2306', '屯昌', '屯昌县', '中国,海南省,直辖县级,屯昌县', '3', 'tunchang', '0898', '571600', 'T', '110.102773', '19.362916'); +INSERT INTO `yoshop_region` VALUES ('2315', '2306', '澄迈', '澄迈县', '中国,海南省,直辖县级,澄迈县', '3', 'chengmai', '0898', '571900', 'C', '110.007147', '19.737095'); +INSERT INTO `yoshop_region` VALUES ('2316', '2306', '临高', '临高县', '中国,海南省,直辖县级,临高县', '3', 'lingao', '0898', '571800', 'L', '109.687697', '19.908293'); +INSERT INTO `yoshop_region` VALUES ('2317', '2306', '白沙', '白沙黎族自治县', '中国,海南省,直辖县级,白沙黎族自治县', '3', 'baisha', '0898', '572800', 'B', '109.452606', '19.224584'); +INSERT INTO `yoshop_region` VALUES ('2318', '2306', '昌江', '昌江黎族自治县', '中国,海南省,直辖县级,昌江黎族自治县', '3', 'changjiang', '0898', '572700', 'C', '109.053351', '19.260968'); +INSERT INTO `yoshop_region` VALUES ('2319', '2306', '乐东', '乐东黎族自治县', '中国,海南省,直辖县级,乐东黎族自治县', '3', 'ledong', '0898', '572500', 'L', '109.175444', '18.74758'); +INSERT INTO `yoshop_region` VALUES ('2320', '2306', '陵水', '陵水黎族自治县', '中国,海南省,直辖县级,陵水黎族自治县', '3', 'lingshui', '0898', '572400', 'L', '110.037218', '18.505006'); +INSERT INTO `yoshop_region` VALUES ('2321', '2306', '保亭', '保亭黎族苗族自治县', '中国,海南省,直辖县级,保亭黎族苗族自治县', '3', 'baoting', '0898', '572300', 'B', '109.70245', '18.636371'); +INSERT INTO `yoshop_region` VALUES ('2322', '2306', '琼中', '琼中黎族苗族自治县', '中国,海南省,直辖县级,琼中黎族苗族自治县', '3', 'qiongzhong', '0898', '572900', 'Q', '109.839996', '19.03557'); +INSERT INTO `yoshop_region` VALUES ('2323', '0', '重庆', '重庆市', '中国,重庆', '1', 'chongqing', '', '', 'Z', '106.504962', '29.533155'); +INSERT INTO `yoshop_region` VALUES ('2324', '2323', '重庆', '重庆市', '中国,重庆,重庆市', '2', 'chongqing', '023', '400000', 'Z', '106.504962', '29.533155'); +INSERT INTO `yoshop_region` VALUES ('2325', '2324', '万州', '万州区', '中国,重庆,重庆市,万州区', '3', 'wanzhou', '023', '404000', 'W', '108.40869', '30.80788'); +INSERT INTO `yoshop_region` VALUES ('2326', '2324', '涪陵', '涪陵区', '中国,重庆,重庆市,涪陵区', '3', 'fuling', '023', '408000', 'F', '107.39007', '29.70292'); +INSERT INTO `yoshop_region` VALUES ('2327', '2324', '渝中', '渝中区', '中国,重庆,重庆市,渝中区', '3', 'yuzhong', '023', '400010', 'Y', '106.56901', '29.55279'); +INSERT INTO `yoshop_region` VALUES ('2328', '2324', '大渡口', '大渡口区', '中国,重庆,重庆市,大渡口区', '3', 'dadukou', '023', '400080', 'D', '106.48262', '29.48447'); +INSERT INTO `yoshop_region` VALUES ('2329', '2324', '江北', '江北区', '中国,重庆,重庆市,江北区', '3', 'jiangbei', '023', '400020', 'J', '106.57434', '29.60658'); +INSERT INTO `yoshop_region` VALUES ('2330', '2324', '沙坪坝', '沙坪坝区', '中国,重庆,重庆市,沙坪坝区', '3', 'shapingba', '023', '400030', 'S', '106.45752', '29.54113'); +INSERT INTO `yoshop_region` VALUES ('2331', '2324', '九龙坡', '九龙坡区', '中国,重庆,重庆市,九龙坡区', '3', 'jiulongpo', '023', '400050', 'J', '106.51107', '29.50197'); +INSERT INTO `yoshop_region` VALUES ('2332', '2324', '南岸', '南岸区', '中国,重庆,重庆市,南岸区', '3', 'nan\'an', '023', '400064', 'N', '106.56347', '29.52311'); +INSERT INTO `yoshop_region` VALUES ('2333', '2324', '北碚', '北碚区', '中国,重庆,重庆市,北碚区', '3', 'beibei', '023', '400700', 'B', '106.39614', '29.80574'); +INSERT INTO `yoshop_region` VALUES ('2334', '2324', '綦江', '綦江区', '中国,重庆,重庆市,綦江区', '3', 'qijiang', '023', '400800', null, '106.926779', '28.960656'); +INSERT INTO `yoshop_region` VALUES ('2335', '2324', '大足', '大足区', '中国,重庆,重庆市,大足区', '3', 'dazu', '023', '400900', 'D', '105.768121', '29.484025'); +INSERT INTO `yoshop_region` VALUES ('2336', '2324', '渝北', '渝北区', '中国,重庆,重庆市,渝北区', '3', 'yubei', '023', '401120', 'Y', '106.6307', '29.7182'); +INSERT INTO `yoshop_region` VALUES ('2337', '2324', '巴南', '巴南区', '中国,重庆,重庆市,巴南区', '3', 'banan', '023', '401320', 'B', '106.52365', '29.38311'); +INSERT INTO `yoshop_region` VALUES ('2338', '2324', '黔江', '黔江区', '中国,重庆,重庆市,黔江区', '3', 'qianjiang', '023', '409700', 'Q', '108.7709', '29.5332'); +INSERT INTO `yoshop_region` VALUES ('2339', '2324', '长寿', '长寿区', '中国,重庆,重庆市,长寿区', '3', 'changshou', '023', '401220', 'C', '107.08166', '29.85359'); +INSERT INTO `yoshop_region` VALUES ('2340', '2324', '江津', '江津区', '中国,重庆,重庆市,江津区', '3', 'jiangjin', '023', '402260', 'J', '106.25912', '29.29008'); +INSERT INTO `yoshop_region` VALUES ('2341', '2324', '合川', '合川区', '中国,重庆,重庆市,合川区', '3', 'hechuan', '023', '401520', 'H', '106.27633', '29.97227'); +INSERT INTO `yoshop_region` VALUES ('2342', '2324', '永川', '永川区', '中国,重庆,重庆市,永川区', '3', 'yongchuan', '023', '402160', 'Y', '105.927', '29.35593'); +INSERT INTO `yoshop_region` VALUES ('2343', '2324', '南川', '南川区', '中国,重庆,重庆市,南川区', '3', 'nanchuan', '023', '408400', 'N', '107.09936', '29.15751'); +INSERT INTO `yoshop_region` VALUES ('2344', '2324', '璧山', '璧山区', '中国,重庆,重庆市,璧山区', '3', 'bishan', '023', '402760', null, '106.231126', '29.593581'); +INSERT INTO `yoshop_region` VALUES ('2345', '2324', '铜梁', '铜梁区', '中国,重庆,重庆市,铜梁区', '3', 'tongliang', '023', '402560', 'T', '106.054948', '29.839944'); +INSERT INTO `yoshop_region` VALUES ('2346', '2324', '潼南', '潼南区', '中国,重庆,重庆市,潼南区', '3', 'tongnan', '023', '402660', null, '105.84005', '30.1912'); +INSERT INTO `yoshop_region` VALUES ('2347', '2324', '荣昌', '荣昌区', '中国,重庆,重庆市,荣昌区', '3', 'rongchang', '023', '402460', 'R', '105.59442', '29.40488'); +INSERT INTO `yoshop_region` VALUES ('2348', '2324', '梁平', '梁平区', '中国,重庆,重庆市,梁平区', '3', 'liangping', '023', '405200', 'L', '107.79998', '30.67545'); +INSERT INTO `yoshop_region` VALUES ('2349', '2363', '城口', '城口县', '中国,重庆,重庆市,城口县', '3', 'chengkou', '023', '405900', 'C', '108.66513', '31.94801'); +INSERT INTO `yoshop_region` VALUES ('2350', '2363', '丰都', '丰都县', '中国,重庆,重庆市,丰都县', '3', 'fengdu', '023', '408200', 'F', '107.73098', '29.86348'); +INSERT INTO `yoshop_region` VALUES ('2351', '2363', '垫江', '垫江县', '中国,重庆,重庆市,垫江县', '3', 'dianjiang', '023', '408300', 'D', '107.35446', '30.33359'); +INSERT INTO `yoshop_region` VALUES ('2352', '2363', '武隆', '武隆县', '中国,重庆,重庆市,武隆县', '3', 'wulong', '023', '408500', 'W', '107.7601', '29.32548'); +INSERT INTO `yoshop_region` VALUES ('2353', '2363', '忠县', '忠县', '中国,重庆,重庆市,忠县', '3', 'zhongxian', '023', '404300', 'Z', '108.03689', '30.28898'); +INSERT INTO `yoshop_region` VALUES ('2354', '2363', '开县', '开县', '中国,重庆,重庆市,开县', '3', 'kaixian', '023', '405400', 'K', '108.39306', '31.16095'); +INSERT INTO `yoshop_region` VALUES ('2355', '2363', '云阳', '云阳县', '中国,重庆,重庆市,云阳县', '3', 'yunyang', '023', '404500', 'Y', '108.69726', '30.93062'); +INSERT INTO `yoshop_region` VALUES ('2356', '2363', '奉节', '奉节县', '中国,重庆,重庆市,奉节县', '3', 'fengjie', '023', '404600', 'F', '109.46478', '31.01825'); +INSERT INTO `yoshop_region` VALUES ('2357', '2363', '巫山', '巫山县', '中国,重庆,重庆市,巫山县', '3', 'wushan', '023', '404700', 'W', '109.87814', '31.07458'); +INSERT INTO `yoshop_region` VALUES ('2358', '2363', '巫溪', '巫溪县', '中国,重庆,重庆市,巫溪县', '3', 'wuxi', '023', '405800', 'W', '109.63128', '31.39756'); +INSERT INTO `yoshop_region` VALUES ('2359', '2363', '石柱', '石柱土家族自治县', '中国,重庆,重庆市,石柱土家族自治县', '3', 'shizhu', '023', '409100', 'S', '108.11389', '30.00054'); +INSERT INTO `yoshop_region` VALUES ('2360', '2363', '秀山', '秀山土家族苗族自治县', '中国,重庆,重庆市,秀山土家族苗族自治县', '3', 'xiushan', '023', '409900', 'X', '108.98861', '28.45062'); +INSERT INTO `yoshop_region` VALUES ('2361', '2363', '酉阳', '酉阳土家族苗族自治县', '中国,重庆,重庆市,酉阳土家族苗族自治县', '3', 'youyang', '023', '409800', 'Y', '108.77212', '28.8446'); +INSERT INTO `yoshop_region` VALUES ('2362', '2363', '彭水', '彭水苗族土家族自治县', '中国,重庆,重庆市,彭水苗族土家族自治县', '3', 'pengshui', '023', '409600', 'P', '108.16638', '29.29516'); +INSERT INTO `yoshop_region` VALUES ('2363', '2323', '县', '县', '中国,重庆,县', '2', 'xian', '023', '400000', 'L', '106.463344', '29.729153'); +INSERT INTO `yoshop_region` VALUES ('2367', '0', '四川', '四川省', '中国,四川省', '1', 'sichuan', '', '', 'S', '104.065735', '30.659462'); +INSERT INTO `yoshop_region` VALUES ('2368', '2367', '成都', '成都市', '中国,四川省,成都市', '2', 'chengdu', '028', '610015', 'C', '104.065735', '30.659462'); +INSERT INTO `yoshop_region` VALUES ('2369', '2368', '锦江', '锦江区', '中国,四川省,成都市,锦江区', '3', 'jinjiang', '028', '610021', 'J', '104.08347', '30.65614'); +INSERT INTO `yoshop_region` VALUES ('2370', '2368', '青羊', '青羊区', '中国,四川省,成都市,青羊区', '3', 'qingyang', '028', '610031', 'Q', '104.06151', '30.67387'); +INSERT INTO `yoshop_region` VALUES ('2371', '2368', '金牛', '金牛区', '中国,四川省,成都市,金牛区', '3', 'jinniu', '028', '610036', 'J', '104.05114', '30.69126'); +INSERT INTO `yoshop_region` VALUES ('2372', '2368', '武侯', '武侯区', '中国,四川省,成都市,武侯区', '3', 'wuhou', '028', '610041', 'W', '104.04303', '30.64235'); +INSERT INTO `yoshop_region` VALUES ('2373', '2368', '成华', '成华区', '中国,四川省,成都市,成华区', '3', 'chenghua', '028', '610066', 'C', '104.10193', '30.65993'); +INSERT INTO `yoshop_region` VALUES ('2374', '2368', '龙泉驿', '龙泉驿区', '中国,四川省,成都市,龙泉驿区', '3', 'longquanyi', '028', '610100', 'L', '104.27462', '30.55658'); +INSERT INTO `yoshop_region` VALUES ('2375', '2368', '青白江', '青白江区', '中国,四川省,成都市,青白江区', '3', 'qingbaijiang', '028', '610300', 'Q', '104.251', '30.87841'); +INSERT INTO `yoshop_region` VALUES ('2376', '2368', '新都', '新都区', '中国,四川省,成都市,新都区', '3', 'xindu', '028', '610500', 'X', '104.15921', '30.82314'); +INSERT INTO `yoshop_region` VALUES ('2377', '2368', '温江', '温江区', '中国,四川省,成都市,温江区', '3', 'wenjiang', '028', '611130', 'W', '103.84881', '30.68444'); +INSERT INTO `yoshop_region` VALUES ('2378', '2368', '金堂', '金堂县', '中国,四川省,成都市,金堂县', '3', 'jintang', '028', '610400', 'J', '104.41195', '30.86195'); +INSERT INTO `yoshop_region` VALUES ('2379', '2368', '双流', '双流县', '中国,四川省,成都市,双流县', '3', 'shuangliu', '028', '610200', 'S', '103.92373', '30.57444'); +INSERT INTO `yoshop_region` VALUES ('2380', '2368', '郫县', '郫县', '中国,四川省,成都市,郫县', '3', 'pixian', '028', '611730', null, '103.88717', '30.81054'); +INSERT INTO `yoshop_region` VALUES ('2381', '2368', '大邑', '大邑县', '中国,四川省,成都市,大邑县', '3', 'dayi', '028', '611330', 'D', '103.52075', '30.58738'); +INSERT INTO `yoshop_region` VALUES ('2382', '2368', '蒲江', '蒲江县', '中国,四川省,成都市,蒲江县', '3', 'pujiang', '028', '611630', 'P', '103.50616', '30.19667'); +INSERT INTO `yoshop_region` VALUES ('2383', '2368', '新津', '新津县', '中国,四川省,成都市,新津县', '3', 'xinjin', '028', '611430', 'X', '103.8114', '30.40983'); +INSERT INTO `yoshop_region` VALUES ('2384', '2368', '都江堰', '都江堰市', '中国,四川省,成都市,都江堰市', '3', 'dujiangyan', '028', '611830', 'D', '103.61941', '30.99825'); +INSERT INTO `yoshop_region` VALUES ('2385', '2368', '彭州', '彭州市', '中国,四川省,成都市,彭州市', '3', 'pengzhou', '028', '611930', 'P', '103.958', '30.99011'); +INSERT INTO `yoshop_region` VALUES ('2386', '2368', '邛崃', '邛崃市', '中国,四川省,成都市,邛崃市', '3', 'qionglai', '028', '611530', null, '103.46283', '30.41489'); +INSERT INTO `yoshop_region` VALUES ('2387', '2368', '崇州', '崇州市', '中国,四川省,成都市,崇州市', '3', 'chongzhou', '028', '611230', 'C', '103.67285', '30.63014'); +INSERT INTO `yoshop_region` VALUES ('2388', '2367', '自贡', '自贡市', '中国,四川省,自贡市', '2', 'zigong', '0813', '643000', 'Z', '104.773447', '29.352765'); +INSERT INTO `yoshop_region` VALUES ('2389', '2388', '自流井', '自流井区', '中国,四川省,自贡市,自流井区', '3', 'ziliujing', '0813', '643000', 'Z', '104.77719', '29.33745'); +INSERT INTO `yoshop_region` VALUES ('2390', '2388', '贡井', '贡井区', '中国,四川省,自贡市,贡井区', '3', 'gongjing', '0813', '643020', 'G', '104.71536', '29.34576'); +INSERT INTO `yoshop_region` VALUES ('2391', '2388', '大安', '大安区', '中国,四川省,自贡市,大安区', '3', 'da\'an', '0813', '643010', 'D', '104.77383', '29.36364'); +INSERT INTO `yoshop_region` VALUES ('2392', '2388', '沿滩', '沿滩区', '中国,四川省,自贡市,沿滩区', '3', 'yantan', '0813', '643030', 'Y', '104.88012', '29.26611'); +INSERT INTO `yoshop_region` VALUES ('2393', '2388', '荣县', '荣县', '中国,四川省,自贡市,荣县', '3', 'rongxian', '0813', '643100', 'R', '104.4176', '29.44445'); +INSERT INTO `yoshop_region` VALUES ('2394', '2388', '富顺', '富顺县', '中国,四川省,自贡市,富顺县', '3', 'fushun', '0813', '643200', 'F', '104.97491', '29.18123'); +INSERT INTO `yoshop_region` VALUES ('2395', '2367', '攀枝花', '攀枝花市', '中国,四川省,攀枝花市', '2', 'panzhihua', '0812', '617000', 'P', '101.716007', '26.580446'); +INSERT INTO `yoshop_region` VALUES ('2396', '2395', '东区', '东区', '中国,四川省,攀枝花市,东区', '3', 'dongqu', '0812', '617067', 'D', '101.7052', '26.54677'); +INSERT INTO `yoshop_region` VALUES ('2397', '2395', '西区', '西区', '中国,四川省,攀枝花市,西区', '3', 'xiqu', '0812', '617068', 'X', '101.63058', '26.59753'); +INSERT INTO `yoshop_region` VALUES ('2398', '2395', '仁和', '仁和区', '中国,四川省,攀枝花市,仁和区', '3', 'renhe', '0812', '617061', 'R', '101.73812', '26.49841'); +INSERT INTO `yoshop_region` VALUES ('2399', '2395', '米易', '米易县', '中国,四川省,攀枝花市,米易县', '3', 'miyi', '0812', '617200', 'M', '102.11132', '26.88718'); +INSERT INTO `yoshop_region` VALUES ('2400', '2395', '盐边', '盐边县', '中国,四川省,攀枝花市,盐边县', '3', 'yanbian', '0812', '617100', 'Y', '101.85446', '26.68847'); +INSERT INTO `yoshop_region` VALUES ('2401', '2367', '泸州', '泸州市', '中国,四川省,泸州市', '2', 'luzhou', '0830', '646000', null, '105.443348', '28.889138'); +INSERT INTO `yoshop_region` VALUES ('2402', '2401', '江阳', '江阳区', '中国,四川省,泸州市,江阳区', '3', 'jiangyang', '0830', '646000', 'J', '105.45336', '28.88934'); +INSERT INTO `yoshop_region` VALUES ('2403', '2401', '纳溪', '纳溪区', '中国,四川省,泸州市,纳溪区', '3', 'naxi', '0830', '646300', 'N', '105.37255', '28.77343'); +INSERT INTO `yoshop_region` VALUES ('2404', '2401', '龙马潭', '龙马潭区', '中国,四川省,泸州市,龙马潭区', '3', 'longmatan', '0830', '646000', 'L', '105.43774', '28.91308'); +INSERT INTO `yoshop_region` VALUES ('2405', '2401', '泸县', '泸县', '中国,四川省,泸州市,泸县', '3', 'luxian', '0830', '646106', null, '105.38192', '29.15041'); +INSERT INTO `yoshop_region` VALUES ('2406', '2401', '合江', '合江县', '中国,四川省,泸州市,合江县', '3', 'hejiang', '0830', '646200', 'H', '105.8352', '28.81005'); +INSERT INTO `yoshop_region` VALUES ('2407', '2401', '叙永', '叙永县', '中国,四川省,泸州市,叙永县', '3', 'xuyong', '0830', '646400', 'X', '105.44473', '28.15586'); +INSERT INTO `yoshop_region` VALUES ('2408', '2401', '古蔺', '古蔺县', '中国,四川省,泸州市,古蔺县', '3', 'gulin', '0830', '646500', 'G', '105.81347', '28.03867'); +INSERT INTO `yoshop_region` VALUES ('2409', '2367', '德阳', '德阳市', '中国,四川省,德阳市', '2', 'deyang', '0838', '618000', 'D', '104.398651', '31.127991'); +INSERT INTO `yoshop_region` VALUES ('2410', '2409', '旌阳', '旌阳区', '中国,四川省,德阳市,旌阳区', '3', 'jingyang', '0838', '618000', null, '104.39367', '31.13906'); +INSERT INTO `yoshop_region` VALUES ('2411', '2409', '中江', '中江县', '中国,四川省,德阳市,中江县', '3', 'zhongjiang', '0838', '618100', 'Z', '104.67861', '31.03297'); +INSERT INTO `yoshop_region` VALUES ('2412', '2409', '罗江', '罗江县', '中国,四川省,德阳市,罗江县', '3', 'luojiang', '0838', '618500', 'L', '104.51025', '31.31665'); +INSERT INTO `yoshop_region` VALUES ('2413', '2409', '广汉', '广汉市', '中国,四川省,德阳市,广汉市', '3', 'guanghan', '0838', '618300', 'G', '104.28234', '30.97686'); +INSERT INTO `yoshop_region` VALUES ('2414', '2409', '什邡', '什邡市', '中国,四川省,德阳市,什邡市', '3', 'shifang', '0838', '618400', 'S', '104.16754', '31.1264'); +INSERT INTO `yoshop_region` VALUES ('2415', '2409', '绵竹', '绵竹市', '中国,四川省,德阳市,绵竹市', '3', 'mianzhu', '0838', '618200', 'M', '104.22076', '31.33772'); +INSERT INTO `yoshop_region` VALUES ('2416', '2367', '绵阳', '绵阳市', '中国,四川省,绵阳市', '2', 'mianyang', '0816', '621000', 'M', '104.741722', '31.46402'); +INSERT INTO `yoshop_region` VALUES ('2417', '2416', '涪城', '涪城区', '中国,四川省,绵阳市,涪城区', '3', 'fucheng', '0816', '621000', 'F', '104.75719', '31.45522'); +INSERT INTO `yoshop_region` VALUES ('2418', '2416', '游仙', '游仙区', '中国,四川省,绵阳市,游仙区', '3', 'youxian', '0816', '621022', 'Y', '104.77092', '31.46574'); +INSERT INTO `yoshop_region` VALUES ('2419', '2416', '三台', '三台县', '中国,四川省,绵阳市,三台县', '3', 'santai', '0816', '621100', 'S', '105.09079', '31.09179'); +INSERT INTO `yoshop_region` VALUES ('2420', '2416', '盐亭', '盐亭县', '中国,四川省,绵阳市,盐亭县', '3', 'yanting', '0816', '621600', 'Y', '105.3898', '31.22176'); +INSERT INTO `yoshop_region` VALUES ('2421', '2416', '安县', '安县', '中国,四川省,绵阳市,安县', '3', 'anxian', '0816', '622650', 'A', '104.56738', '31.53487'); +INSERT INTO `yoshop_region` VALUES ('2422', '2416', '梓潼', '梓潼县', '中国,四川省,绵阳市,梓潼县', '3', 'zitong', '0816', '622150', null, '105.16183', '31.6359'); +INSERT INTO `yoshop_region` VALUES ('2423', '2416', '北川', '北川羌族自治县', '中国,四川省,绵阳市,北川羌族自治县', '3', 'beichuan', '0816', '622750', 'B', '104.46408', '31.83286'); +INSERT INTO `yoshop_region` VALUES ('2424', '2416', '平武', '平武县', '中国,四川省,绵阳市,平武县', '3', 'pingwu', '0816', '622550', 'P', '104.52862', '32.40791'); +INSERT INTO `yoshop_region` VALUES ('2425', '2416', '江油', '江油市', '中国,四川省,绵阳市,江油市', '3', 'jiangyou', '0816', '621700', 'J', '104.74539', '31.77775'); +INSERT INTO `yoshop_region` VALUES ('2426', '2367', '广元', '广元市', '中国,四川省,广元市', '2', 'guangyuan', '0839', '628000', 'G', '105.829757', '32.433668'); +INSERT INTO `yoshop_region` VALUES ('2427', '2426', '利州', '利州区', '中国,四川省,广元市,利州区', '3', 'lizhou', '0839', '628017', 'L', '105.826194', '32.432276'); +INSERT INTO `yoshop_region` VALUES ('2428', '2426', '昭化', '昭化区', '中国,四川省,广元市,昭化区', '3', 'zhaohua', '0839', '628017', 'Z', '105.640491', '32.386518'); +INSERT INTO `yoshop_region` VALUES ('2429', '2426', '朝天', '朝天区', '中国,四川省,广元市,朝天区', '3', 'chaotian', '0839', '628017', 'C', '105.89273', '32.64398'); +INSERT INTO `yoshop_region` VALUES ('2430', '2426', '旺苍', '旺苍县', '中国,四川省,广元市,旺苍县', '3', 'wangcang', '0839', '628200', 'W', '106.29022', '32.22845'); +INSERT INTO `yoshop_region` VALUES ('2431', '2426', '青川', '青川县', '中国,四川省,广元市,青川县', '3', 'qingchuan', '0839', '628100', 'Q', '105.2391', '32.58563'); +INSERT INTO `yoshop_region` VALUES ('2432', '2426', '剑阁', '剑阁县', '中国,四川省,广元市,剑阁县', '3', 'jiange', '0839', '628300', 'J', '105.5252', '32.28845'); +INSERT INTO `yoshop_region` VALUES ('2433', '2426', '苍溪', '苍溪县', '中国,四川省,广元市,苍溪县', '3', 'cangxi', '0839', '628400', 'C', '105.936', '31.73209'); +INSERT INTO `yoshop_region` VALUES ('2434', '2367', '遂宁', '遂宁市', '中国,四川省,遂宁市', '2', 'suining', '0825', '629000', 'S', '105.571331', '30.513311'); +INSERT INTO `yoshop_region` VALUES ('2435', '2434', '船山', '船山区', '中国,四川省,遂宁市,船山区', '3', 'chuanshan', '0825', '629000', 'C', '105.5809', '30.49991'); +INSERT INTO `yoshop_region` VALUES ('2436', '2434', '安居', '安居区', '中国,四川省,遂宁市,安居区', '3', 'anju', '0825', '629000', 'A', '105.46402', '30.35778'); +INSERT INTO `yoshop_region` VALUES ('2437', '2434', '蓬溪', '蓬溪县', '中国,四川省,遂宁市,蓬溪县', '3', 'pengxi', '0825', '629100', 'P', '105.70752', '30.75775'); +INSERT INTO `yoshop_region` VALUES ('2438', '2434', '射洪', '射洪县', '中国,四川省,遂宁市,射洪县', '3', 'shehong', '0825', '629200', 'S', '105.38922', '30.87203'); +INSERT INTO `yoshop_region` VALUES ('2439', '2434', '大英', '大英县', '中国,四川省,遂宁市,大英县', '3', 'daying', '0825', '629300', 'D', '105.24346', '30.59434'); +INSERT INTO `yoshop_region` VALUES ('2440', '2367', '内江', '内江市', '中国,四川省,内江市', '2', 'neijiang', '0832', '641000', 'N', '105.066138', '29.58708'); +INSERT INTO `yoshop_region` VALUES ('2441', '2440', '市中区', '市中区', '中国,四川省,内江市,市中区', '3', 'shizhongqu', '0832', '641000', 'S', '105.0679', '29.58709'); +INSERT INTO `yoshop_region` VALUES ('2442', '2440', '东兴', '东兴区', '中国,四川省,内江市,东兴区', '3', 'dongxing', '0832', '641100', 'D', '105.07554', '29.59278'); +INSERT INTO `yoshop_region` VALUES ('2443', '2440', '威远', '威远县', '中国,四川省,内江市,威远县', '3', 'weiyuan', '0832', '642450', 'W', '104.66955', '29.52823'); +INSERT INTO `yoshop_region` VALUES ('2444', '2440', '资中', '资中县', '中国,四川省,内江市,资中县', '3', 'zizhong', '0832', '641200', 'Z', '104.85205', '29.76409'); +INSERT INTO `yoshop_region` VALUES ('2445', '2440', '隆昌', '隆昌县', '中国,四川省,内江市,隆昌县', '3', 'longchang', '0832', '642150', 'L', '105.28738', '29.33937'); +INSERT INTO `yoshop_region` VALUES ('2446', '2367', '乐山', '乐山市', '中国,四川省,乐山市', '2', 'leshan', '0833', '614000', 'L', '103.761263', '29.582024'); +INSERT INTO `yoshop_region` VALUES ('2447', '2446', '市中区', '市中区', '中国,四川省,乐山市,市中区', '3', 'shizhongqu', '0833', '614000', 'S', '103.76159', '29.55543'); +INSERT INTO `yoshop_region` VALUES ('2448', '2446', '沙湾', '沙湾区', '中国,四川省,乐山市,沙湾区', '3', 'shawan', '0833', '614900', 'S', '103.54873', '29.41194'); +INSERT INTO `yoshop_region` VALUES ('2449', '2446', '五通桥', '五通桥区', '中国,四川省,乐山市,五通桥区', '3', 'wutongqiao', '0833', '614800', 'W', '103.82345', '29.40344'); +INSERT INTO `yoshop_region` VALUES ('2450', '2446', '金口河', '金口河区', '中国,四川省,乐山市,金口河区', '3', 'jinkouhe', '0833', '614700', 'J', '103.07858', '29.24578'); +INSERT INTO `yoshop_region` VALUES ('2451', '2446', '犍为', '犍为县', '中国,四川省,乐山市,犍为县', '3', 'qianwei', '0833', '614400', null, '103.94989', '29.20973'); +INSERT INTO `yoshop_region` VALUES ('2452', '2446', '井研', '井研县', '中国,四川省,乐山市,井研县', '3', 'jingyan', '0833', '613100', 'J', '104.07019', '29.65228'); +INSERT INTO `yoshop_region` VALUES ('2453', '2446', '夹江', '夹江县', '中国,四川省,乐山市,夹江县', '3', 'jiajiang', '0833', '614100', 'J', '103.57199', '29.73868'); +INSERT INTO `yoshop_region` VALUES ('2454', '2446', '沐川', '沐川县', '中国,四川省,乐山市,沐川县', '3', 'muchuan', '0833', '614500', null, '103.90353', '28.95646'); +INSERT INTO `yoshop_region` VALUES ('2455', '2446', '峨边', '峨边彝族自治县', '中国,四川省,乐山市,峨边彝族自治县', '3', 'ebian', '0833', '614300', 'E', '103.26339', '29.23004'); +INSERT INTO `yoshop_region` VALUES ('2456', '2446', '马边', '马边彝族自治县', '中国,四川省,乐山市,马边彝族自治县', '3', 'mabian', '0833', '614600', 'M', '103.54617', '28.83593'); +INSERT INTO `yoshop_region` VALUES ('2457', '2446', '峨眉山', '峨眉山市', '中国,四川省,乐山市,峨眉山市', '3', 'emeishan', '0833', '614200', 'E', '103.4844', '29.60117'); +INSERT INTO `yoshop_region` VALUES ('2458', '2367', '南充', '南充市', '中国,四川省,南充市', '2', 'nanchong', '0817', '637000', 'N', '106.082974', '30.795281'); +INSERT INTO `yoshop_region` VALUES ('2459', '2458', '顺庆', '顺庆区', '中国,四川省,南充市,顺庆区', '3', 'shunqing', '0817', '637000', 'S', '106.09216', '30.79642'); +INSERT INTO `yoshop_region` VALUES ('2460', '2458', '高坪', '高坪区', '中国,四川省,南充市,高坪区', '3', 'gaoping', '0817', '637100', 'G', '106.11894', '30.78162'); +INSERT INTO `yoshop_region` VALUES ('2461', '2458', '嘉陵', '嘉陵区', '中国,四川省,南充市,嘉陵区', '3', 'jialing', '0817', '637100', 'J', '106.07141', '30.75848'); +INSERT INTO `yoshop_region` VALUES ('2462', '2458', '南部', '南部县', '中国,四川省,南充市,南部县', '3', 'nanbu', '0817', '637300', 'N', '106.06738', '31.35451'); +INSERT INTO `yoshop_region` VALUES ('2463', '2458', '营山', '营山县', '中国,四川省,南充市,营山县', '3', 'yingshan', '0817', '637700', 'Y', '106.56637', '31.07747'); +INSERT INTO `yoshop_region` VALUES ('2464', '2458', '蓬安', '蓬安县', '中国,四川省,南充市,蓬安县', '3', 'peng\'an', '0817', '637800', 'P', '106.41262', '31.02964'); +INSERT INTO `yoshop_region` VALUES ('2465', '2458', '仪陇', '仪陇县', '中国,四川省,南充市,仪陇县', '3', 'yilong', '0817', '637600', 'Y', '106.29974', '31.27628'); +INSERT INTO `yoshop_region` VALUES ('2466', '2458', '西充', '西充县', '中国,四川省,南充市,西充县', '3', 'xichong', '0817', '637200', 'X', '105.89996', '30.9969'); +INSERT INTO `yoshop_region` VALUES ('2467', '2458', '阆中', '阆中市', '中国,四川省,南充市,阆中市', '3', 'langzhong', '0817', '637400', null, '106.00494', '31.55832'); +INSERT INTO `yoshop_region` VALUES ('2468', '2367', '眉山', '眉山市', '中国,四川省,眉山市', '2', 'meishan', '028', '620020', 'M', '103.831788', '30.048318'); +INSERT INTO `yoshop_region` VALUES ('2469', '2468', '东坡', '东坡区', '中国,四川省,眉山市,东坡区', '3', 'dongpo', '028', '620010', 'D', '103.832', '30.04219'); +INSERT INTO `yoshop_region` VALUES ('2470', '2468', '彭山', '彭山区', '中国,四川省,眉山市,彭山区', '3', 'pengshan', '028', '620860', 'P', '103.87268', '30.19283'); +INSERT INTO `yoshop_region` VALUES ('2471', '2468', '仁寿', '仁寿县', '中国,四川省,眉山市,仁寿县', '3', 'renshou', '028', '620500', 'R', '104.13412', '29.99599'); +INSERT INTO `yoshop_region` VALUES ('2472', '2468', '洪雅', '洪雅县', '中国,四川省,眉山市,洪雅县', '3', 'hongya', '028', '620360', 'H', '103.37313', '29.90661'); +INSERT INTO `yoshop_region` VALUES ('2473', '2468', '丹棱', '丹棱县', '中国,四川省,眉山市,丹棱县', '3', 'danling', '028', '620200', 'D', '103.51339', '30.01562'); +INSERT INTO `yoshop_region` VALUES ('2474', '2468', '青神', '青神县', '中国,四川省,眉山市,青神县', '3', 'qingshen', '028', '620460', 'Q', '103.84771', '29.83235'); +INSERT INTO `yoshop_region` VALUES ('2475', '2367', '宜宾', '宜宾市', '中国,四川省,宜宾市', '2', 'yibin', '0831', '644000', 'Y', '104.630825', '28.760189'); +INSERT INTO `yoshop_region` VALUES ('2476', '2475', '翠屏', '翠屏区', '中国,四川省,宜宾市,翠屏区', '3', 'cuiping', '0831', '644000', 'C', '104.61978', '28.76566'); +INSERT INTO `yoshop_region` VALUES ('2477', '2475', '南溪', '南溪区', '中国,四川省,宜宾市,南溪区', '3', 'nanxi', '0831', '644100', 'N', '104.981133', '28.839806'); +INSERT INTO `yoshop_region` VALUES ('2478', '2475', '宜宾', '宜宾县', '中国,四川省,宜宾市,宜宾县', '3', 'yibin', '0831', '644600', 'Y', '104.53314', '28.68996'); +INSERT INTO `yoshop_region` VALUES ('2479', '2475', '江安', '江安县', '中国,四川省,宜宾市,江安县', '3', 'jiang\'an', '0831', '644200', 'J', '105.06683', '28.72385'); +INSERT INTO `yoshop_region` VALUES ('2480', '2475', '长宁', '长宁县', '中国,四川省,宜宾市,长宁县', '3', 'changning', '0831', '644300', 'C', '104.9252', '28.57777'); +INSERT INTO `yoshop_region` VALUES ('2481', '2475', '高县', '高县', '中国,四川省,宜宾市,高县', '3', 'gaoxian', '0831', '645150', 'G', '104.51754', '28.43619'); +INSERT INTO `yoshop_region` VALUES ('2482', '2475', '珙县', '珙县', '中国,四川省,宜宾市,珙县', '3', 'gongxian', '0831', '644500', null, '104.71398', '28.44512'); +INSERT INTO `yoshop_region` VALUES ('2483', '2475', '筠连', '筠连县', '中国,四川省,宜宾市,筠连县', '3', 'junlian', '0831', '645250', null, '104.51217', '28.16495'); +INSERT INTO `yoshop_region` VALUES ('2484', '2475', '兴文', '兴文县', '中国,四川省,宜宾市,兴文县', '3', 'xingwen', '0831', '644400', 'X', '105.23675', '28.3044'); +INSERT INTO `yoshop_region` VALUES ('2485', '2475', '屏山', '屏山县', '中国,四川省,宜宾市,屏山县', '3', 'pingshan', '0831', '645350', 'P', '104.16293', '28.64369'); +INSERT INTO `yoshop_region` VALUES ('2486', '2367', '广安', '广安市', '中国,四川省,广安市', '2', 'guang\'an', '0826', '638000', 'G', '106.633369', '30.456398'); +INSERT INTO `yoshop_region` VALUES ('2487', '2486', '广安', '广安区', '中国,四川省,广安市,广安区', '3', 'guang\'an', '0826', '638000', 'G', '106.64163', '30.47389'); +INSERT INTO `yoshop_region` VALUES ('2488', '2486', '前锋', '前锋区', '中国,四川省,广安市,前锋区', '3', 'qianfeng', '0826', '638019', 'Q', '106.893537', '30.494572'); +INSERT INTO `yoshop_region` VALUES ('2489', '2486', '岳池', '岳池县', '中国,四川省,广安市,岳池县', '3', 'yuechi', '0826', '638300', 'Y', '106.44079', '30.53918'); +INSERT INTO `yoshop_region` VALUES ('2490', '2486', '武胜', '武胜县', '中国,四川省,广安市,武胜县', '3', 'wusheng', '0826', '638400', 'W', '106.29592', '30.34932'); +INSERT INTO `yoshop_region` VALUES ('2491', '2486', '邻水', '邻水县', '中国,四川省,广安市,邻水县', '3', 'linshui', '0826', '638500', 'L', '106.92968', '30.33449'); +INSERT INTO `yoshop_region` VALUES ('2492', '2486', '华蓥', '华蓥市', '中国,四川省,广安市,华蓥市', '3', 'huaying', '0826', '638600', 'H', '106.78466', '30.39007'); +INSERT INTO `yoshop_region` VALUES ('2493', '2367', '达州', '达州市', '中国,四川省,达州市', '2', 'dazhou', '0818', '635000', 'D', '107.502262', '31.209484'); +INSERT INTO `yoshop_region` VALUES ('2494', '2493', '通川', '通川区', '中国,四川省,达州市,通川区', '3', 'tongchuan', '0818', '635000', 'T', '107.50456', '31.21469'); +INSERT INTO `yoshop_region` VALUES ('2495', '2493', '达川', '达川区', '中国,四川省,达州市,达川区', '3', 'dachuan', '0818', '635000', 'D', '107.502262', '31.209484'); +INSERT INTO `yoshop_region` VALUES ('2496', '2493', '宣汉', '宣汉县', '中国,四川省,达州市,宣汉县', '3', 'xuanhan', '0818', '636150', 'X', '107.72775', '31.35516'); +INSERT INTO `yoshop_region` VALUES ('2497', '2493', '开江', '开江县', '中国,四川省,达州市,开江县', '3', 'kaijiang', '0818', '636250', 'K', '107.86889', '31.0841'); +INSERT INTO `yoshop_region` VALUES ('2498', '2493', '大竹', '大竹县', '中国,四川省,达州市,大竹县', '3', 'dazhu', '0818', '635100', 'D', '107.20855', '30.74147'); +INSERT INTO `yoshop_region` VALUES ('2499', '2493', '渠县', '渠县', '中国,四川省,达州市,渠县', '3', 'quxian', '0818', '635200', 'Q', '106.97381', '30.8376'); +INSERT INTO `yoshop_region` VALUES ('2500', '2493', '万源', '万源市', '中国,四川省,达州市,万源市', '3', 'wanyuan', '0818', '636350', 'W', '108.03598', '32.08091'); +INSERT INTO `yoshop_region` VALUES ('2501', '2367', '雅安', '雅安市', '中国,四川省,雅安市', '2', 'ya\'an', '0835', '625000', 'Y', '103.001033', '29.987722'); +INSERT INTO `yoshop_region` VALUES ('2502', '2501', '雨城', '雨城区', '中国,四川省,雅安市,雨城区', '3', 'yucheng', '0835', '625000', 'Y', '103.03305', '30.00531'); +INSERT INTO `yoshop_region` VALUES ('2503', '2501', '名山', '名山区', '中国,四川省,雅安市,名山区', '3', 'mingshan', '0835', '625100', 'M', '103.112214', '30.084718'); +INSERT INTO `yoshop_region` VALUES ('2504', '2501', '荥经', '荥经县', '中国,四川省,雅安市,荥经县', '3', 'yingjing', '0835', '625200', null, '102.84652', '29.79402'); +INSERT INTO `yoshop_region` VALUES ('2505', '2501', '汉源', '汉源县', '中国,四川省,雅安市,汉源县', '3', 'hanyuan', '0835', '625300', 'H', '102.6784', '29.35168'); +INSERT INTO `yoshop_region` VALUES ('2506', '2501', '石棉', '石棉县', '中国,四川省,雅安市,石棉县', '3', 'shimian', '0835', '625400', 'S', '102.35943', '29.22796'); +INSERT INTO `yoshop_region` VALUES ('2507', '2501', '天全', '天全县', '中国,四川省,雅安市,天全县', '3', 'tianquan', '0835', '625500', 'T', '102.75906', '30.06754'); +INSERT INTO `yoshop_region` VALUES ('2508', '2501', '芦山', '芦山县', '中国,四川省,雅安市,芦山县', '3', 'lushan', '0835', '625600', 'L', '102.92791', '30.14369'); +INSERT INTO `yoshop_region` VALUES ('2509', '2501', '宝兴', '宝兴县', '中国,四川省,雅安市,宝兴县', '3', 'baoxing', '0835', '625700', 'B', '102.81555', '30.36836'); +INSERT INTO `yoshop_region` VALUES ('2510', '2367', '巴中', '巴中市', '中国,四川省,巴中市', '2', 'bazhong', '0827', '636000', 'B', '106.753669', '31.858809'); +INSERT INTO `yoshop_region` VALUES ('2511', '2510', '巴州', '巴州区', '中国,四川省,巴中市,巴州区', '3', 'bazhou', '0827', '636001', 'B', '106.76889', '31.85125'); +INSERT INTO `yoshop_region` VALUES ('2512', '2510', '恩阳', '恩阳区', '中国,四川省,巴中市,恩阳区', '3', 'enyang', '0827', '636064', 'E', '106.753669', '31.858809'); +INSERT INTO `yoshop_region` VALUES ('2513', '2510', '通江', '通江县', '中国,四川省,巴中市,通江县', '3', 'tongjiang', '0827', '636700', 'T', '107.24398', '31.91294'); +INSERT INTO `yoshop_region` VALUES ('2514', '2510', '南江', '南江县', '中国,四川省,巴中市,南江县', '3', 'nanjiang', '0827', '636600', 'N', '106.84164', '32.35335'); +INSERT INTO `yoshop_region` VALUES ('2515', '2510', '平昌', '平昌县', '中国,四川省,巴中市,平昌县', '3', 'pingchang', '0827', '636400', 'P', '107.10424', '31.5594'); +INSERT INTO `yoshop_region` VALUES ('2516', '2367', '资阳', '资阳市', '中国,四川省,资阳市', '2', 'ziyang', '028', '641300', 'Z', '104.641917', '30.122211'); +INSERT INTO `yoshop_region` VALUES ('2517', '2516', '雁江', '雁江区', '中国,四川省,资阳市,雁江区', '3', 'yanjiang', '028', '641300', 'Y', '104.65216', '30.11525'); +INSERT INTO `yoshop_region` VALUES ('2518', '2516', '安岳', '安岳县', '中国,四川省,资阳市,安岳县', '3', 'anyue', '028', '642350', 'A', '105.3363', '30.09786'); +INSERT INTO `yoshop_region` VALUES ('2519', '2516', '乐至', '乐至县', '中国,四川省,资阳市,乐至县', '3', 'lezhi', '028', '641500', 'L', '105.03207', '30.27227'); +INSERT INTO `yoshop_region` VALUES ('2520', '2516', '简阳', '简阳市', '中国,四川省,资阳市,简阳市', '3', 'jianyang', '028', '641400', 'J', '104.54864', '30.3904'); +INSERT INTO `yoshop_region` VALUES ('2521', '2367', '阿坝', '阿坝藏族羌族自治州', '中国,四川省,阿坝藏族羌族自治州', '2', 'aba', '0837', '624000', 'A', '102.221374', '31.899792'); +INSERT INTO `yoshop_region` VALUES ('2522', '2521', '汶川', '汶川县', '中国,四川省,阿坝藏族羌族自治州,汶川县', '3', 'wenchuan', '0837', '623000', null, '103.59079', '31.47326'); +INSERT INTO `yoshop_region` VALUES ('2523', '2521', '理县', '理县', '中国,四川省,阿坝藏族羌族自治州,理县', '3', 'lixian', '0837', '623100', 'L', '103.17175', '31.43603'); +INSERT INTO `yoshop_region` VALUES ('2524', '2521', '茂县', '茂县', '中国,四川省,阿坝藏族羌族自治州,茂县', '3', 'maoxian', '0837', '623200', 'M', '103.85372', '31.682'); +INSERT INTO `yoshop_region` VALUES ('2525', '2521', '松潘', '松潘县', '中国,四川省,阿坝藏族羌族自治州,松潘县', '3', 'songpan', '0837', '623300', 'S', '103.59924', '32.63871'); +INSERT INTO `yoshop_region` VALUES ('2526', '2521', '九寨沟', '九寨沟县', '中国,四川省,阿坝藏族羌族自治州,九寨沟县', '3', 'jiuzhaigou', '0837', '623400', 'J', '104.23672', '33.26318'); +INSERT INTO `yoshop_region` VALUES ('2527', '2521', '金川', '金川县', '中国,四川省,阿坝藏族羌族自治州,金川县', '3', 'jinchuan', '0837', '624100', 'J', '102.06555', '31.47623'); +INSERT INTO `yoshop_region` VALUES ('2528', '2521', '小金', '小金县', '中国,四川省,阿坝藏族羌族自治州,小金县', '3', 'xiaojin', '0837', '624200', 'X', '102.36499', '30.99934'); +INSERT INTO `yoshop_region` VALUES ('2529', '2521', '黑水', '黑水县', '中国,四川省,阿坝藏族羌族自治州,黑水县', '3', 'heishui', '0837', '623500', 'H', '102.99176', '32.06184'); +INSERT INTO `yoshop_region` VALUES ('2530', '2521', '马尔康', '马尔康县', '中国,四川省,阿坝藏族羌族自治州,马尔康县', '3', 'maerkang', '0837', '624000', 'M', '102.20625', '31.90584'); +INSERT INTO `yoshop_region` VALUES ('2531', '2521', '壤塘', '壤塘县', '中国,四川省,阿坝藏族羌族自治州,壤塘县', '3', 'rangtang', '0837', '624300', 'R', '100.9783', '32.26578'); +INSERT INTO `yoshop_region` VALUES ('2532', '2521', '阿坝', '阿坝县', '中国,四川省,阿坝藏族羌族自治州,阿坝县', '3', 'aba', '0837', '624600', 'A', '101.70632', '32.90301'); +INSERT INTO `yoshop_region` VALUES ('2533', '2521', '若尔盖', '若尔盖县', '中国,四川省,阿坝藏族羌族自治州,若尔盖县', '3', 'ruoergai', '0837', '624500', 'R', '102.9598', '33.57432'); +INSERT INTO `yoshop_region` VALUES ('2534', '2521', '红原', '红原县', '中国,四川省,阿坝藏族羌族自治州,红原县', '3', 'hongyuan', '0837', '624400', 'H', '102.54525', '32.78989'); +INSERT INTO `yoshop_region` VALUES ('2535', '2367', '甘孜', '甘孜藏族自治州', '中国,四川省,甘孜藏族自治州', '2', 'garze', '0836', '626000', 'G', '101.963815', '30.050663'); +INSERT INTO `yoshop_region` VALUES ('2536', '2535', '康定', '康定县', '中国,四川省,甘孜藏族自治州,康定县', '3', 'kangding', '0836', '626000', 'K', '101.96487', '30.05532'); +INSERT INTO `yoshop_region` VALUES ('2537', '2535', '泸定', '泸定县', '中国,四川省,甘孜藏族自治州,泸定县', '3', 'luding', '0836', '626100', null, '102.23507', '29.91475'); +INSERT INTO `yoshop_region` VALUES ('2538', '2535', '丹巴', '丹巴县', '中国,四川省,甘孜藏族自治州,丹巴县', '3', 'danba', '0836', '626300', 'D', '101.88424', '30.87656'); +INSERT INTO `yoshop_region` VALUES ('2539', '2535', '九龙', '九龙县', '中国,四川省,甘孜藏族自治州,九龙县', '3', 'jiulong', '0836', '626200', 'J', '101.50848', '29.00091'); +INSERT INTO `yoshop_region` VALUES ('2540', '2535', '雅江', '雅江县', '中国,四川省,甘孜藏族自治州,雅江县', '3', 'yajiang', '0836', '627450', 'Y', '101.01492', '30.03281'); +INSERT INTO `yoshop_region` VALUES ('2541', '2535', '道孚', '道孚县', '中国,四川省,甘孜藏族自治州,道孚县', '3', 'daofu', '0836', '626400', 'D', '101.12554', '30.98046'); +INSERT INTO `yoshop_region` VALUES ('2542', '2535', '炉霍', '炉霍县', '中国,四川省,甘孜藏族自治州,炉霍县', '3', 'luhuo', '0836', '626500', 'L', '100.67681', '31.3917'); +INSERT INTO `yoshop_region` VALUES ('2543', '2535', '甘孜', '甘孜县', '中国,四川省,甘孜藏族自治州,甘孜县', '3', 'ganzi', '0836', '626700', 'G', '99.99307', '31.62672'); +INSERT INTO `yoshop_region` VALUES ('2544', '2535', '新龙', '新龙县', '中国,四川省,甘孜藏族自治州,新龙县', '3', 'xinlong', '0836', '626800', 'X', '100.3125', '30.94067'); +INSERT INTO `yoshop_region` VALUES ('2545', '2535', '德格', '德格县', '中国,四川省,甘孜藏族自治州,德格县', '3', 'dege', '0836', '627250', 'D', '98.58078', '31.80615'); +INSERT INTO `yoshop_region` VALUES ('2546', '2535', '白玉', '白玉县', '中国,四川省,甘孜藏族自治州,白玉县', '3', 'baiyu', '0836', '627150', 'B', '98.82568', '31.20902'); +INSERT INTO `yoshop_region` VALUES ('2547', '2535', '石渠', '石渠县', '中国,四川省,甘孜藏族自治州,石渠县', '3', 'shiqu', '0836', '627350', 'S', '98.10156', '32.97884'); +INSERT INTO `yoshop_region` VALUES ('2548', '2535', '色达', '色达县', '中国,四川省,甘孜藏族自治州,色达县', '3', 'seda', '0836', '626600', 'S', '100.33224', '32.26839'); +INSERT INTO `yoshop_region` VALUES ('2549', '2535', '理塘', '理塘县', '中国,四川省,甘孜藏族自治州,理塘县', '3', 'litang', '0836', '627550', 'L', '100.27005', '29.99674'); +INSERT INTO `yoshop_region` VALUES ('2550', '2535', '巴塘', '巴塘县', '中国,四川省,甘孜藏族自治州,巴塘县', '3', 'batang', '0836', '627650', 'B', '99.10409', '30.00423'); +INSERT INTO `yoshop_region` VALUES ('2551', '2535', '乡城', '乡城县', '中国,四川省,甘孜藏族自治州,乡城县', '3', 'xiangcheng', '0836', '627850', 'X', '99.79943', '28.93554'); +INSERT INTO `yoshop_region` VALUES ('2552', '2535', '稻城', '稻城县', '中国,四川省,甘孜藏族自治州,稻城县', '3', 'daocheng', '0836', '627750', 'D', '100.29809', '29.0379'); +INSERT INTO `yoshop_region` VALUES ('2553', '2535', '得荣', '得荣县', '中国,四川省,甘孜藏族自治州,得荣县', '3', 'derong', '0836', '627950', 'D', '99.28628', '28.71297'); +INSERT INTO `yoshop_region` VALUES ('2554', '2367', '凉山', '凉山彝族自治州', '中国,四川省,凉山彝族自治州', '2', 'liangshan', '0834', '615000', 'L', '102.258746', '27.886762'); +INSERT INTO `yoshop_region` VALUES ('2555', '2554', '西昌', '西昌市', '中国,四川省,凉山彝族自治州,西昌市', '3', 'xichang', '0835', '615000', 'X', '102.26413', '27.89524'); +INSERT INTO `yoshop_region` VALUES ('2556', '2554', '木里', '木里藏族自治县', '中国,四川省,凉山彝族自治州,木里藏族自治县', '3', 'muli', '0851', '615800', 'M', '101.2796', '27.92875'); +INSERT INTO `yoshop_region` VALUES ('2557', '2554', '盐源', '盐源县', '中国,四川省,凉山彝族自治州,盐源县', '3', 'yanyuan', '0836', '615700', 'Y', '101.5097', '27.42177'); +INSERT INTO `yoshop_region` VALUES ('2558', '2554', '德昌', '德昌县', '中国,四川省,凉山彝族自治州,德昌县', '3', 'dechang', '0837', '615500', 'D', '102.18017', '27.40482'); +INSERT INTO `yoshop_region` VALUES ('2559', '2554', '会理', '会理县', '中国,四川省,凉山彝族自治州,会理县', '3', 'huili', '0838', '615100', 'H', '102.24539', '26.65627'); +INSERT INTO `yoshop_region` VALUES ('2560', '2554', '会东', '会东县', '中国,四川省,凉山彝族自治州,会东县', '3', 'huidong', '0839', '615200', 'H', '102.57815', '26.63429'); +INSERT INTO `yoshop_region` VALUES ('2561', '2554', '宁南', '宁南县', '中国,四川省,凉山彝族自治州,宁南县', '3', 'ningnan', '0840', '615400', 'N', '102.76116', '27.06567'); +INSERT INTO `yoshop_region` VALUES ('2562', '2554', '普格', '普格县', '中国,四川省,凉山彝族自治州,普格县', '3', 'puge', '0841', '615300', 'P', '102.54055', '27.37485'); +INSERT INTO `yoshop_region` VALUES ('2563', '2554', '布拖', '布拖县', '中国,四川省,凉山彝族自治州,布拖县', '3', 'butuo', '0842', '616350', 'B', '102.81234', '27.7079'); +INSERT INTO `yoshop_region` VALUES ('2564', '2554', '金阳', '金阳县', '中国,四川省,凉山彝族自治州,金阳县', '3', 'jinyang', '0843', '616250', 'J', '103.24774', '27.69698'); +INSERT INTO `yoshop_region` VALUES ('2565', '2554', '昭觉', '昭觉县', '中国,四川省,凉山彝族自治州,昭觉县', '3', 'zhaojue', '0844', '616150', 'Z', '102.84661', '28.01155'); +INSERT INTO `yoshop_region` VALUES ('2566', '2554', '喜德', '喜德县', '中国,四川省,凉山彝族自治州,喜德县', '3', 'xide', '0845', '616750', 'X', '102.41336', '28.30739'); +INSERT INTO `yoshop_region` VALUES ('2567', '2554', '冕宁', '冕宁县', '中国,四川省,凉山彝族自治州,冕宁县', '3', 'mianning', '0846', '615600', 'M', '102.17108', '28.55161'); +INSERT INTO `yoshop_region` VALUES ('2568', '2554', '越西', '越西县', '中国,四川省,凉山彝族自治州,越西县', '3', 'yuexi', '0847', '616650', 'Y', '102.5079', '28.64133'); +INSERT INTO `yoshop_region` VALUES ('2569', '2554', '甘洛', '甘洛县', '中国,四川省,凉山彝族自治州,甘洛县', '3', 'ganluo', '0848', '616850', 'G', '102.77154', '28.96624'); +INSERT INTO `yoshop_region` VALUES ('2570', '2554', '美姑', '美姑县', '中国,四川省,凉山彝族自治州,美姑县', '3', 'meigu', '0849', '616450', 'M', '103.13116', '28.32596'); +INSERT INTO `yoshop_region` VALUES ('2571', '2554', '雷波', '雷波县', '中国,四川省,凉山彝族自治州,雷波县', '3', 'leibo', '0850', '616550', 'L', '103.57287', '28.26407'); +INSERT INTO `yoshop_region` VALUES ('2572', '0', '贵州', '贵州省', '中国,贵州省', '1', 'guizhou', '', '', 'G', '106.713478', '26.578343'); +INSERT INTO `yoshop_region` VALUES ('2573', '2572', '贵阳', '贵阳市', '中国,贵州省,贵阳市', '2', 'guiyang', '0851', '550001', 'G', '106.713478', '26.578343'); +INSERT INTO `yoshop_region` VALUES ('2574', '2573', '南明', '南明区', '中国,贵州省,贵阳市,南明区', '3', 'nanming', '0851', '550001', 'N', '106.7145', '26.56819'); +INSERT INTO `yoshop_region` VALUES ('2575', '2573', '云岩', '云岩区', '中国,贵州省,贵阳市,云岩区', '3', 'yunyan', '0851', '550001', 'Y', '106.72485', '26.60484'); +INSERT INTO `yoshop_region` VALUES ('2576', '2573', '花溪', '花溪区', '中国,贵州省,贵阳市,花溪区', '3', 'huaxi', '0851', '550025', 'H', '106.67688', '26.43343'); +INSERT INTO `yoshop_region` VALUES ('2577', '2573', '乌当', '乌当区', '中国,贵州省,贵阳市,乌当区', '3', 'wudang', '0851', '550018', 'W', '106.7521', '26.6302'); +INSERT INTO `yoshop_region` VALUES ('2578', '2573', '白云', '白云区', '中国,贵州省,贵阳市,白云区', '3', 'baiyun', '0851', '550014', 'B', '106.63088', '26.68284'); +INSERT INTO `yoshop_region` VALUES ('2579', '2573', '观山湖', '观山湖区', '中国,贵州省,贵阳市,观山湖区', '3', 'guanshanhu', '0851', '550009', 'G', '106.625442', '26.618209'); +INSERT INTO `yoshop_region` VALUES ('2580', '2573', '开阳', '开阳县', '中国,贵州省,贵阳市,开阳县', '3', 'kaiyang', '0851', '550300', 'K', '106.9692', '27.05533'); +INSERT INTO `yoshop_region` VALUES ('2581', '2573', '息烽', '息烽县', '中国,贵州省,贵阳市,息烽县', '3', 'xifeng', '0851', '551100', 'X', '106.738', '27.09346'); +INSERT INTO `yoshop_region` VALUES ('2582', '2573', '修文', '修文县', '中国,贵州省,贵阳市,修文县', '3', 'xiuwen', '0851', '550200', 'X', '106.59487', '26.83783'); +INSERT INTO `yoshop_region` VALUES ('2583', '2573', '清镇', '清镇市', '中国,贵州省,贵阳市,清镇市', '3', 'qingzhen', '0851', '551400', 'Q', '106.46862', '26.55261'); +INSERT INTO `yoshop_region` VALUES ('2584', '2572', '六盘水', '六盘水市', '中国,贵州省,六盘水市', '2', 'liupanshui', '0858', '553400', 'L', '104.846743', '26.584643'); +INSERT INTO `yoshop_region` VALUES ('2585', '2584', '钟山', '钟山区', '中国,贵州省,六盘水市,钟山区', '3', 'zhongshan', '0858', '553000', 'Z', '104.87848', '26.57699'); +INSERT INTO `yoshop_region` VALUES ('2586', '2584', '六枝', '六枝特区', '中国,贵州省,六盘水市,六枝特区', '3', 'liuzhi', '0858', '553400', 'L', '105.48062', '26.20117'); +INSERT INTO `yoshop_region` VALUES ('2587', '2584', '水城', '水城县', '中国,贵州省,六盘水市,水城县', '3', 'shuicheng', '0858', '553000', 'S', '104.95764', '26.54785'); +INSERT INTO `yoshop_region` VALUES ('2588', '2584', '盘县', '盘县', '中国,贵州省,六盘水市,盘县', '3', 'panxian', '0858', '561601', 'P', '104.47061', '25.7136'); +INSERT INTO `yoshop_region` VALUES ('2589', '2572', '遵义', '遵义市', '中国,贵州省,遵义市', '2', 'zunyi', '0852', '563000', 'Z', '106.937265', '27.706626'); +INSERT INTO `yoshop_region` VALUES ('2590', '2589', '红花岗', '红花岗区', '中国,贵州省,遵义市,红花岗区', '3', 'honghuagang', '0852', '563000', 'H', '106.89404', '27.64471'); +INSERT INTO `yoshop_region` VALUES ('2591', '2589', '汇川', '汇川区', '中国,贵州省,遵义市,汇川区', '3', 'huichuan', '0852', '563000', 'H', '106.9393', '27.70625'); +INSERT INTO `yoshop_region` VALUES ('2592', '2589', '遵义', '遵义县', '中国,贵州省,遵义市,遵义县', '3', 'zunyi', '0852', '563100', 'Z', '106.83331', '27.53772'); +INSERT INTO `yoshop_region` VALUES ('2593', '2589', '桐梓', '桐梓县', '中国,贵州省,遵义市,桐梓县', '3', 'tongzi', '0852', '563200', 'T', '106.82568', '28.13806'); +INSERT INTO `yoshop_region` VALUES ('2594', '2589', '绥阳', '绥阳县', '中国,贵州省,遵义市,绥阳县', '3', 'suiyang', '0852', '563300', 'S', '107.19064', '27.94702'); +INSERT INTO `yoshop_region` VALUES ('2595', '2589', '正安', '正安县', '中国,贵州省,遵义市,正安县', '3', 'zheng\'an', '0852', '563400', 'Z', '107.44357', '28.5512'); +INSERT INTO `yoshop_region` VALUES ('2596', '2589', '道真', '道真仡佬族苗族自治县', '中国,贵州省,遵义市,道真仡佬族苗族自治县', '3', 'daozhen', '0852', '563500', 'D', '107.61152', '28.864'); +INSERT INTO `yoshop_region` VALUES ('2597', '2589', '务川', '务川仡佬族苗族自治县', '中国,贵州省,遵义市,务川仡佬族苗族自治县', '3', 'wuchuan', '0852', '564300', 'W', '107.88935', '28.52227'); +INSERT INTO `yoshop_region` VALUES ('2598', '2589', '凤冈', '凤冈县', '中国,贵州省,遵义市,凤冈县', '3', 'fenggang', '0852', '564200', 'F', '107.71682', '27.95461'); +INSERT INTO `yoshop_region` VALUES ('2599', '2589', '湄潭', '湄潭县', '中国,贵州省,遵义市,湄潭县', '3', 'meitan', '0852', '564100', null, '107.48779', '27.76676'); +INSERT INTO `yoshop_region` VALUES ('2600', '2589', '余庆', '余庆县', '中国,贵州省,遵义市,余庆县', '3', 'yuqing', '0852', '564400', 'Y', '107.88821', '27.22532'); +INSERT INTO `yoshop_region` VALUES ('2601', '2589', '习水', '习水县', '中国,贵州省,遵义市,习水县', '3', 'xishui', '0852', '564600', 'X', '106.21267', '28.31976'); +INSERT INTO `yoshop_region` VALUES ('2602', '2589', '赤水', '赤水市', '中国,贵州省,遵义市,赤水市', '3', 'chishui', '0852', '564700', 'C', '105.69845', '28.58921'); +INSERT INTO `yoshop_region` VALUES ('2603', '2589', '仁怀', '仁怀市', '中国,贵州省,遵义市,仁怀市', '3', 'renhuai', '0852', '564500', 'R', '106.40152', '27.79231'); +INSERT INTO `yoshop_region` VALUES ('2604', '2572', '安顺', '安顺市', '中国,贵州省,安顺市', '2', 'anshun', '0853', '561000', 'A', '105.932188', '26.245544'); +INSERT INTO `yoshop_region` VALUES ('2605', '2604', '西秀', '西秀区', '中国,贵州省,安顺市,西秀区', '3', 'xixiu', '0853', '561000', 'X', '105.96585', '26.24491'); +INSERT INTO `yoshop_region` VALUES ('2606', '2604', '平坝', '平坝区', '中国,贵州省,安顺市,平坝区', '3', 'pingba', '0853', '561100', 'P', '106.25683', '26.40543'); +INSERT INTO `yoshop_region` VALUES ('2607', '2604', '普定', '普定县', '中国,贵州省,安顺市,普定县', '3', 'puding', '0853', '562100', 'P', '105.74285', '26.30141'); +INSERT INTO `yoshop_region` VALUES ('2608', '2604', '镇宁', '镇宁布依族苗族自治县', '中国,贵州省,安顺市,镇宁布依族苗族自治县', '3', 'zhenning', '0853', '561200', 'Z', '105.76513', '26.05533'); +INSERT INTO `yoshop_region` VALUES ('2609', '2604', '关岭', '关岭布依族苗族自治县', '中国,贵州省,安顺市,关岭布依族苗族自治县', '3', 'guanling', '0853', '561300', 'G', '105.61883', '25.94248'); +INSERT INTO `yoshop_region` VALUES ('2610', '2604', '紫云', '紫云苗族布依族自治县', '中国,贵州省,安顺市,紫云苗族布依族自治县', '3', 'ziyun', '0853', '550800', 'Z', '106.08364', '25.75258'); +INSERT INTO `yoshop_region` VALUES ('2611', '2572', '毕节', '毕节市', '中国,贵州省,毕节市', '2', 'bijie', '0857', '551700', 'B', '105.28501', '27.301693'); +INSERT INTO `yoshop_region` VALUES ('2612', '2611', '七星关', '七星关区', '中国,贵州省,毕节市,七星关区', '3', 'qixingguan', '0857', '551700', 'Q', '104.9497', '27.153556'); +INSERT INTO `yoshop_region` VALUES ('2613', '2611', '大方', '大方县', '中国,贵州省,毕节市,大方县', '3', 'dafang', '0857', '551600', 'D', '105.609254', '27.143521'); +INSERT INTO `yoshop_region` VALUES ('2614', '2611', '黔西', '黔西县', '中国,贵州省,毕节市,黔西县', '3', 'qianxi', '0857', '551500', 'Q', '106.038299', '27.024923'); +INSERT INTO `yoshop_region` VALUES ('2615', '2611', '金沙', '金沙县', '中国,贵州省,毕节市,金沙县', '3', 'jinsha', '0857', '551800', 'J', '106.222103', '27.459693'); +INSERT INTO `yoshop_region` VALUES ('2616', '2611', '织金', '织金县', '中国,贵州省,毕节市,织金县', '3', 'zhijin', '0857', '552100', 'Z', '105.768997', '26.668497'); +INSERT INTO `yoshop_region` VALUES ('2617', '2611', '纳雍', '纳雍县', '中国,贵州省,毕节市,纳雍县', '3', 'nayong', '0857', '553300', 'N', '105.375322', '26.769875'); +INSERT INTO `yoshop_region` VALUES ('2618', '2611', '威宁', '威宁彝族回族苗族自治县', '中国,贵州省,毕节市,威宁彝族回族苗族自治县', '3', 'weining', '0857', '553100', 'W', '104.286523', '26.859099'); +INSERT INTO `yoshop_region` VALUES ('2619', '2611', '赫章', '赫章县', '中国,贵州省,毕节市,赫章县', '3', 'hezhang', '0857', '553200', 'H', '104.726438', '27.119243'); +INSERT INTO `yoshop_region` VALUES ('2620', '2572', '铜仁', '铜仁市', '中国,贵州省,铜仁市', '2', 'tongren', '0856', '554300', 'T', '109.191555', '27.718346'); +INSERT INTO `yoshop_region` VALUES ('2621', '2620', '碧江', '碧江区', '中国,贵州省,铜仁市,碧江区', '3', 'bijiang', '0856', '554300', 'B', '109.191555', '27.718346'); +INSERT INTO `yoshop_region` VALUES ('2622', '2620', '万山', '万山区', '中国,贵州省,铜仁市,万山区', '3', 'wanshan', '0856', '554200', 'W', '109.21199', '27.51903'); +INSERT INTO `yoshop_region` VALUES ('2623', '2620', '江口', '江口县', '中国,贵州省,铜仁市,江口县', '3', 'jiangkou', '0856', '554400', 'J', '108.848427', '27.691904'); +INSERT INTO `yoshop_region` VALUES ('2624', '2620', '玉屏', '玉屏侗族自治县', '中国,贵州省,铜仁市,玉屏侗族自治县', '3', 'yuping', '0856', '554004', 'Y', '108.917882', '27.238024'); +INSERT INTO `yoshop_region` VALUES ('2625', '2620', '石阡', '石阡县', '中国,贵州省,铜仁市,石阡县', '3', 'shiqian', '0856', '555100', 'S', '108.229854', '27.519386'); +INSERT INTO `yoshop_region` VALUES ('2626', '2620', '思南', '思南县', '中国,贵州省,铜仁市,思南县', '3', 'sinan', '0856', '565100', 'S', '108.255827', '27.941331'); +INSERT INTO `yoshop_region` VALUES ('2627', '2620', '印江', '印江土家族苗族自治县', '中国,贵州省,铜仁市,印江土家族苗族自治县', '3', 'yinjiang', '0856', '555200', 'Y', '108.405517', '27.997976'); +INSERT INTO `yoshop_region` VALUES ('2628', '2620', '德江', '德江县', '中国,贵州省,铜仁市,德江县', '3', 'dejiang', '0856', '565200', 'D', '108.117317', '28.26094'); +INSERT INTO `yoshop_region` VALUES ('2629', '2620', '沿河', '沿河土家族自治县', '中国,贵州省,铜仁市,沿河土家族自治县', '3', 'yuanhe', '0856', '565300', 'Y', '108.495746', '28.560487'); +INSERT INTO `yoshop_region` VALUES ('2630', '2620', '松桃', '松桃苗族自治县', '中国,贵州省,铜仁市,松桃苗族自治县', '3', 'songtao', '0856', '554100', 'S', '109.202627', '28.165419'); +INSERT INTO `yoshop_region` VALUES ('2631', '2572', '黔西南', '黔西南布依族苗族自治州', '中国,贵州省,黔西南布依族苗族自治州', '2', 'qianxinan', '0859', '562400', 'Q', '104.897971', '25.08812'); +INSERT INTO `yoshop_region` VALUES ('2632', '2631', '兴义', '兴义市 ', '中国,贵州省,黔西南布依族苗族自治州,兴义市 ', '3', 'xingyi', '0859', '562400', 'X', '104.89548', '25.09205'); +INSERT INTO `yoshop_region` VALUES ('2633', '2631', '兴仁', '兴仁县', '中国,贵州省,黔西南布依族苗族自治州,兴仁县', '3', 'xingren', '0859', '562300', 'X', '105.18652', '25.43282'); +INSERT INTO `yoshop_region` VALUES ('2634', '2631', '普安', '普安县', '中国,贵州省,黔西南布依族苗族自治州,普安县', '3', 'pu\'an', '0859', '561500', 'P', '104.95529', '25.78603'); +INSERT INTO `yoshop_region` VALUES ('2635', '2631', '晴隆', '晴隆县', '中国,贵州省,黔西南布依族苗族自治州,晴隆县', '3', 'qinglong', '0859', '561400', 'Q', '105.2192', '25.83522'); +INSERT INTO `yoshop_region` VALUES ('2636', '2631', '贞丰', '贞丰县', '中国,贵州省,黔西南布依族苗族自治州,贞丰县', '3', 'zhenfeng', '0859', '562200', 'Z', '105.65454', '25.38464'); +INSERT INTO `yoshop_region` VALUES ('2637', '2631', '望谟', '望谟县', '中国,贵州省,黔西南布依族苗族自治州,望谟县', '3', 'wangmo', '0859', '552300', 'W', '106.09957', '25.17822'); +INSERT INTO `yoshop_region` VALUES ('2638', '2631', '册亨', '册亨县', '中国,贵州省,黔西南布依族苗族自治州,册亨县', '3', 'ceheng', '0859', '552200', 'C', '105.8124', '24.98376'); +INSERT INTO `yoshop_region` VALUES ('2639', '2631', '安龙', '安龙县', '中国,贵州省,黔西南布依族苗族自治州,安龙县', '3', 'anlong', '0859', '552400', 'A', '105.44268', '25.09818'); +INSERT INTO `yoshop_region` VALUES ('2640', '2572', '黔东南', '黔东南苗族侗族自治州', '中国,贵州省,黔东南苗族侗族自治州', '2', 'qiandongnan', '0855', '556000', 'Q', '107.977488', '26.583352'); +INSERT INTO `yoshop_region` VALUES ('2641', '2640', '凯里', '凯里市', '中国,贵州省,黔东南苗族侗族自治州,凯里市', '3', 'kaili', '0855', '556000', 'K', '107.98132', '26.56689'); +INSERT INTO `yoshop_region` VALUES ('2642', '2640', '黄平', '黄平县', '中国,贵州省,黔东南苗族侗族自治州,黄平县', '3', 'huangping', '0855', '556100', 'H', '107.90179', '26.89573'); +INSERT INTO `yoshop_region` VALUES ('2643', '2640', '施秉', '施秉县', '中国,贵州省,黔东南苗族侗族自治州,施秉县', '3', 'shibing', '0855', '556200', 'S', '108.12597', '27.03495'); +INSERT INTO `yoshop_region` VALUES ('2644', '2640', '三穗', '三穗县', '中国,贵州省,黔东南苗族侗族自治州,三穗县', '3', 'sansui', '0855', '556500', 'S', '108.67132', '26.94765'); +INSERT INTO `yoshop_region` VALUES ('2645', '2640', '镇远', '镇远县', '中国,贵州省,黔东南苗族侗族自治州,镇远县', '3', 'zhenyuan', '0855', '557700', 'Z', '108.42721', '27.04933'); +INSERT INTO `yoshop_region` VALUES ('2646', '2640', '岑巩', '岑巩县', '中国,贵州省,黔东南苗族侗族自治州,岑巩县', '3', 'cengong', '0855', '557800', null, '108.81884', '27.17539'); +INSERT INTO `yoshop_region` VALUES ('2647', '2640', '天柱', '天柱县', '中国,贵州省,黔东南苗族侗族自治州,天柱县', '3', 'tianzhu', '0855', '556600', 'T', '109.20718', '26.90781'); +INSERT INTO `yoshop_region` VALUES ('2648', '2640', '锦屏', '锦屏县', '中国,贵州省,黔东南苗族侗族自治州,锦屏县', '3', 'jinping', '0855', '556700', 'J', '109.19982', '26.67635'); +INSERT INTO `yoshop_region` VALUES ('2649', '2640', '剑河', '剑河县', '中国,贵州省,黔东南苗族侗族自治州,剑河县', '3', 'jianhe', '0855', '556400', 'J', '108.5913', '26.6525'); +INSERT INTO `yoshop_region` VALUES ('2650', '2640', '台江', '台江县', '中国,贵州省,黔东南苗族侗族自治州,台江县', '3', 'taijiang', '0855', '556300', 'T', '108.31814', '26.66916'); +INSERT INTO `yoshop_region` VALUES ('2651', '2640', '黎平', '黎平县', '中国,贵州省,黔东南苗族侗族自治州,黎平县', '3', 'liping', '0855', '557300', 'L', '109.13607', '26.23114'); +INSERT INTO `yoshop_region` VALUES ('2652', '2640', '榕江', '榕江县', '中国,贵州省,黔东南苗族侗族自治州,榕江县', '3', 'rongjiang', '0855', '557200', null, '108.52072', '25.92421'); +INSERT INTO `yoshop_region` VALUES ('2653', '2640', '从江', '从江县', '中国,贵州省,黔东南苗族侗族自治州,从江县', '3', 'congjiang', '0855', '557400', 'C', '108.90527', '25.75415'); +INSERT INTO `yoshop_region` VALUES ('2654', '2640', '雷山', '雷山县', '中国,贵州省,黔东南苗族侗族自治州,雷山县', '3', 'leishan', '0855', '557100', 'L', '108.07745', '26.38385'); +INSERT INTO `yoshop_region` VALUES ('2655', '2640', '麻江', '麻江县', '中国,贵州省,黔东南苗族侗族自治州,麻江县', '3', 'majiang', '0855', '557600', 'M', '107.59155', '26.49235'); +INSERT INTO `yoshop_region` VALUES ('2656', '2640', '丹寨', '丹寨县', '中国,贵州省,黔东南苗族侗族自治州,丹寨县', '3', 'danzhai', '0855', '557500', 'D', '107.79718', '26.19816'); +INSERT INTO `yoshop_region` VALUES ('2657', '2572', '黔南', '黔南布依族苗族自治州', '中国,贵州省,黔南布依族苗族自治州', '2', 'qiannan', '0854', '558000', 'Q', '107.517156', '26.258219'); +INSERT INTO `yoshop_region` VALUES ('2658', '2657', '都匀', '都匀市', '中国,贵州省,黔南布依族苗族自治州,都匀市', '3', 'duyun', '0854', '558000', 'D', '107.51872', '26.2594'); +INSERT INTO `yoshop_region` VALUES ('2659', '2657', '福泉', '福泉市', '中国,贵州省,黔南布依族苗族自治州,福泉市', '3', 'fuquan', '0854', '550500', 'F', '107.51715', '26.67989'); +INSERT INTO `yoshop_region` VALUES ('2660', '2657', '荔波', '荔波县', '中国,贵州省,黔南布依族苗族自治州,荔波县', '3', 'libo', '0854', '558400', 'L', '107.88592', '25.4139'); +INSERT INTO `yoshop_region` VALUES ('2661', '2657', '贵定', '贵定县', '中国,贵州省,黔南布依族苗族自治州,贵定县', '3', 'guiding', '0854', '551300', 'G', '107.23654', '26.57812'); +INSERT INTO `yoshop_region` VALUES ('2662', '2657', '瓮安', '瓮安县', '中国,贵州省,黔南布依族苗族自治州,瓮安县', '3', 'weng\'an', '0854', '550400', 'W', '107.4757', '27.06813'); +INSERT INTO `yoshop_region` VALUES ('2663', '2657', '独山', '独山县', '中国,贵州省,黔南布依族苗族自治州,独山县', '3', 'dushan', '0854', '558200', 'D', '107.54101', '25.8245'); +INSERT INTO `yoshop_region` VALUES ('2664', '2657', '平塘', '平塘县', '中国,贵州省,黔南布依族苗族自治州,平塘县', '3', 'pingtang', '0854', '558300', 'P', '107.32428', '25.83294'); +INSERT INTO `yoshop_region` VALUES ('2665', '2657', '罗甸', '罗甸县', '中国,贵州省,黔南布依族苗族自治州,罗甸县', '3', 'luodian', '0854', '550100', 'L', '106.75186', '25.42586'); +INSERT INTO `yoshop_region` VALUES ('2666', '2657', '长顺', '长顺县', '中国,贵州省,黔南布依族苗族自治州,长顺县', '3', 'changshun', '0854', '550700', 'C', '106.45217', '26.02299'); +INSERT INTO `yoshop_region` VALUES ('2667', '2657', '龙里', '龙里县', '中国,贵州省,黔南布依族苗族自治州,龙里县', '3', 'longli', '0854', '551200', 'L', '106.97662', '26.45076'); +INSERT INTO `yoshop_region` VALUES ('2668', '2657', '惠水', '惠水县', '中国,贵州省,黔南布依族苗族自治州,惠水县', '3', 'huishui', '0854', '550600', 'H', '106.65911', '26.13389'); +INSERT INTO `yoshop_region` VALUES ('2669', '2657', '三都', '三都水族自治县', '中国,贵州省,黔南布依族苗族自治州,三都水族自治县', '3', 'sandu', '0854', '558100', 'S', '107.87464', '25.98562'); +INSERT INTO `yoshop_region` VALUES ('2670', '0', '云南', '云南省', '中国,云南省', '1', 'yunnan', '', '', 'Y', '102.712251', '25.040609'); +INSERT INTO `yoshop_region` VALUES ('2671', '2670', '昆明', '昆明市', '中国,云南省,昆明市', '2', 'kunming', '0871', '650500', 'K', '102.712251', '25.040609'); +INSERT INTO `yoshop_region` VALUES ('2672', '2671', '五华', '五华区', '中国,云南省,昆明市,五华区', '3', 'wuhua', '0871', '650021', 'W', '102.70786', '25.03521'); +INSERT INTO `yoshop_region` VALUES ('2673', '2671', '盘龙', '盘龙区', '中国,云南省,昆明市,盘龙区', '3', 'panlong', '0871', '650051', 'P', '102.71994', '25.04053'); +INSERT INTO `yoshop_region` VALUES ('2674', '2671', '官渡', '官渡区', '中国,云南省,昆明市,官渡区', '3', 'guandu', '0871', '650200', 'G', '102.74362', '25.01497'); +INSERT INTO `yoshop_region` VALUES ('2675', '2671', '西山', '西山区', '中国,云南省,昆明市,西山区', '3', 'xishan', '0871', '650118', 'X', '102.66464', '25.03796'); +INSERT INTO `yoshop_region` VALUES ('2676', '2671', '东川', '东川区', '中国,云南省,昆明市,东川区', '3', 'dongchuan', '0871', '654100', 'D', '103.18832', '26.083'); +INSERT INTO `yoshop_region` VALUES ('2677', '2671', '呈贡', '呈贡区', '中国,云南省,昆明市,呈贡区', '3', 'chenggong', '0871', '650500', 'C', '102.801382', '24.889275'); +INSERT INTO `yoshop_region` VALUES ('2678', '2671', '晋宁', '晋宁县', '中国,云南省,昆明市,晋宁县', '3', 'jinning', '0871', '650600', 'J', '102.59393', '24.6665'); +INSERT INTO `yoshop_region` VALUES ('2679', '2671', '富民', '富民县', '中国,云南省,昆明市,富民县', '3', 'fumin', '0871', '650400', 'F', '102.4985', '25.22119'); +INSERT INTO `yoshop_region` VALUES ('2680', '2671', '宜良', '宜良县', '中国,云南省,昆明市,宜良县', '3', 'yiliang', '0871', '652100', 'Y', '103.14117', '24.91705'); +INSERT INTO `yoshop_region` VALUES ('2681', '2671', '石林', '石林彝族自治县', '中国,云南省,昆明市,石林彝族自治县', '3', 'shilin', '0871', '652200', 'S', '103.27148', '24.75897'); +INSERT INTO `yoshop_region` VALUES ('2682', '2671', '嵩明', '嵩明县', '中国,云南省,昆明市,嵩明县', '3', 'songming', '0871', '651700', null, '103.03729', '25.33986'); +INSERT INTO `yoshop_region` VALUES ('2683', '2671', '禄劝', '禄劝彝族苗族自治县', '中国,云南省,昆明市,禄劝彝族苗族自治县', '3', 'luquan', '0871', '651500', 'L', '102.4671', '25.55387'); +INSERT INTO `yoshop_region` VALUES ('2684', '2671', '寻甸', '寻甸回族彝族自治县 ', '中国,云南省,昆明市,寻甸回族彝族自治县 ', '3', 'xundian', '0871', '655200', 'X', '103.2557', '25.55961'); +INSERT INTO `yoshop_region` VALUES ('2685', '2671', '安宁', '安宁市', '中国,云南省,昆明市,安宁市', '3', 'anning', '0871', '650300', 'A', '102.46972', '24.91652'); +INSERT INTO `yoshop_region` VALUES ('2686', '2670', '曲靖', '曲靖市', '中国,云南省,曲靖市', '2', 'qujing', '0874', '655000', 'Q', '103.797851', '25.501557'); +INSERT INTO `yoshop_region` VALUES ('2687', '2686', '麒麟', '麒麟区', '中国,云南省,曲靖市,麒麟区', '3', 'qilin', '0874', '655000', null, '103.80504', '25.49515'); +INSERT INTO `yoshop_region` VALUES ('2688', '2686', '马龙', '马龙县', '中国,云南省,曲靖市,马龙县', '3', 'malong', '0874', '655100', 'M', '103.57873', '25.42521'); +INSERT INTO `yoshop_region` VALUES ('2689', '2686', '陆良', '陆良县', '中国,云南省,曲靖市,陆良县', '3', 'luliang', '0874', '655600', 'L', '103.6665', '25.02335'); +INSERT INTO `yoshop_region` VALUES ('2690', '2686', '师宗', '师宗县', '中国,云南省,曲靖市,师宗县', '3', 'shizong', '0874', '655700', 'S', '103.99084', '24.82822'); +INSERT INTO `yoshop_region` VALUES ('2691', '2686', '罗平', '罗平县', '中国,云南省,曲靖市,罗平县', '3', 'luoping', '0874', '655800', 'L', '104.30859', '24.88444'); +INSERT INTO `yoshop_region` VALUES ('2692', '2686', '富源', '富源县', '中国,云南省,曲靖市,富源县', '3', 'fuyuan', '0874', '655500', 'F', '104.25387', '25.66587'); +INSERT INTO `yoshop_region` VALUES ('2693', '2686', '会泽', '会泽县', '中国,云南省,曲靖市,会泽县', '3', 'huize', '0874', '654200', 'H', '103.30017', '26.41076'); +INSERT INTO `yoshop_region` VALUES ('2694', '2686', '沾益', '沾益县', '中国,云南省,曲靖市,沾益县', '3', 'zhanyi', '0874', '655331', 'Z', '103.82135', '25.60715'); +INSERT INTO `yoshop_region` VALUES ('2695', '2686', '宣威', '宣威市', '中国,云南省,曲靖市,宣威市', '3', 'xuanwei', '0874', '655400', 'X', '104.10409', '26.2173'); +INSERT INTO `yoshop_region` VALUES ('2696', '2670', '玉溪', '玉溪市', '中国,云南省,玉溪市', '2', 'yuxi', '0877', '653100', 'Y', '102.543907', '24.350461'); +INSERT INTO `yoshop_region` VALUES ('2697', '2696', '红塔', '红塔区', '中国,云南省,玉溪市,红塔区', '3', 'hongta', '0877', '653100', 'H', '102.5449', '24.35411'); +INSERT INTO `yoshop_region` VALUES ('2698', '2696', '江川', '江川县', '中国,云南省,玉溪市,江川县', '3', 'jiangchuan', '0877', '652600', 'J', '102.75412', '24.28863'); +INSERT INTO `yoshop_region` VALUES ('2699', '2696', '澄江', '澄江县', '中国,云南省,玉溪市,澄江县', '3', 'chengjiang', '0877', '652500', 'C', '102.90817', '24.67376'); +INSERT INTO `yoshop_region` VALUES ('2700', '2696', '通海', '通海县', '中国,云南省,玉溪市,通海县', '3', 'tonghai', '0877', '652700', 'T', '102.76641', '24.11362'); +INSERT INTO `yoshop_region` VALUES ('2701', '2696', '华宁', '华宁县', '中国,云南省,玉溪市,华宁县', '3', 'huaning', '0877', '652800', 'H', '102.92831', '24.1926'); +INSERT INTO `yoshop_region` VALUES ('2702', '2696', '易门', '易门县', '中国,云南省,玉溪市,易门县', '3', 'yimen', '0877', '651100', 'Y', '102.16354', '24.67122'); +INSERT INTO `yoshop_region` VALUES ('2703', '2696', '峨山', '峨山彝族自治县', '中国,云南省,玉溪市,峨山彝族自治县', '3', 'eshan', '0877', '653200', 'E', '102.40576', '24.16904'); +INSERT INTO `yoshop_region` VALUES ('2704', '2696', '新平', '新平彝族傣族自治县', '中国,云南省,玉溪市,新平彝族傣族自治县', '3', 'xinping', '0877', '653400', 'X', '101.98895', '24.06886'); +INSERT INTO `yoshop_region` VALUES ('2705', '2696', '元江', '元江哈尼族彝族傣族自治县', '中国,云南省,玉溪市,元江哈尼族彝族傣族自治县', '3', 'yuanjiang', '0877', '653300', 'Y', '101.99812', '23.59655'); +INSERT INTO `yoshop_region` VALUES ('2706', '2670', '保山', '保山市', '中国,云南省,保山市', '2', 'baoshan', '0875', '678000', 'B', '99.167133', '25.111802'); +INSERT INTO `yoshop_region` VALUES ('2707', '2706', '隆阳', '隆阳区', '中国,云南省,保山市,隆阳区', '3', 'longyang', '0875', '678000', 'L', '99.16334', '25.11163'); +INSERT INTO `yoshop_region` VALUES ('2708', '2706', '施甸', '施甸县', '中国,云南省,保山市,施甸县', '3', 'shidian', '0875', '678200', 'S', '99.18768', '24.72418'); +INSERT INTO `yoshop_region` VALUES ('2709', '2706', '腾冲', '腾冲县', '中国,云南省,保山市,腾冲县', '3', 'tengchong', '0875', '679100', 'T', '98.49414', '25.02539'); +INSERT INTO `yoshop_region` VALUES ('2710', '2706', '龙陵', '龙陵县', '中国,云南省,保山市,龙陵县', '3', 'longling', '0875', '678300', 'L', '98.69024', '24.58746'); +INSERT INTO `yoshop_region` VALUES ('2711', '2706', '昌宁', '昌宁县', '中国,云南省,保山市,昌宁县', '3', 'changning', '0875', '678100', 'C', '99.6036', '24.82763'); +INSERT INTO `yoshop_region` VALUES ('2712', '2670', '昭通', '昭通市', '中国,云南省,昭通市', '2', 'zhaotong', '0870', '657000', 'Z', '103.717216', '27.336999'); +INSERT INTO `yoshop_region` VALUES ('2713', '2712', '昭阳', '昭阳区', '中国,云南省,昭通市,昭阳区', '3', 'zhaoyang', '0870', '657000', 'Z', '103.70654', '27.31998'); +INSERT INTO `yoshop_region` VALUES ('2714', '2712', '鲁甸', '鲁甸县', '中国,云南省,昭通市,鲁甸县', '3', 'ludian', '0870', '657100', 'L', '103.54721', '27.19238'); +INSERT INTO `yoshop_region` VALUES ('2715', '2712', '巧家', '巧家县', '中国,云南省,昭通市,巧家县', '3', 'qiaojia', '0870', '654600', 'Q', '102.92416', '26.91237'); +INSERT INTO `yoshop_region` VALUES ('2716', '2712', '盐津', '盐津县', '中国,云南省,昭通市,盐津县', '3', 'yanjin', '0870', '657500', 'Y', '104.23461', '28.10856'); +INSERT INTO `yoshop_region` VALUES ('2717', '2712', '大关', '大关县', '中国,云南省,昭通市,大关县', '3', 'daguan', '0870', '657400', 'D', '103.89254', '27.7488'); +INSERT INTO `yoshop_region` VALUES ('2718', '2712', '永善', '永善县', '中国,云南省,昭通市,永善县', '3', 'yongshan', '0870', '657300', 'Y', '103.63504', '28.2279'); +INSERT INTO `yoshop_region` VALUES ('2719', '2712', '绥江', '绥江县', '中国,云南省,昭通市,绥江县', '3', 'suijiang', '0870', '657700', 'S', '103.94937', '28.59661'); +INSERT INTO `yoshop_region` VALUES ('2720', '2712', '镇雄', '镇雄县', '中国,云南省,昭通市,镇雄县', '3', 'zhenxiong', '0870', '657200', 'Z', '104.87258', '27.43981'); +INSERT INTO `yoshop_region` VALUES ('2721', '2712', '彝良', '彝良县', '中国,云南省,昭通市,彝良县', '3', 'yiliang', '0870', '657600', 'Y', '104.04983', '27.62809'); +INSERT INTO `yoshop_region` VALUES ('2722', '2712', '威信', '威信县', '中国,云南省,昭通市,威信县', '3', 'weixin', '0870', '657900', 'W', '105.04754', '27.84065'); +INSERT INTO `yoshop_region` VALUES ('2723', '2712', '水富', '水富县', '中国,云南省,昭通市,水富县', '3', 'shuifu', '0870', '657800', 'S', '104.4158', '28.62986'); +INSERT INTO `yoshop_region` VALUES ('2724', '2670', '丽江', '丽江市', '中国,云南省,丽江市', '2', 'lijiang', '0888', '674100', 'L', '100.233026', '26.872108'); +INSERT INTO `yoshop_region` VALUES ('2725', '2724', '古城', '古城区', '中国,云南省,丽江市,古城区', '3', 'gucheng', '0888', '674100', 'G', '100.2257', '26.87697'); +INSERT INTO `yoshop_region` VALUES ('2726', '2724', '玉龙', '玉龙纳西族自治县', '中国,云南省,丽江市,玉龙纳西族自治县', '3', 'yulong', '0888', '674100', 'Y', '100.2369', '26.82149'); +INSERT INTO `yoshop_region` VALUES ('2727', '2724', '永胜', '永胜县', '中国,云南省,丽江市,永胜县', '3', 'yongsheng', '0888', '674200', 'Y', '100.74667', '26.68591'); +INSERT INTO `yoshop_region` VALUES ('2728', '2724', '华坪', '华坪县', '中国,云南省,丽江市,华坪县', '3', 'huaping', '0888', '674800', 'H', '101.26562', '26.62967'); +INSERT INTO `yoshop_region` VALUES ('2729', '2724', '宁蒗', '宁蒗彝族自治县', '中国,云南省,丽江市,宁蒗彝族自治县', '3', 'ninglang', '0888', '674300', 'N', '100.8507', '27.28179'); +INSERT INTO `yoshop_region` VALUES ('2730', '2670', '普洱', '普洱市', '中国,云南省,普洱市', '2', 'pu\'er', '0879', '665000', 'P', '100.972344', '22.777321'); +INSERT INTO `yoshop_region` VALUES ('2731', '2730', '思茅', '思茅区', '中国,云南省,普洱市,思茅区', '3', 'simao', '0879', '665000', 'S', '100.97716', '22.78691'); +INSERT INTO `yoshop_region` VALUES ('2732', '2730', '宁洱', '宁洱哈尼族彝族自治县', '中国,云南省,普洱市,宁洱哈尼族彝族自治县', '3', 'ninger', '0879', '665100', 'N', '101.04653', '23.06341'); +INSERT INTO `yoshop_region` VALUES ('2733', '2730', '墨江', '墨江哈尼族自治县', '中国,云南省,普洱市,墨江哈尼族自治县', '3', 'mojiang', '0879', '654800', 'M', '101.69171', '23.43214'); +INSERT INTO `yoshop_region` VALUES ('2734', '2730', '景东', '景东彝族自治县', '中国,云南省,普洱市,景东彝族自治县', '3', 'jingdong', '0879', '676200', 'J', '100.83599', '24.44791'); +INSERT INTO `yoshop_region` VALUES ('2735', '2730', '景谷', '景谷傣族彝族自治县', '中国,云南省,普洱市,景谷傣族彝族自治县', '3', 'jinggu', '0879', '666400', 'J', '100.70251', '23.49705'); +INSERT INTO `yoshop_region` VALUES ('2736', '2730', '镇沅', '镇沅彝族哈尼族拉祜族自治县', '中国,云南省,普洱市,镇沅彝族哈尼族拉祜族自治县', '3', 'zhenyuan', '0879', '666500', 'Z', '101.10675', '24.00557'); +INSERT INTO `yoshop_region` VALUES ('2737', '2730', '江城', '江城哈尼族彝族自治县', '中国,云南省,普洱市,江城哈尼族彝族自治县', '3', 'jiangcheng', '0879', '665900', 'J', '101.85788', '22.58424'); +INSERT INTO `yoshop_region` VALUES ('2738', '2730', '孟连', '孟连傣族拉祜族佤族自治县', '中国,云南省,普洱市,孟连傣族拉祜族佤族自治县', '3', 'menglian', '0879', '665800', 'M', '99.58424', '22.32922'); +INSERT INTO `yoshop_region` VALUES ('2739', '2730', '澜沧', '澜沧拉祜族自治县', '中国,云南省,普洱市,澜沧拉祜族自治县', '3', 'lancang', '0879', '665600', 'L', '99.93591', '22.55474'); +INSERT INTO `yoshop_region` VALUES ('2740', '2730', '西盟', '西盟佤族自治县', '中国,云南省,普洱市,西盟佤族自治县', '3', 'ximeng', '0879', '665700', 'X', '99.59869', '22.64774'); +INSERT INTO `yoshop_region` VALUES ('2741', '2670', '临沧', '临沧市', '中国,云南省,临沧市', '2', 'lincang', '0883', '677000', 'L', '100.08697', '23.886567'); +INSERT INTO `yoshop_region` VALUES ('2742', '2741', '临翔', '临翔区', '中国,云南省,临沧市,临翔区', '3', 'linxiang', '0883', '677000', 'L', '100.08242', '23.89497'); +INSERT INTO `yoshop_region` VALUES ('2743', '2741', '凤庆', '凤庆县', '中国,云南省,临沧市,凤庆县', '3', 'fengqing', '0883', '675900', 'F', '99.92837', '24.58034'); +INSERT INTO `yoshop_region` VALUES ('2744', '2741', '云县', '云县', '中国,云南省,临沧市,云县', '3', 'yunxian', '0883', '675800', 'Y', '100.12808', '24.44675'); +INSERT INTO `yoshop_region` VALUES ('2745', '2741', '永德', '永德县', '中国,云南省,临沧市,永德县', '3', 'yongde', '0883', '677600', 'Y', '99.25326', '24.0276'); +INSERT INTO `yoshop_region` VALUES ('2746', '2741', '镇康', '镇康县', '中国,云南省,临沧市,镇康县', '3', 'zhenkang', '0883', '677704', 'Z', '98.8255', '23.76241'); +INSERT INTO `yoshop_region` VALUES ('2747', '2741', '双江', '双江拉祜族佤族布朗族傣族自治县', '中国,云南省,临沧市,双江拉祜族佤族布朗族傣族自治县', '3', 'shuangjiang', '0883', '677300', 'S', '99.82769', '23.47349'); +INSERT INTO `yoshop_region` VALUES ('2748', '2741', '耿马', '耿马傣族佤族自治县', '中国,云南省,临沧市,耿马傣族佤族自治县', '3', 'gengma', '0883', '677500', 'G', '99.39785', '23.53776'); +INSERT INTO `yoshop_region` VALUES ('2749', '2741', '沧源', '沧源佤族自治县', '中国,云南省,临沧市,沧源佤族自治县', '3', 'cangyuan', '0883', '677400', 'C', '99.24545', '23.14821'); +INSERT INTO `yoshop_region` VALUES ('2750', '2670', '楚雄', '楚雄彝族自治州', '中国,云南省,楚雄彝族自治州', '2', 'chuxiong', '0878', '675000', 'C', '101.546046', '25.041988'); +INSERT INTO `yoshop_region` VALUES ('2751', '2750', '楚雄', '楚雄市', '中国,云南省,楚雄彝族自治州,楚雄市', '3', 'chuxiong', '0878', '675000', 'C', '101.54615', '25.0329'); +INSERT INTO `yoshop_region` VALUES ('2752', '2750', '双柏', '双柏县', '中国,云南省,楚雄彝族自治州,双柏县', '3', 'shuangbai', '0878', '675100', 'S', '101.64205', '24.68882'); +INSERT INTO `yoshop_region` VALUES ('2753', '2750', '牟定', '牟定县', '中国,云南省,楚雄彝族自治州,牟定县', '3', 'mouding', '0878', '675500', 'M', '101.54', '25.31551'); +INSERT INTO `yoshop_region` VALUES ('2754', '2750', '南华', '南华县', '中国,云南省,楚雄彝族自治州,南华县', '3', 'nanhua', '0878', '675200', 'N', '101.27313', '25.19293'); +INSERT INTO `yoshop_region` VALUES ('2755', '2750', '姚安', '姚安县', '中国,云南省,楚雄彝族自治州,姚安县', '3', 'yao\'an', '0878', '675300', 'Y', '101.24279', '25.50467'); +INSERT INTO `yoshop_region` VALUES ('2756', '2750', '大姚', '大姚县', '中国,云南省,楚雄彝族自治州,大姚县', '3', 'dayao', '0878', '675400', 'D', '101.32397', '25.72218'); +INSERT INTO `yoshop_region` VALUES ('2757', '2750', '永仁', '永仁县', '中国,云南省,楚雄彝族自治州,永仁县', '3', 'yongren', '0878', '651400', 'Y', '101.6716', '26.05794'); +INSERT INTO `yoshop_region` VALUES ('2758', '2750', '元谋', '元谋县', '中国,云南省,楚雄彝族自治州,元谋县', '3', 'yuanmou', '0878', '651300', 'Y', '101.87728', '25.70438'); +INSERT INTO `yoshop_region` VALUES ('2759', '2750', '武定', '武定县', '中国,云南省,楚雄彝族自治州,武定县', '3', 'wuding', '0878', '651600', 'W', '102.4038', '25.5295'); +INSERT INTO `yoshop_region` VALUES ('2760', '2750', '禄丰', '禄丰县', '中国,云南省,楚雄彝族自治州,禄丰县', '3', 'lufeng', '0878', '651200', 'L', '102.07797', '25.14815'); +INSERT INTO `yoshop_region` VALUES ('2761', '2670', '红河', '红河哈尼族彝族自治州', '中国,云南省,红河哈尼族彝族自治州', '2', 'honghe', '0873', '661400', 'H', '103.384182', '23.366775'); +INSERT INTO `yoshop_region` VALUES ('2762', '2761', '个旧', '个旧市', '中国,云南省,红河哈尼族彝族自治州,个旧市', '3', 'gejiu', '0873', '661000', 'G', '103.15966', '23.35894'); +INSERT INTO `yoshop_region` VALUES ('2763', '2761', '开远', '开远市', '中国,云南省,红河哈尼族彝族自治州,开远市', '3', 'kaiyuan', '0873', '661600', 'K', '103.26986', '23.71012'); +INSERT INTO `yoshop_region` VALUES ('2764', '2761', '蒙自', '蒙自市', '中国,云南省,红河哈尼族彝族自治州,蒙自市', '3', 'mengzi', '0873', '661101', 'M', '103.385005', '23.366843'); +INSERT INTO `yoshop_region` VALUES ('2765', '2761', '弥勒', '弥勒市', '中国,云南省,红河哈尼族彝族自治州,弥勒市', '3', 'mile', '0873', '652300', 'M', '103.436988', '24.40837'); +INSERT INTO `yoshop_region` VALUES ('2766', '2761', '屏边', '屏边苗族自治县', '中国,云南省,红河哈尼族彝族自治州,屏边苗族自治县', '3', 'pingbian', '0873', '661200', 'P', '103.68554', '22.98425'); +INSERT INTO `yoshop_region` VALUES ('2767', '2761', '建水', '建水县', '中国,云南省,红河哈尼族彝族自治州,建水县', '3', 'jianshui', '0873', '654300', 'J', '102.82656', '23.63472'); +INSERT INTO `yoshop_region` VALUES ('2768', '2761', '石屏', '石屏县', '中国,云南省,红河哈尼族彝族自治州,石屏县', '3', 'shiping', '0873', '662200', 'S', '102.49408', '23.71441'); +INSERT INTO `yoshop_region` VALUES ('2769', '2761', '泸西', '泸西县', '中国,云南省,红河哈尼族彝族自治州,泸西县', '3', 'luxi', '0873', '652400', null, '103.76373', '24.52854'); +INSERT INTO `yoshop_region` VALUES ('2770', '2761', '元阳', '元阳县', '中国,云南省,红河哈尼族彝族自治州,元阳县', '3', 'yuanyang', '0873', '662400', 'Y', '102.83261', '23.22281'); +INSERT INTO `yoshop_region` VALUES ('2771', '2761', '红河县', '红河县', '中国,云南省,红河哈尼族彝族自治州,红河县', '3', 'honghexian', '0873', '654400', 'H', '102.42059', '23.36767'); +INSERT INTO `yoshop_region` VALUES ('2772', '2761', '金平', '金平苗族瑶族傣族自治县', '中国,云南省,红河哈尼族彝族自治州,金平苗族瑶族傣族自治县', '3', 'jinping', '0873', '661500', 'J', '103.22651', '22.77959'); +INSERT INTO `yoshop_region` VALUES ('2773', '2761', '绿春', '绿春县', '中国,云南省,红河哈尼族彝族自治州,绿春县', '3', 'lvchun', '0873', '662500', 'L', '102.39672', '22.99371'); +INSERT INTO `yoshop_region` VALUES ('2774', '2761', '河口', '河口瑶族自治县', '中国,云南省,红河哈尼族彝族自治州,河口瑶族自治县', '3', 'hekou', '0873', '661300', 'H', '103.93936', '22.52929'); +INSERT INTO `yoshop_region` VALUES ('2775', '2670', '文山', '文山壮族苗族自治州', '中国,云南省,文山壮族苗族自治州', '2', 'wenshan', '0876', '663000', 'W', '104.24401', '23.36951'); +INSERT INTO `yoshop_region` VALUES ('2776', '2775', '文山', '文山市', '中国,云南省,文山壮族苗族自治州,文山市', '3', 'wenshan', '0876', '663000', 'W', '104.244277', '23.369216'); +INSERT INTO `yoshop_region` VALUES ('2777', '2775', '砚山', '砚山县', '中国,云南省,文山壮族苗族自治州,砚山县', '3', 'yanshan', '0876', '663100', 'Y', '104.33306', '23.60723'); +INSERT INTO `yoshop_region` VALUES ('2778', '2775', '西畴', '西畴县', '中国,云南省,文山壮族苗族自治州,西畴县', '3', 'xichou', '0876', '663500', 'X', '104.67419', '23.43941'); +INSERT INTO `yoshop_region` VALUES ('2779', '2775', '麻栗坡', '麻栗坡县', '中国,云南省,文山壮族苗族自治州,麻栗坡县', '3', 'malipo', '0876', '663600', 'M', '104.70132', '23.12028'); +INSERT INTO `yoshop_region` VALUES ('2780', '2775', '马关', '马关县', '中国,云南省,文山壮族苗族自治州,马关县', '3', 'maguan', '0876', '663700', 'M', '104.39514', '23.01293'); +INSERT INTO `yoshop_region` VALUES ('2781', '2775', '丘北', '丘北县', '中国,云南省,文山壮族苗族自治州,丘北县', '3', 'qiubei', '0876', '663200', 'Q', '104.19256', '24.03957'); +INSERT INTO `yoshop_region` VALUES ('2782', '2775', '广南', '广南县', '中国,云南省,文山壮族苗族自治州,广南县', '3', 'guangnan', '0876', '663300', 'G', '105.05511', '24.0464'); +INSERT INTO `yoshop_region` VALUES ('2783', '2775', '富宁', '富宁县', '中国,云南省,文山壮族苗族自治州,富宁县', '3', 'funing', '0876', '663400', 'F', '105.63085', '23.62536'); +INSERT INTO `yoshop_region` VALUES ('2784', '2670', '西双版纳', '西双版纳傣族自治州', '中国,云南省,西双版纳傣族自治州', '2', 'xishuangbanna', '0691', '666100', 'X', '100.797941', '22.001724'); +INSERT INTO `yoshop_region` VALUES ('2785', '2784', '景洪', '景洪市', '中国,云南省,西双版纳傣族自治州,景洪市', '3', 'jinghong', '0691', '666100', 'J', '100.79977', '22.01071'); +INSERT INTO `yoshop_region` VALUES ('2786', '2784', '勐海', '勐海县', '中国,云南省,西双版纳傣族自治州,勐海县', '3', 'menghai', '0691', '666200', null, '100.44931', '21.96175'); +INSERT INTO `yoshop_region` VALUES ('2787', '2784', '勐腊', '勐腊县', '中国,云南省,西双版纳傣族自治州,勐腊县', '3', 'mengla', '0691', '666300', null, '101.56488', '21.48162'); +INSERT INTO `yoshop_region` VALUES ('2788', '2670', '大理', '大理白族自治州', '中国,云南省,大理白族自治州', '2', 'dali', '0872', '671000', 'D', '100.240037', '25.592765'); +INSERT INTO `yoshop_region` VALUES ('2789', '2788', '大理', '大理市', '中国,云南省,大理白族自治州,大理市', '3', 'dali', '0872', '671000', 'D', '100.22998', '25.59157'); +INSERT INTO `yoshop_region` VALUES ('2790', '2788', '漾濞', '漾濞彝族自治县', '中国,云南省,大理白族自治州,漾濞彝族自治县', '3', 'yangbi', '0872', '672500', 'Y', '99.95474', '25.6652'); +INSERT INTO `yoshop_region` VALUES ('2791', '2788', '祥云', '祥云县', '中国,云南省,大理白族自治州,祥云县', '3', 'xiangyun', '0872', '672100', 'X', '100.55761', '25.47342'); +INSERT INTO `yoshop_region` VALUES ('2792', '2788', '宾川', '宾川县', '中国,云南省,大理白族自治州,宾川县', '3', 'binchuan', '0872', '671600', 'B', '100.57666', '25.83144'); +INSERT INTO `yoshop_region` VALUES ('2793', '2788', '弥渡', '弥渡县', '中国,云南省,大理白族自治州,弥渡县', '3', 'midu', '0872', '675600', 'M', '100.49075', '25.34179'); +INSERT INTO `yoshop_region` VALUES ('2794', '2788', '南涧', '南涧彝族自治县', '中国,云南省,大理白族自治州,南涧彝族自治县', '3', 'nanjian', '0872', '675700', 'N', '100.50964', '25.04349'); +INSERT INTO `yoshop_region` VALUES ('2795', '2788', '巍山', '巍山彝族回族自治县', '中国,云南省,大理白族自治州,巍山彝族回族自治县', '3', 'weishan', '0872', '672400', 'W', '100.30612', '25.23197'); +INSERT INTO `yoshop_region` VALUES ('2796', '2788', '永平', '永平县', '中国,云南省,大理白族自治州,永平县', '3', 'yongping', '0872', '672600', 'Y', '99.54095', '25.46451'); +INSERT INTO `yoshop_region` VALUES ('2797', '2788', '云龙', '云龙县', '中国,云南省,大理白族自治州,云龙县', '3', 'yunlong', '0872', '672700', 'Y', '99.37255', '25.88505'); +INSERT INTO `yoshop_region` VALUES ('2798', '2788', '洱源', '洱源县', '中国,云南省,大理白族自治州,洱源县', '3', 'eryuan', '0872', '671200', 'E', '99.94903', '26.10829'); +INSERT INTO `yoshop_region` VALUES ('2799', '2788', '剑川', '剑川县', '中国,云南省,大理白族自治州,剑川县', '3', 'jianchuan', '0872', '671300', 'J', '99.90545', '26.53688'); +INSERT INTO `yoshop_region` VALUES ('2800', '2788', '鹤庆', '鹤庆县', '中国,云南省,大理白族自治州,鹤庆县', '3', 'heqing', '0872', '671500', 'H', '100.17697', '26.55798'); +INSERT INTO `yoshop_region` VALUES ('2801', '2670', '德宏', '德宏傣族景颇族自治州', '中国,云南省,德宏傣族景颇族自治州', '2', 'dehong', '0692', '678400', 'D', '98.578363', '24.436694'); +INSERT INTO `yoshop_region` VALUES ('2802', '2801', '瑞丽', '瑞丽市', '中国,云南省,德宏傣族景颇族自治州,瑞丽市', '3', 'ruili', '0692', '678600', 'R', '97.85183', '24.01277'); +INSERT INTO `yoshop_region` VALUES ('2803', '2801', '芒市', '芒市', '中国,云南省,德宏傣族景颇族自治州,芒市', '3', 'mangshi', '0692', '678400', 'M', '98.588641', '24.433735'); +INSERT INTO `yoshop_region` VALUES ('2804', '2801', '梁河', '梁河县', '中国,云南省,德宏傣族景颇族自治州,梁河县', '3', 'lianghe', '0692', '679200', 'L', '98.29705', '24.80658'); +INSERT INTO `yoshop_region` VALUES ('2805', '2801', '盈江', '盈江县', '中国,云南省,德宏傣族景颇族自治州,盈江县', '3', 'yingjiang', '0692', '679300', 'Y', '97.93179', '24.70579'); +INSERT INTO `yoshop_region` VALUES ('2806', '2801', '陇川', '陇川县', '中国,云南省,德宏傣族景颇族自治州,陇川县', '3', 'longchuan', '0692', '678700', 'L', '97.79199', '24.18302'); +INSERT INTO `yoshop_region` VALUES ('2807', '2670', '怒江', '怒江傈僳族自治州', '中国,云南省,怒江傈僳族自治州', '2', 'nujiang', '0886', '673100', 'N', '98.854304', '25.850949'); +INSERT INTO `yoshop_region` VALUES ('2808', '2807', '泸水', '泸水县', '中国,云南省,怒江傈僳族自治州,泸水县', '3', 'lushui', '0886', '673100', null, '98.85534', '25.83772'); +INSERT INTO `yoshop_region` VALUES ('2809', '2807', '福贡', '福贡县', '中国,云南省,怒江傈僳族自治州,福贡县', '3', 'fugong', '0886', '673400', 'F', '98.86969', '26.90366'); +INSERT INTO `yoshop_region` VALUES ('2810', '2807', '贡山', '贡山独龙族怒族自治县', '中国,云南省,怒江傈僳族自治州,贡山独龙族怒族自治县', '3', 'gongshan', '0886', '673500', 'G', '98.66583', '27.74088'); +INSERT INTO `yoshop_region` VALUES ('2811', '2807', '兰坪', '兰坪白族普米族自治县', '中国,云南省,怒江傈僳族自治州,兰坪白族普米族自治县', '3', 'lanping', '0886', '671400', 'L', '99.41891', '26.45251'); +INSERT INTO `yoshop_region` VALUES ('2812', '2670', '迪庆', '迪庆藏族自治州', '中国,云南省,迪庆藏族自治州', '2', 'deqen', '0887', '674400', 'D', '99.706463', '27.826853'); +INSERT INTO `yoshop_region` VALUES ('2813', '2812', '香格里拉', '香格里拉市', '中国,云南省,迪庆藏族自治州,香格里拉市', '3', 'xianggelila', '0887', '674400', 'X', '99.70601', '27.82308'); +INSERT INTO `yoshop_region` VALUES ('2814', '2812', '德钦', '德钦县', '中国,云南省,迪庆藏族自治州,德钦县', '3', 'deqin', '0887', '674500', 'D', '98.91082', '28.4863'); +INSERT INTO `yoshop_region` VALUES ('2815', '2812', '维西', '维西傈僳族自治县', '中国,云南省,迪庆藏族自治州,维西傈僳族自治县', '3', 'weixi', '0887', '674600', 'W', '99.28402', '27.1793'); +INSERT INTO `yoshop_region` VALUES ('2816', '0', '西藏', '西藏自治区', '中国,西藏自治区', '1', 'tibet', '', '', 'X', '91.132212', '29.660361'); +INSERT INTO `yoshop_region` VALUES ('2817', '2816', '拉萨', '拉萨市', '中国,西藏自治区,拉萨市', '2', 'lhasa', '0891', '850000', 'L', '91.132212', '29.660361'); +INSERT INTO `yoshop_region` VALUES ('2818', '2817', '城关', '城关区', '中国,西藏自治区,拉萨市,城关区', '3', 'chengguan', '0891', '850000', 'C', '91.13859', '29.65312'); +INSERT INTO `yoshop_region` VALUES ('2819', '2817', '林周', '林周县', '中国,西藏自治区,拉萨市,林周县', '3', 'linzhou', '0891', '851600', 'L', '91.2586', '29.89445'); +INSERT INTO `yoshop_region` VALUES ('2820', '2817', '当雄', '当雄县', '中国,西藏自治区,拉萨市,当雄县', '3', 'dangxiong', '0891', '851500', 'D', '91.10076', '30.48309'); +INSERT INTO `yoshop_region` VALUES ('2821', '2817', '尼木', '尼木县', '中国,西藏自治区,拉萨市,尼木县', '3', 'nimu', '0891', '851300', 'N', '90.16378', '29.43353'); +INSERT INTO `yoshop_region` VALUES ('2822', '2817', '曲水', '曲水县', '中国,西藏自治区,拉萨市,曲水县', '3', 'qushui', '0891', '850600', 'Q', '90.73187', '29.35636'); +INSERT INTO `yoshop_region` VALUES ('2823', '2817', '堆龙德庆', '堆龙德庆县', '中国,西藏自治区,拉萨市,堆龙德庆县', '3', 'duilongdeqing', '0891', '851400', 'D', '91.00033', '29.65002'); +INSERT INTO `yoshop_region` VALUES ('2824', '2817', '达孜', '达孜县', '中国,西藏自治区,拉萨市,达孜县', '3', 'dazi', '0891', '850100', 'D', '91.35757', '29.6722'); +INSERT INTO `yoshop_region` VALUES ('2825', '2817', '墨竹工卡', '墨竹工卡县', '中国,西藏自治区,拉萨市,墨竹工卡县', '3', 'mozhugongka', '0891', '850200', 'M', '91.72814', '29.83614'); +INSERT INTO `yoshop_region` VALUES ('2826', '2816', '日喀则', '日喀则市', '中国,西藏自治区,日喀则市', '2', 'rikaze', '0892', '857000', 'R', '88.884874', '29.263792'); +INSERT INTO `yoshop_region` VALUES ('2827', '2826', '桑珠孜', '桑珠孜区', '中国,西藏自治区,日喀则市,桑珠孜区', '3', 'sangzhuzi', '0892', '857000', 'S', '88.880367', '29.269565'); +INSERT INTO `yoshop_region` VALUES ('2828', '2826', '南木林', '南木林县', '中国,西藏自治区,日喀则市,南木林县', '3', 'nanmulin', '0892', '857100', 'N', '89.09686', '29.68206'); +INSERT INTO `yoshop_region` VALUES ('2829', '2826', '江孜', '江孜县', '中国,西藏自治区,日喀则市,江孜县', '3', 'jiangzi', '0892', '857400', 'J', '89.60263', '28.91744'); +INSERT INTO `yoshop_region` VALUES ('2830', '2826', '定日', '定日县', '中国,西藏自治区,日喀则市,定日县', '3', 'dingri', '0892', '858200', 'D', '87.12176', '28.66129'); +INSERT INTO `yoshop_region` VALUES ('2831', '2826', '萨迦', '萨迦县', '中国,西藏自治区,日喀则市,萨迦县', '3', 'sajia', '0892', '857800', 'S', '88.02191', '28.90299'); +INSERT INTO `yoshop_region` VALUES ('2832', '2826', '拉孜', '拉孜县', '中国,西藏自治区,日喀则市,拉孜县', '3', 'lazi', '0892', '858100', 'L', '87.63412', '29.085'); +INSERT INTO `yoshop_region` VALUES ('2833', '2826', '昂仁', '昂仁县', '中国,西藏自治区,日喀则市,昂仁县', '3', 'angren', '0892', '858500', 'A', '87.23858', '29.29496'); +INSERT INTO `yoshop_region` VALUES ('2834', '2826', '谢通门', '谢通门县', '中国,西藏自治区,日喀则市,谢通门县', '3', 'xietongmen', '0892', '858900', 'X', '88.26242', '29.43337'); +INSERT INTO `yoshop_region` VALUES ('2835', '2826', '白朗', '白朗县', '中国,西藏自治区,日喀则市,白朗县', '3', 'bailang', '0892', '857300', 'B', '89.26205', '29.10553'); +INSERT INTO `yoshop_region` VALUES ('2836', '2826', '仁布', '仁布县', '中国,西藏自治区,日喀则市,仁布县', '3', 'renbu', '0892', '857200', 'R', '89.84228', '29.2301'); +INSERT INTO `yoshop_region` VALUES ('2837', '2826', '康马', '康马县', '中国,西藏自治区,日喀则市,康马县', '3', 'kangma', '0892', '857500', 'K', '89.68527', '28.5567'); +INSERT INTO `yoshop_region` VALUES ('2838', '2826', '定结', '定结县', '中国,西藏自治区,日喀则市,定结县', '3', 'dingjie', '0892', '857900', 'D', '87.77255', '28.36403'); +INSERT INTO `yoshop_region` VALUES ('2839', '2826', '仲巴', '仲巴县', '中国,西藏自治区,日喀则市,仲巴县', '3', 'zhongba', '0892', '858800', 'Z', '84.02951', '29.76595'); +INSERT INTO `yoshop_region` VALUES ('2840', '2826', '亚东', '亚东县', '中国,西藏自治区,日喀则市,亚东县', '3', 'yadong', '0892', '857600', 'Y', '88.90802', '27.4839'); +INSERT INTO `yoshop_region` VALUES ('2841', '2826', '吉隆', '吉隆县', '中国,西藏自治区,日喀则市,吉隆县', '3', 'jilong', '0892', '858700', 'J', '85.29846', '28.85382'); +INSERT INTO `yoshop_region` VALUES ('2842', '2826', '聂拉木', '聂拉木县', '中国,西藏自治区,日喀则市,聂拉木县', '3', 'nielamu', '0892', '858300', 'N', '85.97998', '28.15645'); +INSERT INTO `yoshop_region` VALUES ('2843', '2826', '萨嘎', '萨嘎县', '中国,西藏自治区,日喀则市,萨嘎县', '3', 'saga', '0892', '857800', 'S', '85.23413', '29.32936'); +INSERT INTO `yoshop_region` VALUES ('2844', '2826', '岗巴', '岗巴县', '中国,西藏自治区,日喀则市,岗巴县', '3', 'gangba', '0892', '857700', 'G', '88.52069', '28.27504'); +INSERT INTO `yoshop_region` VALUES ('2845', '2816', '昌都', '昌都市', '中国,西藏自治区,昌都市', '2', 'qamdo', '0895', '854000', 'C', '97.178452', '31.136875'); +INSERT INTO `yoshop_region` VALUES ('2846', '2845', '昌都', '卡若区', '中国,西藏自治区,昌都市,卡若区', '3', 'karuo', '0895', '854000', 'K', '97.18043', '31.1385'); +INSERT INTO `yoshop_region` VALUES ('2847', '2845', '江达', '江达县', '中国,西藏自治区,昌都市,江达县', '3', 'jiangda', '0895', '854100', 'J', '98.21865', '31.50343'); +INSERT INTO `yoshop_region` VALUES ('2848', '2845', '贡觉', '贡觉县', '中国,西藏自治区,昌都市,贡觉县', '3', 'gongjue', '0895', '854200', 'G', '98.27163', '30.85941'); +INSERT INTO `yoshop_region` VALUES ('2849', '2845', '类乌齐', '类乌齐县', '中国,西藏自治区,昌都市,类乌齐县', '3', 'leiwuqi', '0895', '855600', 'L', '96.60015', '31.21207'); +INSERT INTO `yoshop_region` VALUES ('2850', '2845', '丁青', '丁青县', '中国,西藏自治区,昌都市,丁青县', '3', 'dingqing', '0895', '855700', 'D', '95.59362', '31.41621'); +INSERT INTO `yoshop_region` VALUES ('2851', '2845', '察雅', '察雅县', '中国,西藏自治区,昌都市,察雅县', '3', 'chaya', '0895', '854300', 'C', '97.56521', '30.65336'); +INSERT INTO `yoshop_region` VALUES ('2852', '2845', '八宿', '八宿县', '中国,西藏自治区,昌都市,八宿县', '3', 'basu', '0895', '854600', 'B', '96.9176', '30.05346'); +INSERT INTO `yoshop_region` VALUES ('2853', '2845', '左贡', '左贡县', '中国,西藏自治区,昌都市,左贡县', '3', 'zuogong', '0895', '854400', 'Z', '97.84429', '29.67108'); +INSERT INTO `yoshop_region` VALUES ('2854', '2845', '芒康', '芒康县', '中国,西藏自治区,昌都市,芒康县', '3', 'mangkang', '0895', '854500', 'M', '98.59378', '29.67946'); +INSERT INTO `yoshop_region` VALUES ('2855', '2845', '洛隆', '洛隆县', '中国,西藏自治区,昌都市,洛隆县', '3', 'luolong', '0895', '855400', 'L', '95.82644', '30.74049'); +INSERT INTO `yoshop_region` VALUES ('2856', '2845', '边坝', '边坝县', '中国,西藏自治区,昌都市,边坝县', '3', 'bianba', '0895', '855500', 'B', '94.70687', '30.93434'); +INSERT INTO `yoshop_region` VALUES ('2857', '2816', '山南', '山南地区', '中国,西藏自治区,山南地区', '2', 'shannan', '0893', '856000', 'S', '91.766529', '29.236023'); +INSERT INTO `yoshop_region` VALUES ('2858', '2857', '乃东', '乃东县', '中国,西藏自治区,山南地区,乃东县', '3', 'naidong', '0893', '856100', 'N', '91.76153', '29.2249'); +INSERT INTO `yoshop_region` VALUES ('2859', '2857', '扎囊', '扎囊县', '中国,西藏自治区,山南地区,扎囊县', '3', 'zhanang', '0893', '850800', 'Z', '91.33288', '29.2399'); +INSERT INTO `yoshop_region` VALUES ('2860', '2857', '贡嘎', '贡嘎县', '中国,西藏自治区,山南地区,贡嘎县', '3', 'gongga', '0893', '850700', 'G', '90.98867', '29.29408'); +INSERT INTO `yoshop_region` VALUES ('2861', '2857', '桑日', '桑日县', '中国,西藏自治区,山南地区,桑日县', '3', 'sangri', '0893', '856200', 'S', '92.02005', '29.26643'); +INSERT INTO `yoshop_region` VALUES ('2862', '2857', '琼结', '琼结县', '中国,西藏自治区,山南地区,琼结县', '3', 'qiongjie', '0893', '856800', 'Q', '91.68093', '29.02632'); +INSERT INTO `yoshop_region` VALUES ('2863', '2857', '曲松', '曲松县', '中国,西藏自治区,山南地区,曲松县', '3', 'qusong', '0893', '856300', 'Q', '92.20263', '29.06412'); +INSERT INTO `yoshop_region` VALUES ('2864', '2857', '措美', '措美县', '中国,西藏自治区,山南地区,措美县', '3', 'cuomei', '0893', '856900', 'C', '91.43237', '28.43794'); +INSERT INTO `yoshop_region` VALUES ('2865', '2857', '洛扎', '洛扎县', '中国,西藏自治区,山南地区,洛扎县', '3', 'luozha', '0893', '856600', 'L', '90.86035', '28.3872'); +INSERT INTO `yoshop_region` VALUES ('2866', '2857', '加查', '加查县', '中国,西藏自治区,山南地区,加查县', '3', 'jiacha', '0893', '856400', 'J', '92.57702', '29.13973'); +INSERT INTO `yoshop_region` VALUES ('2867', '2857', '隆子', '隆子县', '中国,西藏自治区,山南地区,隆子县', '3', 'longzi', '0893', '856600', 'L', '92.46148', '28.40797'); +INSERT INTO `yoshop_region` VALUES ('2868', '2857', '错那', '错那县', '中国,西藏自治区,山南地区,错那县', '3', 'cuona', '0893', '856700', 'C', '91.95752', '27.99224'); +INSERT INTO `yoshop_region` VALUES ('2869', '2857', '浪卡子', '浪卡子县', '中国,西藏自治区,山南地区,浪卡子县', '3', 'langkazi', '0893', '851100', 'L', '90.40002', '28.96948'); +INSERT INTO `yoshop_region` VALUES ('2870', '2816', '那曲', '那曲地区', '中国,西藏自治区,那曲地区', '2', 'nagqu', '0896', '852000', 'N', '92.060214', '31.476004'); +INSERT INTO `yoshop_region` VALUES ('2871', '2870', '那曲', '那曲县', '中国,西藏自治区,那曲地区,那曲县', '3', 'naqu', '0896', '852000', 'N', '92.0535', '31.46964'); +INSERT INTO `yoshop_region` VALUES ('2872', '2870', '嘉黎', '嘉黎县', '中国,西藏自治区,那曲地区,嘉黎县', '3', 'jiali', '0896', '852400', 'J', '93.24987', '30.64233'); +INSERT INTO `yoshop_region` VALUES ('2873', '2870', '比如', '比如县', '中国,西藏自治区,那曲地区,比如县', '3', 'biru', '0896', '852300', 'B', '93.68685', '31.4779'); +INSERT INTO `yoshop_region` VALUES ('2874', '2870', '聂荣', '聂荣县', '中国,西藏自治区,那曲地区,聂荣县', '3', 'nierong', '0896', '853500', 'N', '92.29574', '32.11193'); +INSERT INTO `yoshop_region` VALUES ('2875', '2870', '安多', '安多县', '中国,西藏自治区,那曲地区,安多县', '3', 'anduo', '0896', '853400', 'A', '91.6795', '32.26125'); +INSERT INTO `yoshop_region` VALUES ('2876', '2870', '申扎', '申扎县', '中国,西藏自治区,那曲地区,申扎县', '3', 'shenzha', '0896', '853100', 'S', '88.70776', '30.92995'); +INSERT INTO `yoshop_region` VALUES ('2877', '2870', '索县', '索县', '中国,西藏自治区,那曲地区,索县', '3', 'suoxian', '0896', '852200', 'S', '93.78295', '31.88427'); +INSERT INTO `yoshop_region` VALUES ('2878', '2870', '班戈', '班戈县', '中国,西藏自治区,那曲地区,班戈县', '3', 'bange', '0896', '852500', 'B', '90.01907', '31.36149'); +INSERT INTO `yoshop_region` VALUES ('2879', '2870', '巴青', '巴青县', '中国,西藏自治区,那曲地区,巴青县', '3', 'baqing', '0896', '852100', 'B', '94.05316', '31.91833'); +INSERT INTO `yoshop_region` VALUES ('2880', '2870', '尼玛', '尼玛县', '中国,西藏自治区,那曲地区,尼玛县', '3', 'nima', '0896', '852600', 'N', '87.25256', '31.79654'); +INSERT INTO `yoshop_region` VALUES ('2881', '2870', '双湖', '双湖县', '中国,西藏自治区,那曲地区,双湖县', '3', 'shuanghu', '0896', '852600', 'S', '88.837776', '33.189032'); +INSERT INTO `yoshop_region` VALUES ('2882', '2816', '阿里', '阿里地区', '中国,西藏自治区,阿里地区', '2', 'ngari', '0897', '859000', 'A', '80.105498', '32.503187'); +INSERT INTO `yoshop_region` VALUES ('2883', '2882', '普兰', '普兰县', '中国,西藏自治区,阿里地区,普兰县', '3', 'pulan', '0897', '859500', 'P', '81.177', '30.30002'); +INSERT INTO `yoshop_region` VALUES ('2884', '2882', '札达', '札达县', '中国,西藏自治区,阿里地区,札达县', '3', 'zhada', '0897', '859600', 'Z', '79.80255', '31.48345'); +INSERT INTO `yoshop_region` VALUES ('2885', '2882', '噶尔', '噶尔县', '中国,西藏自治区,阿里地区,噶尔县', '3', 'gaer', '0897', '859400', 'G', '80.09579', '32.50024'); +INSERT INTO `yoshop_region` VALUES ('2886', '2882', '日土', '日土县', '中国,西藏自治区,阿里地区,日土县', '3', 'ritu', '0897', '859700', 'R', '79.7131', '33.38741'); +INSERT INTO `yoshop_region` VALUES ('2887', '2882', '革吉', '革吉县', '中国,西藏自治区,阿里地区,革吉县', '3', 'geji', '0897', '859100', 'G', '81.151', '32.3964'); +INSERT INTO `yoshop_region` VALUES ('2888', '2882', '改则', '改则县', '中国,西藏自治区,阿里地区,改则县', '3', 'gaize', '0897', '859200', 'G', '84.06295', '32.30446'); +INSERT INTO `yoshop_region` VALUES ('2889', '2882', '措勤', '措勤县', '中国,西藏自治区,阿里地区,措勤县', '3', 'cuoqin', '0897', '859300', 'C', '85.16616', '31.02095'); +INSERT INTO `yoshop_region` VALUES ('2890', '2816', '林芝', '林芝地区', '中国,西藏自治区,林芝地区', '2', 'nyingchi', '0894', '850400', 'L', '94.362348', '29.654693'); +INSERT INTO `yoshop_region` VALUES ('2891', '2890', '林芝', '林芝县', '中国,西藏自治区,林芝地区,林芝县', '3', 'linzhi', '0894', '850400', 'L', '94.48391', '29.57562'); +INSERT INTO `yoshop_region` VALUES ('2892', '2890', '工布江达', '工布江达县', '中国,西藏自治区,林芝地区,工布江达县', '3', 'gongbujiangda', '0894', '850300', 'G', '93.2452', '29.88576'); +INSERT INTO `yoshop_region` VALUES ('2893', '2890', '米林', '米林县', '中国,西藏自治区,林芝地区,米林县', '3', 'milin', '0894', '850500', 'M', '94.21316', '29.21535'); +INSERT INTO `yoshop_region` VALUES ('2894', '2890', '墨脱', '墨脱县', '中国,西藏自治区,林芝地区,墨脱县', '3', 'motuo', '0894', '855300', 'M', '95.3316', '29.32698'); +INSERT INTO `yoshop_region` VALUES ('2895', '2890', '波密', '波密县', '中国,西藏自治区,林芝地区,波密县', '3', 'bomi', '0894', '855200', 'B', '95.77096', '29.85907'); +INSERT INTO `yoshop_region` VALUES ('2896', '2890', '察隅', '察隅县', '中国,西藏自治区,林芝地区,察隅县', '3', 'chayu', '0894', '855100', 'C', '97.46679', '28.6618'); +INSERT INTO `yoshop_region` VALUES ('2897', '2890', '朗县', '朗县', '中国,西藏自治区,林芝地区,朗县', '3', 'langxian', '0894', '856500', 'L', '93.0754', '29.04549'); +INSERT INTO `yoshop_region` VALUES ('2898', '0', '陕西', '陕西省', '中国,陕西省', '1', 'shaanxi', '', '', 'S', '108.948024', '34.263161'); +INSERT INTO `yoshop_region` VALUES ('2899', '2898', '西安', '西安市', '中国,陕西省,西安市', '2', 'xi\'an', '029', '710003', 'X', '108.948024', '34.263161'); +INSERT INTO `yoshop_region` VALUES ('2900', '2899', '新城', '新城区', '中国,陕西省,西安市,新城区', '3', 'xincheng', '029', '710004', 'X', '108.9608', '34.26641'); +INSERT INTO `yoshop_region` VALUES ('2901', '2899', '碑林', '碑林区', '中国,陕西省,西安市,碑林区', '3', 'beilin', '029', '710001', 'B', '108.93426', '34.2304'); +INSERT INTO `yoshop_region` VALUES ('2902', '2899', '莲湖', '莲湖区', '中国,陕西省,西安市,莲湖区', '3', 'lianhu', '029', '710003', 'L', '108.9401', '34.26709'); +INSERT INTO `yoshop_region` VALUES ('2903', '2899', '灞桥', '灞桥区', '中国,陕西省,西安市,灞桥区', '3', 'baqiao', '029', '710038', null, '109.06451', '34.27264'); +INSERT INTO `yoshop_region` VALUES ('2904', '2899', '未央', '未央区', '中国,陕西省,西安市,未央区', '3', 'weiyang', '029', '710014', 'W', '108.94683', '34.29296'); +INSERT INTO `yoshop_region` VALUES ('2905', '2899', '雁塔', '雁塔区', '中国,陕西省,西安市,雁塔区', '3', 'yanta', '029', '710061', 'Y', '108.94866', '34.22245'); +INSERT INTO `yoshop_region` VALUES ('2906', '2899', '阎良', '阎良区', '中国,陕西省,西安市,阎良区', '3', 'yanliang', '029', '710087', 'Y', '109.22616', '34.66221'); +INSERT INTO `yoshop_region` VALUES ('2907', '2899', '临潼', '临潼区', '中国,陕西省,西安市,临潼区', '3', 'lintong', '029', '710600', 'L', '109.21417', '34.36665'); +INSERT INTO `yoshop_region` VALUES ('2908', '2899', '长安', '长安区', '中国,陕西省,西安市,长安区', '3', 'chang\'an', '029', '710100', 'C', '108.94586', '34.15559'); +INSERT INTO `yoshop_region` VALUES ('2909', '2899', '蓝田', '蓝田县', '中国,陕西省,西安市,蓝田县', '3', 'lantian', '029', '710500', 'L', '109.32339', '34.15128'); +INSERT INTO `yoshop_region` VALUES ('2910', '2899', '周至', '周至县', '中国,陕西省,西安市,周至县', '3', 'zhouzhi', '029', '710400', 'Z', '108.22207', '34.16337'); +INSERT INTO `yoshop_region` VALUES ('2911', '2899', '户县', '户县', '中国,陕西省,西安市,户县', '3', 'huxian', '029', '710300', 'H', '108.60513', '34.10856'); +INSERT INTO `yoshop_region` VALUES ('2912', '2899', '高陵', '高陵区', '中国,陕西省,西安市,高陵区', '3', 'gaoling', '029', '710200', 'G', '109.08816', '34.53483'); +INSERT INTO `yoshop_region` VALUES ('2913', '2898', '铜川', '铜川市', '中国,陕西省,铜川市', '2', 'tongchuan', '0919', '727100', 'T', '108.963122', '34.90892'); +INSERT INTO `yoshop_region` VALUES ('2914', '2913', '王益', '王益区', '中国,陕西省,铜川市,王益区', '3', 'wangyi', '0919', '727000', 'W', '109.07564', '35.06896'); +INSERT INTO `yoshop_region` VALUES ('2915', '2913', '印台', '印台区', '中国,陕西省,铜川市,印台区', '3', 'yintai', '0919', '727007', 'Y', '109.10208', '35.1169'); +INSERT INTO `yoshop_region` VALUES ('2916', '2913', '耀州', '耀州区', '中国,陕西省,铜川市,耀州区', '3', 'yaozhou', '0919', '727100', 'Y', '108.98556', '34.91308'); +INSERT INTO `yoshop_region` VALUES ('2917', '2913', '宜君', '宜君县', '中国,陕西省,铜川市,宜君县', '3', 'yijun', '0919', '727200', 'Y', '109.11813', '35.40108'); +INSERT INTO `yoshop_region` VALUES ('2918', '2898', '宝鸡', '宝鸡市', '中国,陕西省,宝鸡市', '2', 'baoji', '0917', '721000', 'B', '107.14487', '34.369315'); +INSERT INTO `yoshop_region` VALUES ('2919', '2918', '渭滨', '渭滨区', '中国,陕西省,宝鸡市,渭滨区', '3', 'weibin', '0917', '721000', 'W', '107.14991', '34.37116'); +INSERT INTO `yoshop_region` VALUES ('2920', '2918', '金台', '金台区', '中国,陕西省,宝鸡市,金台区', '3', 'jintai', '0917', '721000', 'J', '107.14691', '34.37612'); +INSERT INTO `yoshop_region` VALUES ('2921', '2918', '陈仓', '陈仓区', '中国,陕西省,宝鸡市,陈仓区', '3', 'chencang', '0917', '721300', 'C', '107.38742', '34.35451'); +INSERT INTO `yoshop_region` VALUES ('2922', '2918', '凤翔', '凤翔县', '中国,陕西省,宝鸡市,凤翔县', '3', 'fengxiang', '0917', '721400', 'F', '107.39645', '34.52321'); +INSERT INTO `yoshop_region` VALUES ('2923', '2918', '岐山', '岐山县', '中国,陕西省,宝鸡市,岐山县', '3', 'qishan', '0917', '722400', null, '107.62173', '34.44378'); +INSERT INTO `yoshop_region` VALUES ('2924', '2918', '扶风', '扶风县', '中国,陕西省,宝鸡市,扶风县', '3', 'fufeng', '0917', '722200', 'F', '107.90017', '34.37524'); +INSERT INTO `yoshop_region` VALUES ('2925', '2918', '眉县', '眉县', '中国,陕西省,宝鸡市,眉县', '3', 'meixian', '0917', '722300', 'M', '107.75079', '34.27569'); +INSERT INTO `yoshop_region` VALUES ('2926', '2918', '陇县', '陇县', '中国,陕西省,宝鸡市,陇县', '3', 'longxian', '0917', '721200', 'L', '106.85946', '34.89404'); +INSERT INTO `yoshop_region` VALUES ('2927', '2918', '千阳', '千阳县', '中国,陕西省,宝鸡市,千阳县', '3', 'qianyang', '0917', '721100', 'Q', '107.13043', '34.64219'); +INSERT INTO `yoshop_region` VALUES ('2928', '2918', '麟游', '麟游县', '中国,陕西省,宝鸡市,麟游县', '3', 'linyou', '0917', '721500', null, '107.79623', '34.67844'); +INSERT INTO `yoshop_region` VALUES ('2929', '2918', '凤县', '凤县', '中国,陕西省,宝鸡市,凤县', '3', 'fengxian', '0917', '721700', 'F', '106.52356', '33.91172'); +INSERT INTO `yoshop_region` VALUES ('2930', '2918', '太白', '太白县', '中国,陕西省,宝鸡市,太白县', '3', 'taibai', '0917', '721600', 'T', '107.31646', '34.06207'); +INSERT INTO `yoshop_region` VALUES ('2931', '2898', '咸阳', '咸阳市', '中国,陕西省,咸阳市', '2', 'xianyang', '029', '712000', 'X', '108.705117', '34.333439'); +INSERT INTO `yoshop_region` VALUES ('2932', '2931', '秦都', '秦都区', '中国,陕西省,咸阳市,秦都区', '3', 'qindu', '029', '712000', 'Q', '108.71493', '34.33804'); +INSERT INTO `yoshop_region` VALUES ('2933', '2931', '杨陵', '杨陵区', '中国,陕西省,咸阳市,杨陵区', '3', 'yangling', '029', '712100', 'Y', '108.083481', '34.270434'); +INSERT INTO `yoshop_region` VALUES ('2934', '2931', '渭城', '渭城区', '中国,陕西省,咸阳市,渭城区', '3', 'weicheng', '029', '712000', 'W', '108.72227', '34.33198'); +INSERT INTO `yoshop_region` VALUES ('2935', '2931', '三原', '三原县', '中国,陕西省,咸阳市,三原县', '3', 'sanyuan', '029', '713800', 'S', '108.93194', '34.61556'); +INSERT INTO `yoshop_region` VALUES ('2936', '2931', '泾阳', '泾阳县', '中国,陕西省,咸阳市,泾阳县', '3', 'jingyang', '029', '713700', null, '108.84259', '34.52705'); +INSERT INTO `yoshop_region` VALUES ('2937', '2931', '乾县', '乾县', '中国,陕西省,咸阳市,乾县', '3', 'qianxian', '029', '713300', 'Q', '108.24231', '34.52946'); +INSERT INTO `yoshop_region` VALUES ('2938', '2931', '礼泉', '礼泉县', '中国,陕西省,咸阳市,礼泉县', '3', 'liquan', '029', '713200', 'L', '108.4263', '34.48455'); +INSERT INTO `yoshop_region` VALUES ('2939', '2931', '永寿', '永寿县', '中国,陕西省,咸阳市,永寿县', '3', 'yongshou', '029', '713400', 'Y', '108.14474', '34.69081'); +INSERT INTO `yoshop_region` VALUES ('2940', '2931', '彬县', '彬县', '中国,陕西省,咸阳市,彬县', '3', 'binxian', '029', '713500', 'B', '108.08468', '35.0342'); +INSERT INTO `yoshop_region` VALUES ('2941', '2931', '长武', '长武县', '中国,陕西省,咸阳市,长武县', '3', 'changwu', '029', '713600', 'C', '107.7951', '35.2067'); +INSERT INTO `yoshop_region` VALUES ('2942', '2931', '旬邑', '旬邑县', '中国,陕西省,咸阳市,旬邑县', '3', 'xunyi', '029', '711300', 'X', '108.3341', '35.11338'); +INSERT INTO `yoshop_region` VALUES ('2943', '2931', '淳化', '淳化县', '中国,陕西省,咸阳市,淳化县', '3', 'chunhua', '029', '711200', 'C', '108.58026', '34.79886'); +INSERT INTO `yoshop_region` VALUES ('2944', '2931', '武功', '武功县', '中国,陕西省,咸阳市,武功县', '3', 'wugong', '029', '712200', 'W', '108.20434', '34.26003'); +INSERT INTO `yoshop_region` VALUES ('2945', '2931', '兴平', '兴平市', '中国,陕西省,咸阳市,兴平市', '3', 'xingping', '029', '713100', 'X', '108.49057', '34.29785'); +INSERT INTO `yoshop_region` VALUES ('2946', '2898', '渭南', '渭南市', '中国,陕西省,渭南市', '2', 'weinan', '0913', '714000', 'W', '109.502882', '34.499381'); +INSERT INTO `yoshop_region` VALUES ('2947', '2946', '临渭', '临渭区', '中国,陕西省,渭南市,临渭区', '3', 'linwei', '0913', '714000', 'L', '109.49296', '34.49822'); +INSERT INTO `yoshop_region` VALUES ('2948', '2946', '华县', '华县', '中国,陕西省,渭南市,华县', '3', 'huaxian', '0913', '714100', 'H', '109.77185', '34.51255'); +INSERT INTO `yoshop_region` VALUES ('2949', '2946', '潼关', '潼关县', '中国,陕西省,渭南市,潼关县', '3', 'tongguan', '0913', '714300', null, '110.24362', '34.54284'); +INSERT INTO `yoshop_region` VALUES ('2950', '2946', '大荔', '大荔县', '中国,陕西省,渭南市,大荔县', '3', 'dali', '0913', '715100', 'D', '109.94216', '34.79565'); +INSERT INTO `yoshop_region` VALUES ('2951', '2946', '合阳', '合阳县', '中国,陕西省,渭南市,合阳县', '3', 'heyang', '0913', '715300', 'H', '110.14862', '35.23805'); +INSERT INTO `yoshop_region` VALUES ('2952', '2946', '澄城', '澄城县', '中国,陕西省,渭南市,澄城县', '3', 'chengcheng', '0913', '715200', 'C', '109.93444', '35.18396'); +INSERT INTO `yoshop_region` VALUES ('2953', '2946', '蒲城', '蒲城县', '中国,陕西省,渭南市,蒲城县', '3', 'pucheng', '0913', '715500', 'P', '109.5903', '34.9568'); +INSERT INTO `yoshop_region` VALUES ('2954', '2946', '白水', '白水县', '中国,陕西省,渭南市,白水县', '3', 'baishui', '0913', '715600', 'B', '109.59286', '35.17863'); +INSERT INTO `yoshop_region` VALUES ('2955', '2946', '富平', '富平县', '中国,陕西省,渭南市,富平县', '3', 'fuping', '0913', '711700', 'F', '109.1802', '34.75109'); +INSERT INTO `yoshop_region` VALUES ('2956', '2946', '韩城', '韩城市', '中国,陕西省,渭南市,韩城市', '3', 'hancheng', '0913', '715400', 'H', '110.44238', '35.47926'); +INSERT INTO `yoshop_region` VALUES ('2957', '2946', '华阴', '华阴市', '中国,陕西省,渭南市,华阴市', '3', 'huayin', '0913', '714200', 'H', '110.08752', '34.56608'); +INSERT INTO `yoshop_region` VALUES ('2958', '2898', '延安', '延安市', '中国,陕西省,延安市', '2', 'yan\'an', '0911', '716000', 'Y', '109.49081', '36.596537'); +INSERT INTO `yoshop_region` VALUES ('2959', '2958', '宝塔', '宝塔区', '中国,陕西省,延安市,宝塔区', '3', 'baota', '0911', '716000', 'B', '109.49336', '36.59154'); +INSERT INTO `yoshop_region` VALUES ('2960', '2958', '延长', '延长县', '中国,陕西省,延安市,延长县', '3', 'yanchang', '0911', '717100', 'Y', '110.01083', '36.57904'); +INSERT INTO `yoshop_region` VALUES ('2961', '2958', '延川', '延川县', '中国,陕西省,延安市,延川县', '3', 'yanchuan', '0911', '717200', 'Y', '110.19415', '36.87817'); +INSERT INTO `yoshop_region` VALUES ('2962', '2958', '子长', '子长县', '中国,陕西省,延安市,子长县', '3', 'zichang', '0911', '717300', 'Z', '109.67532', '37.14253'); +INSERT INTO `yoshop_region` VALUES ('2963', '2958', '安塞', '安塞县', '中国,陕西省,延安市,安塞县', '3', 'ansai', '0911', '717400', 'A', '109.32708', '36.86507'); +INSERT INTO `yoshop_region` VALUES ('2964', '2958', '志丹', '志丹县', '中国,陕西省,延安市,志丹县', '3', 'zhidan', '0911', '717500', 'Z', '108.76815', '36.82177'); +INSERT INTO `yoshop_region` VALUES ('2965', '2958', '吴起', '吴起县', '中国,陕西省,延安市,吴起县', '3', 'wuqi', '0911', '717600', 'W', '108.17611', '36.92785'); +INSERT INTO `yoshop_region` VALUES ('2966', '2958', '甘泉', '甘泉县', '中国,陕西省,延安市,甘泉县', '3', 'ganquan', '0911', '716100', 'G', '109.35012', '36.27754'); +INSERT INTO `yoshop_region` VALUES ('2967', '2958', '富县', '富县', '中国,陕西省,延安市,富县', '3', 'fuxian', '0911', '727500', 'F', '109.37927', '35.98803'); +INSERT INTO `yoshop_region` VALUES ('2968', '2958', '洛川', '洛川县', '中国,陕西省,延安市,洛川县', '3', 'luochuan', '0911', '727400', 'L', '109.43286', '35.76076'); +INSERT INTO `yoshop_region` VALUES ('2969', '2958', '宜川', '宜川县', '中国,陕西省,延安市,宜川县', '3', 'yichuan', '0911', '716200', 'Y', '110.17196', '36.04732'); +INSERT INTO `yoshop_region` VALUES ('2970', '2958', '黄龙', '黄龙县', '中国,陕西省,延安市,黄龙县', '3', 'huanglong', '0911', '715700', 'H', '109.84259', '35.58349'); +INSERT INTO `yoshop_region` VALUES ('2971', '2958', '黄陵', '黄陵县', '中国,陕西省,延安市,黄陵县', '3', 'huangling', '0911', '727300', 'H', '109.26333', '35.58357'); +INSERT INTO `yoshop_region` VALUES ('2972', '2898', '汉中', '汉中市', '中国,陕西省,汉中市', '2', 'hanzhong', '0916', '723000', 'H', '107.028621', '33.077668'); +INSERT INTO `yoshop_region` VALUES ('2973', '2972', '汉台', '汉台区', '中国,陕西省,汉中市,汉台区', '3', 'hantai', '0916', '723000', 'H', '107.03187', '33.06774'); +INSERT INTO `yoshop_region` VALUES ('2974', '2972', '南郑', '南郑县', '中国,陕西省,汉中市,南郑县', '3', 'nanzheng', '0916', '723100', 'N', '106.94024', '33.00299'); +INSERT INTO `yoshop_region` VALUES ('2975', '2972', '城固', '城固县', '中国,陕西省,汉中市,城固县', '3', 'chenggu', '0916', '723200', 'C', '107.33367', '33.15661'); +INSERT INTO `yoshop_region` VALUES ('2976', '2972', '洋县', '洋县', '中国,陕西省,汉中市,洋县', '3', 'yangxian', '0916', '723300', 'Y', '107.54672', '33.22102'); +INSERT INTO `yoshop_region` VALUES ('2977', '2972', '西乡', '西乡县', '中国,陕西省,汉中市,西乡县', '3', 'xixiang', '0916', '723500', 'X', '107.76867', '32.98411'); +INSERT INTO `yoshop_region` VALUES ('2978', '2972', '勉县', '勉县', '中国,陕西省,汉中市,勉县', '3', 'mianxian', '0916', '724200', 'M', '106.67584', '33.15273'); +INSERT INTO `yoshop_region` VALUES ('2979', '2972', '宁强', '宁强县', '中国,陕西省,汉中市,宁强县', '3', 'ningqiang', '0916', '724400', 'N', '106.25958', '32.82881'); +INSERT INTO `yoshop_region` VALUES ('2980', '2972', '略阳', '略阳县', '中国,陕西省,汉中市,略阳县', '3', 'lueyang', '0916', '724300', 'L', '106.15399', '33.33009'); +INSERT INTO `yoshop_region` VALUES ('2981', '2972', '镇巴', '镇巴县', '中国,陕西省,汉中市,镇巴县', '3', 'zhenba', '0916', '723600', 'Z', '107.89648', '32.53487'); +INSERT INTO `yoshop_region` VALUES ('2982', '2972', '留坝', '留坝县', '中国,陕西省,汉中市,留坝县', '3', 'liuba', '0916', '724100', 'L', '106.92233', '33.61606'); +INSERT INTO `yoshop_region` VALUES ('2983', '2972', '佛坪', '佛坪县', '中国,陕西省,汉中市,佛坪县', '3', 'foping', '0916', '723400', 'F', '107.98974', '33.52496'); +INSERT INTO `yoshop_region` VALUES ('2984', '2898', '榆林', '榆林市', '中国,陕西省,榆林市', '2', 'yulin', '0912', '719000', 'Y', '109.741193', '38.290162'); +INSERT INTO `yoshop_region` VALUES ('2985', '2984', '榆阳', '榆阳区', '中国,陕西省,榆林市,榆阳区', '3', 'yuyang', '0912', '719000', 'Y', '109.73473', '38.27843'); +INSERT INTO `yoshop_region` VALUES ('2986', '2984', '神木', '神木县', '中国,陕西省,榆林市,神木县', '3', 'shenmu', '0912', '719300', 'S', '110.4989', '38.84234'); +INSERT INTO `yoshop_region` VALUES ('2987', '2984', '府谷', '府谷县', '中国,陕西省,榆林市,府谷县', '3', 'fugu', '0912', '719400', 'F', '111.06723', '39.02805'); +INSERT INTO `yoshop_region` VALUES ('2988', '2984', '横山', '横山县', '中国,陕西省,榆林市,横山县', '3', 'hengshan', '0912', '719100', 'H', '109.29568', '37.958'); +INSERT INTO `yoshop_region` VALUES ('2989', '2984', '靖边', '靖边县', '中国,陕西省,榆林市,靖边县', '3', 'jingbian', '0912', '718500', 'J', '108.79412', '37.59938'); +INSERT INTO `yoshop_region` VALUES ('2990', '2984', '定边', '定边县', '中国,陕西省,榆林市,定边县', '3', 'dingbian', '0912', '718600', 'D', '107.59793', '37.59037'); +INSERT INTO `yoshop_region` VALUES ('2991', '2984', '绥德', '绥德县', '中国,陕西省,榆林市,绥德县', '3', 'suide', '0912', '718000', 'S', '110.26126', '37.49778'); +INSERT INTO `yoshop_region` VALUES ('2992', '2984', '米脂', '米脂县', '中国,陕西省,榆林市,米脂县', '3', 'mizhi', '0912', '718100', 'M', '110.18417', '37.75529'); +INSERT INTO `yoshop_region` VALUES ('2993', '2984', '佳县', '佳县', '中国,陕西省,榆林市,佳县', '3', 'jiaxian', '0912', '719200', 'J', '110.49362', '38.02248'); +INSERT INTO `yoshop_region` VALUES ('2994', '2984', '吴堡', '吴堡县', '中国,陕西省,榆林市,吴堡县', '3', 'wubu', '0912', '718200', 'W', '110.74533', '37.45709'); +INSERT INTO `yoshop_region` VALUES ('2995', '2984', '清涧', '清涧县', '中国,陕西省,榆林市,清涧县', '3', 'qingjian', '0912', '718300', 'Q', '110.12173', '37.08854'); +INSERT INTO `yoshop_region` VALUES ('2996', '2984', '子洲', '子洲县', '中国,陕西省,榆林市,子洲县', '3', 'zizhou', '0912', '718400', 'Z', '110.03488', '37.61238'); +INSERT INTO `yoshop_region` VALUES ('2997', '2898', '安康', '安康市', '中国,陕西省,安康市', '2', 'ankang', '0915', '725000', 'A', '109.029273', '32.6903'); +INSERT INTO `yoshop_region` VALUES ('2998', '2997', '汉滨', '汉滨区', '中国,陕西省,安康市,汉滨区', '3', 'hanbin', '0915', '725000', 'H', '109.02683', '32.69517'); +INSERT INTO `yoshop_region` VALUES ('2999', '2997', '汉阴', '汉阴县', '中国,陕西省,安康市,汉阴县', '3', 'hanyin', '0915', '725100', 'H', '108.51098', '32.89129'); +INSERT INTO `yoshop_region` VALUES ('3000', '2997', '石泉', '石泉县', '中国,陕西省,安康市,石泉县', '3', 'shiquan', '0915', '725200', 'S', '108.24755', '33.03971'); +INSERT INTO `yoshop_region` VALUES ('3001', '2997', '宁陕', '宁陕县', '中国,陕西省,安康市,宁陕县', '3', 'ningshan', '0915', '711600', 'N', '108.31515', '33.31726'); +INSERT INTO `yoshop_region` VALUES ('3002', '2997', '紫阳', '紫阳县', '中国,陕西省,安康市,紫阳县', '3', 'ziyang', '0915', '725300', 'Z', '108.5368', '32.52115'); +INSERT INTO `yoshop_region` VALUES ('3003', '2997', '岚皋', '岚皋县', '中国,陕西省,安康市,岚皋县', '3', 'langao', '0915', '725400', null, '108.90289', '32.30794'); +INSERT INTO `yoshop_region` VALUES ('3004', '2997', '平利', '平利县', '中国,陕西省,安康市,平利县', '3', 'pingli', '0915', '725500', 'P', '109.35775', '32.39111'); +INSERT INTO `yoshop_region` VALUES ('3005', '2997', '镇坪', '镇坪县', '中国,陕西省,安康市,镇坪县', '3', 'zhenping', '0915', '725600', 'Z', '109.52456', '31.8833'); +INSERT INTO `yoshop_region` VALUES ('3006', '2997', '旬阳', '旬阳县', '中国,陕西省,安康市,旬阳县', '3', 'xunyang', '0915', '725700', 'X', '109.3619', '32.83207'); +INSERT INTO `yoshop_region` VALUES ('3007', '2997', '白河', '白河县', '中国,陕西省,安康市,白河县', '3', 'baihe', '0915', '725800', 'B', '110.11315', '32.80955'); +INSERT INTO `yoshop_region` VALUES ('3008', '2898', '商洛', '商洛市', '中国,陕西省,商洛市', '2', 'shangluo', '0914', '726000', 'S', '109.939776', '33.868319'); +INSERT INTO `yoshop_region` VALUES ('3009', '3008', '商州', '商州区', '中国,陕西省,商洛市,商州区', '3', 'shangzhou', '0914', '726000', 'S', '109.94126', '33.8627'); +INSERT INTO `yoshop_region` VALUES ('3010', '3008', '洛南', '洛南县', '中国,陕西省,商洛市,洛南县', '3', 'luonan', '0914', '726100', 'L', '110.14645', '34.08994'); +INSERT INTO `yoshop_region` VALUES ('3011', '3008', '丹凤', '丹凤县', '中国,陕西省,商洛市,丹凤县', '3', 'danfeng', '0914', '726200', 'D', '110.33486', '33.69468'); +INSERT INTO `yoshop_region` VALUES ('3012', '3008', '商南', '商南县', '中国,陕西省,商洛市,商南县', '3', 'shangnan', '0914', '726300', 'S', '110.88375', '33.52581'); +INSERT INTO `yoshop_region` VALUES ('3013', '3008', '山阳', '山阳县', '中国,陕西省,商洛市,山阳县', '3', 'shanyang', '0914', '726400', 'S', '109.88784', '33.52931'); +INSERT INTO `yoshop_region` VALUES ('3014', '3008', '镇安', '镇安县', '中国,陕西省,商洛市,镇安县', '3', 'zhen\'an', '0914', '711500', 'Z', '109.15374', '33.42366'); +INSERT INTO `yoshop_region` VALUES ('3015', '3008', '柞水', '柞水县', '中国,陕西省,商洛市,柞水县', '3', 'zhashui', '0914', '711400', 'Z', '109.11105', '33.6831'); +INSERT INTO `yoshop_region` VALUES ('3016', '2898', '西咸', '西咸新区', '中国,陕西省,西咸新区', '2', 'xixian', '029', '712000', 'X', '108.810654', '34.307144'); +INSERT INTO `yoshop_region` VALUES ('3017', '3016', '空港', '空港新城', '中国,陕西省,西咸新区,空港新城', '3', 'konggang', '0374', '461000', 'K', '108.760529', '34.440894'); +INSERT INTO `yoshop_region` VALUES ('3018', '3016', '沣东', '沣东新城', '中国,陕西省,西咸新区,沣东新城', '3', 'fengdong', '029', '710000', null, '108.82988', '34.267431'); +INSERT INTO `yoshop_region` VALUES ('3019', '3016', '秦汉', '秦汉新城', '中国,陕西省,西咸新区,秦汉新城', '3', 'qinhan', '029', '712000', 'Q', '108.83812', '34.386513'); +INSERT INTO `yoshop_region` VALUES ('3020', '3016', '沣西', '沣西新城', '中国,陕西省,西咸新区,沣西新城', '3', 'fengxi', '029', '710000', null, '108.71215', '34.190453'); +INSERT INTO `yoshop_region` VALUES ('3021', '3016', '泾河', '泾河新城', '中国,陕西省,西咸新区,泾河新城', '3', 'jinghe', '029', '713700', null, '109.049603', '34.460587'); +INSERT INTO `yoshop_region` VALUES ('3022', '0', '甘肃', '甘肃省', '中国,甘肃省', '1', 'gansu', '', '', 'G', '103.823557', '36.058039'); +INSERT INTO `yoshop_region` VALUES ('3023', '3022', '兰州', '兰州市', '中国,甘肃省,兰州市', '2', 'lanzhou', '0931', '730030', 'L', '103.823557', '36.058039'); +INSERT INTO `yoshop_region` VALUES ('3024', '3023', '城关', '城关区', '中国,甘肃省,兰州市,城关区', '3', 'chengguan', '0931', '730030', 'C', '103.8252', '36.05725'); +INSERT INTO `yoshop_region` VALUES ('3025', '3023', '七里河', '七里河区', '中国,甘肃省,兰州市,七里河区', '3', 'qilihe', '0931', '730050', 'Q', '103.78564', '36.06585'); +INSERT INTO `yoshop_region` VALUES ('3026', '3023', '西固', '西固区', '中国,甘肃省,兰州市,西固区', '3', 'xigu', '0931', '730060', 'X', '103.62811', '36.08858'); +INSERT INTO `yoshop_region` VALUES ('3027', '3023', '安宁', '安宁区', '中国,甘肃省,兰州市,安宁区', '3', 'anning', '0931', '730070', 'A', '103.7189', '36.10384'); +INSERT INTO `yoshop_region` VALUES ('3028', '3023', '红古', '红古区', '中国,甘肃省,兰州市,红古区', '3', 'honggu', '0931', '730084', 'H', '102.85955', '36.34537'); +INSERT INTO `yoshop_region` VALUES ('3029', '3023', '永登', '永登县', '中国,甘肃省,兰州市,永登县', '3', 'yongdeng', '0931', '730300', 'Y', '103.26055', '36.73522'); +INSERT INTO `yoshop_region` VALUES ('3030', '3023', '皋兰', '皋兰县', '中国,甘肃省,兰州市,皋兰县', '3', 'gaolan', '0931', '730200', 'G', '103.94506', '36.33215'); +INSERT INTO `yoshop_region` VALUES ('3031', '3023', '榆中', '榆中县', '中国,甘肃省,兰州市,榆中县', '3', 'yuzhong', '0931', '730100', 'Y', '104.1145', '35.84415'); +INSERT INTO `yoshop_region` VALUES ('3032', '3022', '嘉峪关', '嘉峪关市', '中国,甘肃省,嘉峪关市', '2', 'jiayuguan', '0937', '735100', 'J', '98.277304', '39.786529'); +INSERT INTO `yoshop_region` VALUES ('3033', '3032', '雄关', '雄关区', '中国,甘肃省,嘉峪关市,雄关区', '3', 'xiongguan', '0937', '735100', 'X', '98.277398', '39.77925'); +INSERT INTO `yoshop_region` VALUES ('3034', '3032', '长城', '长城区', '中国,甘肃省,嘉峪关市,长城区', '3', 'changcheng', '0937', '735106', 'C', '98.273523', '39.787431'); +INSERT INTO `yoshop_region` VALUES ('3035', '3032', '镜铁', '镜铁区', '中国,甘肃省,嘉峪关市,镜铁区', '3', 'jingtie', '0937', '735100', 'J', '98.277304', '39.786529'); +INSERT INTO `yoshop_region` VALUES ('3036', '3022', '金昌', '金昌市', '中国,甘肃省,金昌市', '2', 'jinchang', '0935', '737100', 'J', '102.187888', '38.514238'); +INSERT INTO `yoshop_region` VALUES ('3037', '3036', '金川', '金川区', '中国,甘肃省,金昌市,金川区', '3', 'jinchuan', '0935', '737100', 'J', '102.19376', '38.52101'); +INSERT INTO `yoshop_region` VALUES ('3038', '3036', '永昌', '永昌县', '中国,甘肃省,金昌市,永昌县', '3', 'yongchang', '0935', '737200', 'Y', '101.97222', '38.24711'); +INSERT INTO `yoshop_region` VALUES ('3039', '3022', '白银', '白银市', '中国,甘肃省,白银市', '2', 'baiyin', '0943', '730900', 'B', '104.173606', '36.54568'); +INSERT INTO `yoshop_region` VALUES ('3040', '3039', '白银', '白银区', '中国,甘肃省,白银市,白银区', '3', 'baiyin', '0943', '730900', 'B', '104.17355', '36.54411'); +INSERT INTO `yoshop_region` VALUES ('3041', '3039', '平川', '平川区', '中国,甘肃省,白银市,平川区', '3', 'pingchuan', '0943', '730913', 'P', '104.82498', '36.7277'); +INSERT INTO `yoshop_region` VALUES ('3042', '3039', '靖远', '靖远县', '中国,甘肃省,白银市,靖远县', '3', 'jingyuan', '0943', '730600', 'J', '104.68325', '36.56602'); +INSERT INTO `yoshop_region` VALUES ('3043', '3039', '会宁', '会宁县', '中国,甘肃省,白银市,会宁县', '3', 'huining', '0943', '730700', 'H', '105.05297', '35.69626'); +INSERT INTO `yoshop_region` VALUES ('3044', '3039', '景泰', '景泰县', '中国,甘肃省,白银市,景泰县', '3', 'jingtai', '0943', '730400', 'J', '104.06295', '37.18359'); +INSERT INTO `yoshop_region` VALUES ('3045', '3022', '天水', '天水市', '中国,甘肃省,天水市', '2', 'tianshui', '0938', '741000', 'T', '105.724998', '34.578529'); +INSERT INTO `yoshop_region` VALUES ('3046', '3045', '秦州', '秦州区', '中国,甘肃省,天水市,秦州区', '3', 'qinzhou', '0938', '741000', 'Q', '105.72421', '34.58089'); +INSERT INTO `yoshop_region` VALUES ('3047', '3045', '麦积', '麦积区', '中国,甘肃省,天水市,麦积区', '3', 'maiji', '0938', '741020', 'M', '105.89013', '34.57069'); +INSERT INTO `yoshop_region` VALUES ('3048', '3045', '清水', '清水县', '中国,甘肃省,天水市,清水县', '3', 'qingshui', '0938', '741400', 'Q', '106.13671', '34.75032'); +INSERT INTO `yoshop_region` VALUES ('3049', '3045', '秦安', '秦安县', '中国,甘肃省,天水市,秦安县', '3', 'qin\'an', '0938', '741600', 'Q', '105.66955', '34.85894'); +INSERT INTO `yoshop_region` VALUES ('3050', '3045', '甘谷', '甘谷县', '中国,甘肃省,天水市,甘谷县', '3', 'gangu', '0938', '741200', 'G', '105.33291', '34.73665'); +INSERT INTO `yoshop_region` VALUES ('3051', '3045', '武山', '武山县', '中国,甘肃省,天水市,武山县', '3', 'wushan', '0938', '741300', 'W', '104.88382', '34.72123'); +INSERT INTO `yoshop_region` VALUES ('3052', '3045', '张家川', '张家川回族自治县', '中国,甘肃省,天水市,张家川回族自治县', '3', 'zhangjiachuan', '0938', '741500', 'Z', '106.21582', '34.99582'); +INSERT INTO `yoshop_region` VALUES ('3053', '3022', '武威', '武威市', '中国,甘肃省,武威市', '2', 'wuwei', '0935', '733000', 'W', '102.634697', '37.929996'); +INSERT INTO `yoshop_region` VALUES ('3054', '3053', '凉州', '凉州区', '中国,甘肃省,武威市,凉州区', '3', 'liangzhou', '0935', '733000', 'L', '102.64203', '37.92832'); +INSERT INTO `yoshop_region` VALUES ('3055', '3053', '民勤', '民勤县', '中国,甘肃省,武威市,民勤县', '3', 'minqin', '0935', '733300', 'M', '103.09011', '38.62487'); +INSERT INTO `yoshop_region` VALUES ('3056', '3053', '古浪', '古浪县', '中国,甘肃省,武威市,古浪县', '3', 'gulang', '0935', '733100', 'G', '102.89154', '37.46508'); +INSERT INTO `yoshop_region` VALUES ('3057', '3053', '天祝', '天祝藏族自治县', '中国,甘肃省,武威市,天祝藏族自治县', '3', 'tianzhu', '0935', '733200', 'T', '103.1361', '36.97715'); +INSERT INTO `yoshop_region` VALUES ('3058', '3022', '张掖', '张掖市', '中国,甘肃省,张掖市', '2', 'zhangye', '0936', '734000', 'Z', '100.455472', '38.932897'); +INSERT INTO `yoshop_region` VALUES ('3059', '3058', '甘州', '甘州区', '中国,甘肃省,张掖市,甘州区', '3', 'ganzhou', '0936', '734000', 'G', '100.4527', '38.92947'); +INSERT INTO `yoshop_region` VALUES ('3060', '3058', '肃南', '肃南裕固族自治县', '中国,甘肃省,张掖市,肃南裕固族自治县', '3', 'sunan', '0936', '734400', 'S', '99.61407', '38.83776'); +INSERT INTO `yoshop_region` VALUES ('3061', '3058', '民乐', '民乐县', '中国,甘肃省,张掖市,民乐县', '3', 'minle', '0936', '734500', 'M', '100.81091', '38.43479'); +INSERT INTO `yoshop_region` VALUES ('3062', '3058', '临泽', '临泽县', '中国,甘肃省,张掖市,临泽县', '3', 'linze', '0936', '734200', 'L', '100.16445', '39.15252'); +INSERT INTO `yoshop_region` VALUES ('3063', '3058', '高台', '高台县', '中国,甘肃省,张掖市,高台县', '3', 'gaotai', '0936', '734300', 'G', '99.81918', '39.37829'); +INSERT INTO `yoshop_region` VALUES ('3064', '3058', '山丹', '山丹县', '中国,甘肃省,张掖市,山丹县', '3', 'shandan', '0936', '734100', 'S', '101.09359', '38.78468'); +INSERT INTO `yoshop_region` VALUES ('3065', '3022', '平凉', '平凉市', '中国,甘肃省,平凉市', '2', 'pingliang', '0933', '744000', 'P', '106.684691', '35.54279'); +INSERT INTO `yoshop_region` VALUES ('3066', '3065', '崆峒', '崆峒区', '中国,甘肃省,平凉市,崆峒区', '3', 'kongtong', '0933', '744000', null, '106.67483', '35.54262'); +INSERT INTO `yoshop_region` VALUES ('3067', '3065', '泾川', '泾川县', '中国,甘肃省,平凉市,泾川县', '3', 'jingchuan', '0933', '744300', null, '107.36581', '35.33223'); +INSERT INTO `yoshop_region` VALUES ('3068', '3065', '灵台', '灵台县', '中国,甘肃省,平凉市,灵台县', '3', 'lingtai', '0933', '744400', 'L', '107.6174', '35.06768'); +INSERT INTO `yoshop_region` VALUES ('3069', '3065', '崇信', '崇信县', '中国,甘肃省,平凉市,崇信县', '3', 'chongxin', '0933', '744200', 'C', '107.03738', '35.30344'); +INSERT INTO `yoshop_region` VALUES ('3070', '3065', '华亭', '华亭县', '中国,甘肃省,平凉市,华亭县', '3', 'huating', '0933', '744100', 'H', '106.65463', '35.2183'); +INSERT INTO `yoshop_region` VALUES ('3071', '3065', '庄浪', '庄浪县', '中国,甘肃省,平凉市,庄浪县', '3', 'zhuanglang', '0933', '744600', 'Z', '106.03662', '35.20235'); +INSERT INTO `yoshop_region` VALUES ('3072', '3065', '静宁', '静宁县', '中国,甘肃省,平凉市,静宁县', '3', 'jingning', '0933', '743400', 'J', '105.72723', '35.51991'); +INSERT INTO `yoshop_region` VALUES ('3073', '3022', '酒泉', '酒泉市', '中国,甘肃省,酒泉市', '2', 'jiuquan', '0937', '735000', 'J', '98.510795', '39.744023'); +INSERT INTO `yoshop_region` VALUES ('3074', '3073', '肃州', '肃州区', '中国,甘肃省,酒泉市,肃州区', '3', 'suzhou', '0937', '735000', 'S', '98.50775', '39.74506'); +INSERT INTO `yoshop_region` VALUES ('3075', '3073', '金塔', '金塔县', '中国,甘肃省,酒泉市,金塔县', '3', 'jinta', '0937', '735300', 'J', '98.90002', '39.97733'); +INSERT INTO `yoshop_region` VALUES ('3076', '3073', '瓜州', '瓜州县', '中国,甘肃省,酒泉市,瓜州县', '3', 'guazhou', '0937', '736100', 'G', '95.78271', '40.51548'); +INSERT INTO `yoshop_region` VALUES ('3077', '3073', '肃北', '肃北蒙古族自治县', '中国,甘肃省,酒泉市,肃北蒙古族自治县', '3', 'subei', '0937', '736300', 'S', '94.87649', '39.51214'); +INSERT INTO `yoshop_region` VALUES ('3078', '3073', '阿克塞', '阿克塞哈萨克族自治县', '中国,甘肃省,酒泉市,阿克塞哈萨克族自治县', '3', 'akesai', '0937', '736400', 'A', '94.34097', '39.63435'); +INSERT INTO `yoshop_region` VALUES ('3079', '3073', '玉门', '玉门市', '中国,甘肃省,酒泉市,玉门市', '3', 'yumen', '0937', '735200', 'Y', '97.04538', '40.29172'); +INSERT INTO `yoshop_region` VALUES ('3080', '3073', '敦煌', '敦煌市', '中国,甘肃省,酒泉市,敦煌市', '3', 'dunhuang', '0937', '736200', 'D', '94.66159', '40.14211'); +INSERT INTO `yoshop_region` VALUES ('3081', '3022', '庆阳', '庆阳市', '中国,甘肃省,庆阳市', '2', 'qingyang', '0934', '745000', 'Q', '107.638372', '35.734218'); +INSERT INTO `yoshop_region` VALUES ('3082', '3081', '西峰', '西峰区', '中国,甘肃省,庆阳市,西峰区', '3', 'xifeng', '0934', '745000', 'X', '107.65107', '35.73065'); +INSERT INTO `yoshop_region` VALUES ('3083', '3081', '庆城', '庆城县', '中国,甘肃省,庆阳市,庆城县', '3', 'qingcheng', '0934', '745100', 'Q', '107.88272', '36.01507'); +INSERT INTO `yoshop_region` VALUES ('3084', '3081', '环县', '环县', '中国,甘肃省,庆阳市,环县', '3', 'huanxian', '0934', '745700', 'H', '107.30835', '36.56846'); +INSERT INTO `yoshop_region` VALUES ('3085', '3081', '华池', '华池县', '中国,甘肃省,庆阳市,华池县', '3', 'huachi', '0934', '745600', 'H', '107.9891', '36.46108'); +INSERT INTO `yoshop_region` VALUES ('3086', '3081', '合水', '合水县', '中国,甘肃省,庆阳市,合水县', '3', 'heshui', '0934', '745400', 'H', '108.02032', '35.81911'); +INSERT INTO `yoshop_region` VALUES ('3087', '3081', '正宁', '正宁县', '中国,甘肃省,庆阳市,正宁县', '3', 'zhengning', '0934', '745300', 'Z', '108.36007', '35.49174'); +INSERT INTO `yoshop_region` VALUES ('3088', '3081', '宁县', '宁县', '中国,甘肃省,庆阳市,宁县', '3', 'ningxian', '0934', '745200', 'N', '107.92517', '35.50164'); +INSERT INTO `yoshop_region` VALUES ('3089', '3081', '镇原', '镇原县', '中国,甘肃省,庆阳市,镇原县', '3', 'zhenyuan', '0934', '744500', 'Z', '107.199', '35.67712'); +INSERT INTO `yoshop_region` VALUES ('3090', '3022', '定西', '定西市', '中国,甘肃省,定西市', '2', 'dingxi', '0932', '743000', 'D', '104.626294', '35.579578'); +INSERT INTO `yoshop_region` VALUES ('3091', '3090', '安定', '安定区', '中国,甘肃省,定西市,安定区', '3', 'anding', '0932', '743000', 'A', '104.6106', '35.58066'); +INSERT INTO `yoshop_region` VALUES ('3092', '3090', '通渭', '通渭县', '中国,甘肃省,定西市,通渭县', '3', 'tongwei', '0932', '743300', 'T', '105.24224', '35.21101'); +INSERT INTO `yoshop_region` VALUES ('3093', '3090', '陇西', '陇西县', '中国,甘肃省,定西市,陇西县', '3', 'longxi', '0932', '748100', 'L', '104.63446', '35.00238'); +INSERT INTO `yoshop_region` VALUES ('3094', '3090', '渭源', '渭源县', '中国,甘肃省,定西市,渭源县', '3', 'weiyuan', '0932', '748200', 'W', '104.21435', '35.13649'); +INSERT INTO `yoshop_region` VALUES ('3095', '3090', '临洮', '临洮县', '中国,甘肃省,定西市,临洮县', '3', 'lintao', '0932', '730500', 'L', '103.86196', '35.3751'); +INSERT INTO `yoshop_region` VALUES ('3096', '3090', '漳县', '漳县', '中国,甘肃省,定西市,漳县', '3', 'zhangxian', '0932', '748300', 'Z', '104.46704', '34.84977'); +INSERT INTO `yoshop_region` VALUES ('3097', '3090', '岷县', '岷县', '中国,甘肃省,定西市,岷县', '3', 'minxian', '0932', '748400', null, '104.03772', '34.43444'); +INSERT INTO `yoshop_region` VALUES ('3098', '3022', '陇南', '陇南市', '中国,甘肃省,陇南市', '2', 'longnan', '0939', '746000', 'L', '104.929379', '33.388598'); +INSERT INTO `yoshop_region` VALUES ('3099', '3098', '武都', '武都区', '中国,甘肃省,陇南市,武都区', '3', 'wudu', '0939', '746000', 'W', '104.92652', '33.39239'); +INSERT INTO `yoshop_region` VALUES ('3100', '3098', '成县', '成县', '中国,甘肃省,陇南市,成县', '3', 'chengxian', '0939', '742500', 'C', '105.72586', '33.73925'); +INSERT INTO `yoshop_region` VALUES ('3101', '3098', '文县', '文县', '中国,甘肃省,陇南市,文县', '3', 'wenxian', '0939', '746400', 'W', '104.68362', '32.94337'); +INSERT INTO `yoshop_region` VALUES ('3102', '3098', '宕昌', '宕昌县', '中国,甘肃省,陇南市,宕昌县', '3', 'dangchang', '0939', '748500', null, '104.39349', '34.04732'); +INSERT INTO `yoshop_region` VALUES ('3103', '3098', '康县', '康县', '中国,甘肃省,陇南市,康县', '3', 'kangxian', '0939', '746500', 'K', '105.60711', '33.32912'); +INSERT INTO `yoshop_region` VALUES ('3104', '3098', '西和', '西和县', '中国,甘肃省,陇南市,西和县', '3', 'xihe', '0939', '742100', 'X', '105.30099', '34.01432'); +INSERT INTO `yoshop_region` VALUES ('3105', '3098', '礼县', '礼县', '中国,甘肃省,陇南市,礼县', '3', 'lixian', '0939', '742200', 'L', '105.17785', '34.18935'); +INSERT INTO `yoshop_region` VALUES ('3106', '3098', '徽县', '徽县', '中国,甘肃省,陇南市,徽县', '3', 'huixian', '0939', '742300', 'H', '106.08529', '33.76898'); +INSERT INTO `yoshop_region` VALUES ('3107', '3098', '两当', '两当县', '中国,甘肃省,陇南市,两当县', '3', 'liangdang', '0939', '742400', 'L', '106.30484', '33.9096'); +INSERT INTO `yoshop_region` VALUES ('3108', '3022', '临夏', '临夏回族自治州', '中国,甘肃省,临夏回族自治州', '2', 'linxia', '0930', '731100', 'L', '103.212006', '35.599446'); +INSERT INTO `yoshop_region` VALUES ('3109', '3108', '临夏', '临夏市', '中国,甘肃省,临夏回族自治州,临夏市', '3', 'linxia', '0930', '731100', 'L', '103.21', '35.59916'); +INSERT INTO `yoshop_region` VALUES ('3110', '3108', '临夏', '临夏县', '中国,甘肃省,临夏回族自治州,临夏县', '3', 'linxia', '0930', '731800', 'L', '102.9938', '35.49519'); +INSERT INTO `yoshop_region` VALUES ('3111', '3108', '康乐', '康乐县', '中国,甘肃省,临夏回族自治州,康乐县', '3', 'kangle', '0930', '731500', 'K', '103.71093', '35.37219'); +INSERT INTO `yoshop_region` VALUES ('3112', '3108', '永靖', '永靖县', '中国,甘肃省,临夏回族自治州,永靖县', '3', 'yongjing', '0930', '731600', 'Y', '103.32043', '35.93835'); +INSERT INTO `yoshop_region` VALUES ('3113', '3108', '广河', '广河县', '中国,甘肃省,临夏回族自治州,广河县', '3', 'guanghe', '0930', '731300', 'G', '103.56933', '35.48097'); +INSERT INTO `yoshop_region` VALUES ('3114', '3108', '和政', '和政县', '中国,甘肃省,临夏回族自治州,和政县', '3', 'hezheng', '0930', '731200', 'H', '103.34936', '35.42592'); +INSERT INTO `yoshop_region` VALUES ('3115', '3108', '东乡族', '东乡族自治县', '中国,甘肃省,临夏回族自治州,东乡族自治县', '3', 'dongxiangzu', '0930', '731400', 'D', '103.39477', '35.66471'); +INSERT INTO `yoshop_region` VALUES ('3116', '3108', '积石山', '积石山保安族东乡族撒拉族自治县', '中国,甘肃省,临夏回族自治州,积石山保安族东乡族撒拉族自治县', '3', 'jishishan', '0930', '731700', 'J', '102.87374', '35.7182'); +INSERT INTO `yoshop_region` VALUES ('3117', '3022', '甘南', '甘南藏族自治州', '中国,甘肃省,甘南藏族自治州', '2', 'gannan', '0941', '747000', 'G', '102.911008', '34.986354'); +INSERT INTO `yoshop_region` VALUES ('3118', '3117', '合作', '合作市', '中国,甘肃省,甘南藏族自治州,合作市', '3', 'hezuo', '0941', '747000', 'H', '102.91082', '35.00016'); +INSERT INTO `yoshop_region` VALUES ('3119', '3117', '临潭', '临潭县', '中国,甘肃省,甘南藏族自治州,临潭县', '3', 'lintan', '0941', '747500', 'L', '103.35287', '34.69515'); +INSERT INTO `yoshop_region` VALUES ('3120', '3117', '卓尼', '卓尼县', '中国,甘肃省,甘南藏族自治州,卓尼县', '3', 'zhuoni', '0941', '747600', 'Z', '103.50811', '34.58919'); +INSERT INTO `yoshop_region` VALUES ('3121', '3117', '舟曲', '舟曲县', '中国,甘肃省,甘南藏族自治州,舟曲县', '3', 'zhouqu', '0941', '746300', 'Z', '104.37155', '33.78468'); +INSERT INTO `yoshop_region` VALUES ('3122', '3117', '迭部', '迭部县', '中国,甘肃省,甘南藏族自治州,迭部县', '3', 'diebu', '0941', '747400', 'D', '103.22274', '34.05623'); +INSERT INTO `yoshop_region` VALUES ('3123', '3117', '玛曲', '玛曲县', '中国,甘肃省,甘南藏族自治州,玛曲县', '3', 'maqu', '0941', '747300', 'M', '102.0754', '33.997'); +INSERT INTO `yoshop_region` VALUES ('3124', '3117', '碌曲', '碌曲县', '中国,甘肃省,甘南藏族自治州,碌曲县', '3', 'luqu', '0941', '747200', 'L', '102.49176', '34.58872'); +INSERT INTO `yoshop_region` VALUES ('3125', '3117', '夏河', '夏河县', '中国,甘肃省,甘南藏族自治州,夏河县', '3', 'xiahe', '0941', '747100', 'X', '102.52215', '35.20487'); +INSERT INTO `yoshop_region` VALUES ('3126', '0', '青海', '青海省', '中国,青海省', '1', 'qinghai', '', '', 'Q', '101.778916', '36.623178'); +INSERT INTO `yoshop_region` VALUES ('3127', '3126', '西宁', '西宁市', '中国,青海省,西宁市', '2', 'xining', '0971', '810000', 'X', '101.778916', '36.623178'); +INSERT INTO `yoshop_region` VALUES ('3128', '3127', '城东', '城东区', '中国,青海省,西宁市,城东区', '3', 'chengdong', '0971', '810007', 'C', '101.80373', '36.59969'); +INSERT INTO `yoshop_region` VALUES ('3129', '3127', '城中', '城中区', '中国,青海省,西宁市,城中区', '3', 'chengzhong', '0971', '810000', 'C', '101.78394', '36.62279'); +INSERT INTO `yoshop_region` VALUES ('3130', '3127', '城西', '城西区', '中国,青海省,西宁市,城西区', '3', 'chengxi', '0971', '810001', 'C', '101.76588', '36.62828'); +INSERT INTO `yoshop_region` VALUES ('3131', '3127', '城北', '城北区', '中国,青海省,西宁市,城北区', '3', 'chengbei', '0971', '810003', 'C', '101.7662', '36.65014'); +INSERT INTO `yoshop_region` VALUES ('3132', '3127', '大通', '大通回族土族自治县', '中国,青海省,西宁市,大通回族土族自治县', '3', 'datong', '0971', '810100', 'D', '101.70236', '36.93489'); +INSERT INTO `yoshop_region` VALUES ('3133', '3127', '湟中', '湟中县', '中国,青海省,西宁市,湟中县', '3', 'huangzhong', '0971', '811600', null, '101.57159', '36.50083'); +INSERT INTO `yoshop_region` VALUES ('3134', '3127', '湟源', '湟源县', '中国,青海省,西宁市,湟源县', '3', 'huangyuan', '0971', '812100', null, '101.25643', '36.68243'); +INSERT INTO `yoshop_region` VALUES ('3135', '3126', '海东', '海东市', '中国,青海省,海东市', '2', 'haidong', '0972', '810700', 'H', '102.10327', '36.502916'); +INSERT INTO `yoshop_region` VALUES ('3136', '3135', '乐都', '乐都区', '中国,青海省,海东市,乐都区', '3', 'ledu', '0972', '810700', 'L', '102.402431', '36.480291'); +INSERT INTO `yoshop_region` VALUES ('3137', '3135', '平安', '平安县', '中国,青海省,海东市,平安县', '3', 'ping\'an', '0972', '810600', 'P', '102.104295', '36.502714'); +INSERT INTO `yoshop_region` VALUES ('3138', '3135', '民和', '民和回族土族自治县', '中国,青海省,海东市,民和回族土族自治县', '3', 'minhe', '0972', '810800', 'M', '102.804209', '36.329451'); +INSERT INTO `yoshop_region` VALUES ('3139', '3135', '互助', '互助土族自治县', '中国,青海省,海东市,互助土族自治县', '3', 'huzhu', '0972', '810500', 'H', '101.956734', '36.83994'); +INSERT INTO `yoshop_region` VALUES ('3140', '3135', '化隆', '化隆回族自治县', '中国,青海省,海东市,化隆回族自治县', '3', 'hualong', '0972', '810900', 'H', '102.262329', '36.098322'); +INSERT INTO `yoshop_region` VALUES ('3141', '3135', '循化', '循化撒拉族自治县', '中国,青海省,海东市,循化撒拉族自治县', '3', 'xunhua', '0972', '811100', 'X', '102.486534', '35.847247'); +INSERT INTO `yoshop_region` VALUES ('3142', '3126', '海北', '海北藏族自治州', '中国,青海省,海北藏族自治州', '2', 'haibei', '0970', '812200', 'H', '100.901059', '36.959435'); +INSERT INTO `yoshop_region` VALUES ('3143', '3142', '门源', '门源回族自治县', '中国,青海省,海北藏族自治州,门源回族自治县', '3', 'menyuan', '0970', '810300', 'M', '101.62228', '37.37611'); +INSERT INTO `yoshop_region` VALUES ('3144', '3142', '祁连', '祁连县', '中国,青海省,海北藏族自治州,祁连县', '3', 'qilian', '0970', '810400', 'Q', '100.24618', '38.17901'); +INSERT INTO `yoshop_region` VALUES ('3145', '3142', '海晏', '海晏县', '中国,青海省,海北藏族自治州,海晏县', '3', 'haiyan', '0970', '812200', 'H', '100.9927', '36.89902'); +INSERT INTO `yoshop_region` VALUES ('3146', '3142', '刚察', '刚察县', '中国,青海省,海北藏族自治州,刚察县', '3', 'gangcha', '0970', '812300', 'G', '100.14675', '37.32161'); +INSERT INTO `yoshop_region` VALUES ('3147', '3126', '黄南', '黄南藏族自治州', '中国,青海省,黄南藏族自治州', '2', 'huangnan', '0973', '811300', 'H', '102.019988', '35.517744'); +INSERT INTO `yoshop_region` VALUES ('3148', '3147', '同仁', '同仁县', '中国,青海省,黄南藏族自治州,同仁县', '3', 'tongren', '0973', '811300', 'T', '102.0184', '35.51603'); +INSERT INTO `yoshop_region` VALUES ('3149', '3147', '尖扎', '尖扎县', '中国,青海省,黄南藏族自治州,尖扎县', '3', 'jianzha', '0973', '811200', 'J', '102.03411', '35.93947'); +INSERT INTO `yoshop_region` VALUES ('3150', '3147', '泽库', '泽库县', '中国,青海省,黄南藏族自治州,泽库县', '3', 'zeku', '0973', '811400', 'Z', '101.46444', '35.03519'); +INSERT INTO `yoshop_region` VALUES ('3151', '3147', '河南', '河南蒙古族自治县', '中国,青海省,黄南藏族自治州,河南蒙古族自治县', '3', 'henan', '0973', '811500', 'H', '101.60864', '34.73476'); +INSERT INTO `yoshop_region` VALUES ('3152', '3126', '海南', '海南藏族自治州', '中国,青海省,海南藏族自治州', '2', 'hainan', '0974', '813000', 'H', '100.619542', '36.280353'); +INSERT INTO `yoshop_region` VALUES ('3153', '3152', '共和', '共和县', '中国,青海省,海南藏族自治州,共和县', '3', 'gonghe', '0974', '813000', 'G', '100.62003', '36.2841'); +INSERT INTO `yoshop_region` VALUES ('3154', '3152', '同德', '同德县', '中国,青海省,海南藏族自治州,同德县', '3', 'tongde', '0974', '813200', 'T', '100.57159', '35.25488'); +INSERT INTO `yoshop_region` VALUES ('3155', '3152', '贵德', '贵德县', '中国,青海省,海南藏族自治州,贵德县', '3', 'guide', '0974', '811700', 'G', '101.432', '36.044'); +INSERT INTO `yoshop_region` VALUES ('3156', '3152', '兴海', '兴海县', '中国,青海省,海南藏族自治州,兴海县', '3', 'xinghai', '0974', '813300', 'X', '99.98846', '35.59031'); +INSERT INTO `yoshop_region` VALUES ('3157', '3152', '贵南', '贵南县', '中国,青海省,海南藏族自治州,贵南县', '3', 'guinan', '0974', '813100', 'G', '100.74716', '35.58667'); +INSERT INTO `yoshop_region` VALUES ('3158', '3126', '果洛', '果洛藏族自治州', '中国,青海省,果洛藏族自治州', '2', 'golog', '0975', '814000', 'G', '100.242143', '34.4736'); +INSERT INTO `yoshop_region` VALUES ('3159', '3158', '玛沁', '玛沁县', '中国,青海省,果洛藏族自治州,玛沁县', '3', 'maqin', '0975', '814000', 'M', '100.23901', '34.47746'); +INSERT INTO `yoshop_region` VALUES ('3160', '3158', '班玛', '班玛县', '中国,青海省,果洛藏族自治州,班玛县', '3', 'banma', '0975', '814300', 'B', '100.73745', '32.93253'); +INSERT INTO `yoshop_region` VALUES ('3161', '3158', '甘德', '甘德县', '中国,青海省,果洛藏族自治州,甘德县', '3', 'gande', '0975', '814100', 'G', '99.90246', '33.96838'); +INSERT INTO `yoshop_region` VALUES ('3162', '3158', '达日', '达日县', '中国,青海省,果洛藏族自治州,达日县', '3', 'dari', '0975', '814200', 'D', '99.65179', '33.75193'); +INSERT INTO `yoshop_region` VALUES ('3163', '3158', '久治', '久治县', '中国,青海省,果洛藏族自治州,久治县', '3', 'jiuzhi', '0975', '624700', 'J', '101.48342', '33.42989'); +INSERT INTO `yoshop_region` VALUES ('3164', '3158', '玛多', '玛多县', '中国,青海省,果洛藏族自治州,玛多县', '3', 'maduo', '0975', '813500', 'M', '98.20996', '34.91567'); +INSERT INTO `yoshop_region` VALUES ('3165', '3126', '玉树', '玉树藏族自治州', '中国,青海省,玉树藏族自治州', '2', 'yushu', '0976', '815000', 'Y', '97.008522', '33.004049'); +INSERT INTO `yoshop_region` VALUES ('3166', '3165', '玉树', '玉树市', '中国,青海省,玉树藏族自治州,玉树市', '3', 'yushu', '0976', '815000', 'Y', '97.008762', '33.00393'); +INSERT INTO `yoshop_region` VALUES ('3167', '3165', '杂多', '杂多县', '中国,青海省,玉树藏族自治州,杂多县', '3', 'zaduo', '0976', '815300', 'Z', '95.29864', '32.89318'); +INSERT INTO `yoshop_region` VALUES ('3168', '3165', '称多', '称多县', '中国,青海省,玉树藏族自治州,称多县', '3', 'chenduo', '0976', '815100', 'C', '97.10788', '33.36899'); +INSERT INTO `yoshop_region` VALUES ('3169', '3165', '治多', '治多县', '中国,青海省,玉树藏族自治州,治多县', '3', 'zhiduo', '0976', '815400', 'Z', '95.61572', '33.8528'); +INSERT INTO `yoshop_region` VALUES ('3170', '3165', '囊谦', '囊谦县', '中国,青海省,玉树藏族自治州,囊谦县', '3', 'nangqian', '0976', '815200', 'N', '96.47753', '32.20359'); +INSERT INTO `yoshop_region` VALUES ('3171', '3165', '曲麻莱', '曲麻莱县', '中国,青海省,玉树藏族自治州,曲麻莱县', '3', 'qumalai', '0976', '815500', 'Q', '95.79757', '34.12609'); +INSERT INTO `yoshop_region` VALUES ('3172', '3126', '海西', '海西蒙古族藏族自治州', '中国,青海省,海西蒙古族藏族自治州', '2', 'haixi', '0977', '817000', 'H', '97.370785', '37.374663'); +INSERT INTO `yoshop_region` VALUES ('3173', '3172', '格尔木', '格尔木市', '中国,青海省,海西蒙古族藏族自治州,格尔木市', '3', 'geermu', '0977', '816000', 'G', '94.90329', '36.40236'); +INSERT INTO `yoshop_region` VALUES ('3174', '3172', '德令哈', '德令哈市', '中国,青海省,海西蒙古族藏族自治州,德令哈市', '3', 'delingha', '0977', '817000', 'D', '97.36084', '37.36946'); +INSERT INTO `yoshop_region` VALUES ('3175', '3172', '乌兰', '乌兰县', '中国,青海省,海西蒙古族藏族自治州,乌兰县', '3', 'wulan', '0977', '817100', 'W', '98.48196', '36.93471'); +INSERT INTO `yoshop_region` VALUES ('3176', '3172', '都兰', '都兰县', '中国,青海省,海西蒙古族藏族自治州,都兰县', '3', 'dulan', '0977', '816100', 'D', '98.09228', '36.30135'); +INSERT INTO `yoshop_region` VALUES ('3177', '3172', '天峻', '天峻县', '中国,青海省,海西蒙古族藏族自治州,天峻县', '3', 'tianjun', '0977', '817200', 'T', '99.02453', '37.30326'); +INSERT INTO `yoshop_region` VALUES ('3178', '0', '宁夏', '宁夏回族自治区', '中国,宁夏回族自治区', '1', 'ningxia', '', '', 'N', '106.278179', '38.46637'); +INSERT INTO `yoshop_region` VALUES ('3179', '3178', '银川', '银川市', '中国,宁夏回族自治区,银川市', '2', 'yinchuan', '0951', '750004', 'Y', '106.278179', '38.46637'); +INSERT INTO `yoshop_region` VALUES ('3180', '3179', '兴庆', '兴庆区', '中国,宁夏回族自治区,银川市,兴庆区', '3', 'xingqing', '0951', '750001', 'X', '106.28872', '38.47392'); +INSERT INTO `yoshop_region` VALUES ('3181', '3179', '西夏', '西夏区', '中国,宁夏回族自治区,银川市,西夏区', '3', 'xixia', '0951', '750021', 'X', '106.15023', '38.49137'); +INSERT INTO `yoshop_region` VALUES ('3182', '3179', '金凤', '金凤区', '中国,宁夏回族自治区,银川市,金凤区', '3', 'jinfeng', '0951', '750011', 'J', '106.24261', '38.47294'); +INSERT INTO `yoshop_region` VALUES ('3183', '3179', '永宁', '永宁县', '中国,宁夏回族自治区,银川市,永宁县', '3', 'yongning', '0951', '750100', 'Y', '106.2517', '38.27559'); +INSERT INTO `yoshop_region` VALUES ('3184', '3179', '贺兰', '贺兰县', '中国,宁夏回族自治区,银川市,贺兰县', '3', 'helan', '0951', '750200', 'H', '106.34982', '38.5544'); +INSERT INTO `yoshop_region` VALUES ('3185', '3179', '灵武', '灵武市', '中国,宁夏回族自治区,银川市,灵武市', '3', 'lingwu', '0951', '750004', 'L', '106.33999', '38.10266'); +INSERT INTO `yoshop_region` VALUES ('3186', '3178', '石嘴山', '石嘴山市', '中国,宁夏回族自治区,石嘴山市', '2', 'shizuishan', '0952', '753000', 'S', '106.376173', '39.01333'); +INSERT INTO `yoshop_region` VALUES ('3187', '3186', '大武口', '大武口区', '中国,宁夏回族自治区,石嘴山市,大武口区', '3', 'dawukou', '0952', '753000', 'D', '106.37717', '39.01226'); +INSERT INTO `yoshop_region` VALUES ('3188', '3186', '惠农', '惠农区', '中国,宁夏回族自治区,石嘴山市,惠农区', '3', 'huinong', '0952', '753600', 'H', '106.71145', '39.13193'); +INSERT INTO `yoshop_region` VALUES ('3189', '3186', '平罗', '平罗县', '中国,宁夏回族自治区,石嘴山市,平罗县', '3', 'pingluo', '0952', '753400', 'P', '106.54538', '38.90429'); +INSERT INTO `yoshop_region` VALUES ('3190', '3178', '吴忠', '吴忠市', '中国,宁夏回族自治区,吴忠市', '2', 'wuzhong', '0953', '751100', 'W', '106.199409', '37.986165'); +INSERT INTO `yoshop_region` VALUES ('3191', '3190', '利通', '利通区', '中国,宁夏回族自治区,吴忠市,利通区', '3', 'litong', '0953', '751100', 'L', '106.20311', '37.98512'); +INSERT INTO `yoshop_region` VALUES ('3192', '3190', '红寺堡', '红寺堡区', '中国,宁夏回族自治区,吴忠市,红寺堡区', '3', 'hongsibao', '0953', '751900', 'H', '106.19822', '37.99747'); +INSERT INTO `yoshop_region` VALUES ('3193', '3190', '盐池', '盐池县', '中国,宁夏回族自治区,吴忠市,盐池县', '3', 'yanchi', '0953', '751500', 'Y', '107.40707', '37.7833'); +INSERT INTO `yoshop_region` VALUES ('3194', '3190', '同心', '同心县', '中国,宁夏回族自治区,吴忠市,同心县', '3', 'tongxin', '0953', '751300', 'T', '105.91418', '36.98116'); +INSERT INTO `yoshop_region` VALUES ('3195', '3190', '青铜峡', '青铜峡市', '中国,宁夏回族自治区,吴忠市,青铜峡市', '3', 'qingtongxia', '0953', '751600', 'Q', '106.07489', '38.02004'); +INSERT INTO `yoshop_region` VALUES ('3196', '3178', '固原', '固原市', '中国,宁夏回族自治区,固原市', '2', 'guyuan', '0954', '756000', 'G', '106.285241', '36.004561'); +INSERT INTO `yoshop_region` VALUES ('3197', '3196', '原州', '原州区', '中国,宁夏回族自治区,固原市,原州区', '3', 'yuanzhou', '0954', '756000', 'Y', '106.28778', '36.00374'); +INSERT INTO `yoshop_region` VALUES ('3198', '3196', '西吉', '西吉县', '中国,宁夏回族自治区,固原市,西吉县', '3', 'xiji', '0954', '756200', 'X', '105.73107', '35.96616'); +INSERT INTO `yoshop_region` VALUES ('3199', '3196', '隆德', '隆德县', '中国,宁夏回族自治区,固原市,隆德县', '3', 'longde', '0954', '756300', 'L', '106.12426', '35.61718'); +INSERT INTO `yoshop_region` VALUES ('3200', '3196', '泾源', '泾源县', '中国,宁夏回族自治区,固原市,泾源县', '3', 'jingyuan', '0954', '756400', null, '106.33902', '35.49072'); +INSERT INTO `yoshop_region` VALUES ('3201', '3196', '彭阳', '彭阳县', '中国,宁夏回族自治区,固原市,彭阳县', '3', 'pengyang', '0954', '756500', 'P', '106.64462', '35.85076'); +INSERT INTO `yoshop_region` VALUES ('3202', '3178', '中卫', '中卫市', '中国,宁夏回族自治区,中卫市', '2', 'zhongwei', '0955', '751700', 'Z', '105.189568', '37.514951'); +INSERT INTO `yoshop_region` VALUES ('3203', '3202', '沙坡头', '沙坡头区', '中国,宁夏回族自治区,中卫市,沙坡头区', '3', 'shapotou', '0955', '755000', 'S', '105.18962', '37.51044'); +INSERT INTO `yoshop_region` VALUES ('3204', '3202', '中宁', '中宁县', '中国,宁夏回族自治区,中卫市,中宁县', '3', 'zhongning', '0955', '751200', 'Z', '105.68515', '37.49149'); +INSERT INTO `yoshop_region` VALUES ('3205', '3202', '海原', '海原县', '中国,宁夏回族自治区,中卫市,海原县', '3', 'haiyuan', '0955', '751800', 'H', '105.64712', '36.56498'); +INSERT INTO `yoshop_region` VALUES ('3206', '0', '新疆', '新疆维吾尔自治区', '中国,新疆维吾尔自治区', '1', 'xinjiang', '', '', 'X', '87.617733', '43.792818'); +INSERT INTO `yoshop_region` VALUES ('3207', '3206', '乌鲁木齐', '乌鲁木齐市', '中国,新疆维吾尔自治区,乌鲁木齐市', '2', 'urumqi', '0991', '830002', 'W', '87.617733', '43.792818'); +INSERT INTO `yoshop_region` VALUES ('3208', '3207', '天山', '天山区', '中国,新疆维吾尔自治区,乌鲁木齐市,天山区', '3', 'tianshan', '0991', '830002', 'T', '87.63167', '43.79439'); +INSERT INTO `yoshop_region` VALUES ('3209', '3207', '沙依巴克', '沙依巴克区', '中国,新疆维吾尔自治区,乌鲁木齐市,沙依巴克区', '3', 'shayibake', '0991', '830000', 'S', '87.59788', '43.80118'); +INSERT INTO `yoshop_region` VALUES ('3210', '3207', '新市', '新市区', '中国,新疆维吾尔自治区,乌鲁木齐市,新市区', '3', 'xinshi', '0991', '830011', 'X', '87.57406', '43.84348'); +INSERT INTO `yoshop_region` VALUES ('3211', '3207', '水磨沟', '水磨沟区', '中国,新疆维吾尔自治区,乌鲁木齐市,水磨沟区', '3', 'shuimogou', '0991', '830017', 'S', '87.64249', '43.83247'); +INSERT INTO `yoshop_region` VALUES ('3212', '3207', '头屯河', '头屯河区', '中国,新疆维吾尔自治区,乌鲁木齐市,头屯河区', '3', 'toutunhe', '0991', '830022', 'T', '87.29138', '43.85487'); +INSERT INTO `yoshop_region` VALUES ('3213', '3207', '达坂城', '达坂城区', '中国,新疆维吾尔自治区,乌鲁木齐市,达坂城区', '3', 'dabancheng', '0991', '830039', 'D', '88.30697', '43.35797'); +INSERT INTO `yoshop_region` VALUES ('3214', '3207', '米东', '米东区', '中国,新疆维吾尔自治区,乌鲁木齐市,米东区', '3', 'midong', '0991', '830019', 'M', '87.68583', '43.94739'); +INSERT INTO `yoshop_region` VALUES ('3215', '3207', '乌鲁木齐', '乌鲁木齐县', '中国,新疆维吾尔自治区,乌鲁木齐市,乌鲁木齐县', '3', 'wulumuqi', '0991', '830063', 'W', '87.40939', '43.47125'); +INSERT INTO `yoshop_region` VALUES ('3216', '3206', '克拉玛依', '克拉玛依市', '中国,新疆维吾尔自治区,克拉玛依市', '2', 'karamay', '0990', '834000', 'K', '84.873946', '45.595886'); +INSERT INTO `yoshop_region` VALUES ('3217', '3216', '独山子', '独山子区', '中国,新疆维吾尔自治区,克拉玛依市,独山子区', '3', 'dushanzi', '0992', '834021', 'D', '84.88671', '44.32867'); +INSERT INTO `yoshop_region` VALUES ('3218', '3216', '克拉玛依', '克拉玛依区', '中国,新疆维吾尔自治区,克拉玛依市,克拉玛依区', '3', 'kelamayi', '0990', '834000', 'K', '84.86225', '45.59089'); +INSERT INTO `yoshop_region` VALUES ('3219', '3216', '白碱滩', '白碱滩区', '中国,新疆维吾尔自治区,克拉玛依市,白碱滩区', '3', 'baijiantan', '0990', '834008', 'B', '85.13244', '45.68768'); +INSERT INTO `yoshop_region` VALUES ('3220', '3216', '乌尔禾', '乌尔禾区', '中国,新疆维吾尔自治区,克拉玛依市,乌尔禾区', '3', 'wuerhe', '0990', '834012', 'W', '85.69143', '46.09006'); +INSERT INTO `yoshop_region` VALUES ('3221', '3206', '吐鲁番', '吐鲁番地区', '中国,新疆维吾尔自治区,吐鲁番地区', '2', 'turpan', '0995', '838000', 'T', '89.184078', '42.947613'); +INSERT INTO `yoshop_region` VALUES ('3222', '3221', '吐鲁番', '吐鲁番市', '中国,新疆维吾尔自治区,吐鲁番地区,吐鲁番市', '3', 'tulufan', '0995', '838000', 'T', '89.18579', '42.93505'); +INSERT INTO `yoshop_region` VALUES ('3223', '3221', '鄯善', '鄯善县', '中国,新疆维吾尔自治区,吐鲁番地区,鄯善县', '3', 'shanshan', '0995', '838200', null, '90.21402', '42.8635'); +INSERT INTO `yoshop_region` VALUES ('3224', '3221', '托克逊', '托克逊县', '中国,新疆维吾尔自治区,吐鲁番地区,托克逊县', '3', 'tuokexun', '0995', '838100', 'T', '88.65823', '42.79231'); +INSERT INTO `yoshop_region` VALUES ('3225', '3206', '哈密', '哈密地区', '中国,新疆维吾尔自治区,哈密地区', '2', 'hami', '0902', '839000', 'H', '93.51316', '42.833248'); +INSERT INTO `yoshop_region` VALUES ('3226', '3225', '哈密', '哈密市', '中国,新疆维吾尔自治区,哈密地区,哈密市', '3', 'hami', '0902', '839000', 'H', '93.51452', '42.82699'); +INSERT INTO `yoshop_region` VALUES ('3227', '3225', '巴里坤', '巴里坤哈萨克自治县', '中国,新疆维吾尔自治区,哈密地区,巴里坤哈萨克自治县', '3', 'balikun', '0902', '839200', 'B', '93.01236', '43.59993'); +INSERT INTO `yoshop_region` VALUES ('3228', '3225', '伊吾', '伊吾县', '中国,新疆维吾尔自治区,哈密地区,伊吾县', '3', 'yiwu', '0902', '839300', 'Y', '94.69403', '43.2537'); +INSERT INTO `yoshop_region` VALUES ('3229', '3206', '昌吉', '昌吉回族自治州', '中国,新疆维吾尔自治区,昌吉回族自治州', '2', 'changji', '0994', '831100', 'C', '87.304012', '44.014577'); +INSERT INTO `yoshop_region` VALUES ('3230', '3229', '昌吉', '昌吉市', '中国,新疆维吾尔自治区,昌吉回族自治州,昌吉市', '3', 'changji', '0994', '831100', 'C', '87.30249', '44.01267'); +INSERT INTO `yoshop_region` VALUES ('3231', '3229', '阜康', '阜康市', '中国,新疆维吾尔自治区,昌吉回族自治州,阜康市', '3', 'fukang', '0994', '831500', 'F', '87.98529', '44.1584'); +INSERT INTO `yoshop_region` VALUES ('3232', '3229', '呼图壁', '呼图壁县', '中国,新疆维吾尔自治区,昌吉回族自治州,呼图壁县', '3', 'hutubi', '0994', '831200', 'H', '86.89892', '44.18977'); +INSERT INTO `yoshop_region` VALUES ('3233', '3229', '玛纳斯', '玛纳斯县', '中国,新疆维吾尔自治区,昌吉回族自治州,玛纳斯县', '3', 'manasi', '0994', '832200', 'M', '86.2145', '44.30438'); +INSERT INTO `yoshop_region` VALUES ('3234', '3229', '奇台', '奇台县', '中国,新疆维吾尔自治区,昌吉回族自治州,奇台县', '3', 'qitai', '0994', '831800', 'Q', '89.5932', '44.02221'); +INSERT INTO `yoshop_region` VALUES ('3235', '3229', '吉木萨尔', '吉木萨尔县', '中国,新疆维吾尔自治区,昌吉回族自治州,吉木萨尔县', '3', 'jimusaer', '0994', '831700', 'J', '89.18078', '44.00048'); +INSERT INTO `yoshop_region` VALUES ('3236', '3229', '木垒', '木垒哈萨克自治县', '中国,新疆维吾尔自治区,昌吉回族自治州,木垒哈萨克自治县', '3', 'mulei', '0994', '831900', 'M', '90.28897', '43.83508'); +INSERT INTO `yoshop_region` VALUES ('3237', '3206', '博尔塔拉', '博尔塔拉蒙古自治州', '中国,新疆维吾尔自治区,博尔塔拉蒙古自治州', '2', 'bortala', '0909', '833400', 'B', '82.074778', '44.903258'); +INSERT INTO `yoshop_region` VALUES ('3238', '3237', '博乐', '博乐市', '中国,新疆维吾尔自治区,博尔塔拉蒙古自治州,博乐市', '3', 'bole', '0909', '833400', 'B', '82.0713', '44.90052'); +INSERT INTO `yoshop_region` VALUES ('3239', '3237', '阿拉山口', '阿拉山口市', '中国,新疆维吾尔自治区,博尔塔拉蒙古自治州,阿拉山口市', '3', 'alashankou', '0909', '833400', 'A', '82.567721', '45.170616'); +INSERT INTO `yoshop_region` VALUES ('3240', '3237', '精河', '精河县', '中国,新疆维吾尔自治区,博尔塔拉蒙古自治州,精河县', '3', 'jinghe', '0909', '833300', 'J', '82.89419', '44.60774'); +INSERT INTO `yoshop_region` VALUES ('3241', '3237', '温泉', '温泉县', '中国,新疆维吾尔自治区,博尔塔拉蒙古自治州,温泉县', '3', 'wenquan', '0909', '833500', 'W', '81.03134', '44.97373'); +INSERT INTO `yoshop_region` VALUES ('3242', '3206', '巴音郭楞', '巴音郭楞蒙古自治州', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州', '2', 'bayingol', '0996', '841000', 'B', '86.150969', '41.768552'); +INSERT INTO `yoshop_region` VALUES ('3243', '3242', '库尔勒', '库尔勒市', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,库尔勒市', '3', 'kuerle', '0996', '841000', 'K', '86.15528', '41.76602'); +INSERT INTO `yoshop_region` VALUES ('3244', '3242', '轮台', '轮台县', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,轮台县', '3', 'luntai', '0996', '841600', 'L', '84.26101', '41.77642'); +INSERT INTO `yoshop_region` VALUES ('3245', '3242', '尉犁', '尉犁县', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,尉犁县', '3', 'yuli', '0996', '841500', 'W', '86.25903', '41.33632'); +INSERT INTO `yoshop_region` VALUES ('3246', '3242', '若羌', '若羌县', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,若羌县', '3', 'ruoqiang', '0996', '841800', 'R', '88.16812', '39.0179'); +INSERT INTO `yoshop_region` VALUES ('3247', '3242', '且末', '且末县', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,且末县', '3', 'qiemo', '0996', '841900', 'Q', '85.52975', '38.14534'); +INSERT INTO `yoshop_region` VALUES ('3248', '3242', '焉耆', '焉耆回族自治县', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,焉耆回族自治县', '3', 'yanqi', '0996', '841100', 'Y', '86.5744', '42.059'); +INSERT INTO `yoshop_region` VALUES ('3249', '3242', '和静', '和静县', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,和静县', '3', 'hejing', '0996', '841300', 'H', '86.39611', '42.31838'); +INSERT INTO `yoshop_region` VALUES ('3250', '3242', '和硕', '和硕县', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,和硕县', '3', 'heshuo', '0996', '841200', 'H', '86.86392', '42.26814'); +INSERT INTO `yoshop_region` VALUES ('3251', '3242', '博湖', '博湖县', '中国,新疆维吾尔自治区,巴音郭楞蒙古自治州,博湖县', '3', 'bohu', '0996', '841400', 'B', '86.63333', '41.98014'); +INSERT INTO `yoshop_region` VALUES ('3252', '3206', '阿克苏', '阿克苏地区', '中国,新疆维吾尔自治区,阿克苏地区', '2', 'aksu', '0997', '843000', 'A', '80.265068', '41.170712'); +INSERT INTO `yoshop_region` VALUES ('3253', '3252', '阿克苏', '阿克苏市', '中国,新疆维吾尔自治区,阿克苏地区,阿克苏市', '3', 'akesu', '0997', '843000', 'A', '80.26338', '41.16754'); +INSERT INTO `yoshop_region` VALUES ('3254', '3252', '温宿', '温宿县', '中国,新疆维吾尔自治区,阿克苏地区,温宿县', '3', 'wensu', '0997', '843100', 'W', '80.24173', '41.27679'); +INSERT INTO `yoshop_region` VALUES ('3255', '3252', '库车', '库车县', '中国,新疆维吾尔自治区,阿克苏地区,库车县', '3', 'kuche', '0997', '842000', 'K', '82.96209', '41.71793'); +INSERT INTO `yoshop_region` VALUES ('3256', '3252', '沙雅', '沙雅县', '中国,新疆维吾尔自治区,阿克苏地区,沙雅县', '3', 'shaya', '0997', '842200', 'S', '82.78131', '41.22497'); +INSERT INTO `yoshop_region` VALUES ('3257', '3252', '新和', '新和县', '中国,新疆维吾尔自治区,阿克苏地区,新和县', '3', 'xinhe', '0997', '842100', 'X', '82.61053', '41.54964'); +INSERT INTO `yoshop_region` VALUES ('3258', '3252', '拜城', '拜城县', '中国,新疆维吾尔自治区,阿克苏地区,拜城县', '3', 'baicheng', '0997', '842300', 'B', '81.87564', '41.79801'); +INSERT INTO `yoshop_region` VALUES ('3259', '3252', '乌什', '乌什县', '中国,新疆维吾尔自治区,阿克苏地区,乌什县', '3', 'wushi', '0997', '843400', 'W', '79.22937', '41.21569'); +INSERT INTO `yoshop_region` VALUES ('3260', '3252', '阿瓦提', '阿瓦提县', '中国,新疆维吾尔自治区,阿克苏地区,阿瓦提县', '3', 'awati', '0997', '843200', 'A', '80.38336', '40.63926'); +INSERT INTO `yoshop_region` VALUES ('3261', '3252', '柯坪', '柯坪县', '中国,新疆维吾尔自治区,阿克苏地区,柯坪县', '3', 'keping', '0997', '843600', 'K', '79.04751', '40.50585'); +INSERT INTO `yoshop_region` VALUES ('3262', '3206', '克孜勒苏', '克孜勒苏柯尔克孜自治州', '中国,新疆维吾尔自治区,克孜勒苏柯尔克孜自治州', '2', 'kizilsu', '0908', '845350', 'K', '76.172825', '39.713431'); +INSERT INTO `yoshop_region` VALUES ('3263', '3262', '阿图什', '阿图什市', '中国,新疆维吾尔自治区,克孜勒苏柯尔克孜自治州,阿图什市', '3', 'atushi', '0908', '845350', 'A', '76.16827', '39.71615'); +INSERT INTO `yoshop_region` VALUES ('3264', '3262', '阿克陶', '阿克陶县', '中国,新疆维吾尔自治区,克孜勒苏柯尔克孜自治州,阿克陶县', '3', 'aketao', '0908', '845550', 'A', '75.94692', '39.14892'); +INSERT INTO `yoshop_region` VALUES ('3265', '3262', '阿合奇', '阿合奇县', '中国,新疆维吾尔自治区,克孜勒苏柯尔克孜自治州,阿合奇县', '3', 'aheqi', '0997', '843500', 'A', '78.44848', '40.93947'); +INSERT INTO `yoshop_region` VALUES ('3266', '3262', '乌恰', '乌恰县', '中国,新疆维吾尔自治区,克孜勒苏柯尔克孜自治州,乌恰县', '3', 'wuqia', '0908', '845450', 'W', '75.25839', '39.71984'); +INSERT INTO `yoshop_region` VALUES ('3267', '3206', '喀什', '喀什地区', '中国,新疆维吾尔自治区,喀什地区', '2', 'kashgar', '0998', '844000', 'K', '75.989138', '39.467664'); +INSERT INTO `yoshop_region` VALUES ('3268', '3267', '喀什', '喀什市', '中国,新疆维吾尔自治区,喀什地区,喀什市', '3', 'kashi', '0998', '844000', 'K', '75.99379', '39.46768'); +INSERT INTO `yoshop_region` VALUES ('3269', '3267', '疏附', '疏附县', '中国,新疆维吾尔自治区,喀什地区,疏附县', '3', 'shufu', '0998', '844100', 'S', '75.86029', '39.37534'); +INSERT INTO `yoshop_region` VALUES ('3270', '3267', '疏勒', '疏勒县', '中国,新疆维吾尔自治区,喀什地区,疏勒县', '3', 'shule', '0998', '844200', 'S', '76.05398', '39.40625'); +INSERT INTO `yoshop_region` VALUES ('3271', '3267', '英吉沙', '英吉沙县', '中国,新疆维吾尔自治区,喀什地区,英吉沙县', '3', 'yingjisha', '0998', '844500', 'Y', '76.17565', '38.92968'); +INSERT INTO `yoshop_region` VALUES ('3272', '3267', '泽普', '泽普县', '中国,新疆维吾尔自治区,喀什地区,泽普县', '3', 'zepu', '0998', '844800', 'Z', '77.27145', '38.18935'); +INSERT INTO `yoshop_region` VALUES ('3273', '3267', '莎车', '莎车县', '中国,新疆维吾尔自治区,喀什地区,莎车县', '3', 'shache', '0998', '844700', 'S', '77.24316', '38.41601'); +INSERT INTO `yoshop_region` VALUES ('3274', '3267', '叶城', '叶城县', '中国,新疆维吾尔自治区,喀什地区,叶城县', '3', 'yecheng', '0998', '844900', 'Y', '77.41659', '37.88324'); +INSERT INTO `yoshop_region` VALUES ('3275', '3267', '麦盖提', '麦盖提县', '中国,新疆维吾尔自治区,喀什地区,麦盖提县', '3', 'maigaiti', '0998', '844600', 'M', '77.64224', '38.89662'); +INSERT INTO `yoshop_region` VALUES ('3276', '3267', '岳普湖', '岳普湖县', '中国,新疆维吾尔自治区,喀什地区,岳普湖县', '3', 'yuepuhu', '0998', '844400', 'Y', '76.77233', '39.23561'); +INSERT INTO `yoshop_region` VALUES ('3277', '3267', '伽师', '伽师县', '中国,新疆维吾尔自治区,喀什地区,伽师县', '3', 'jiashi', '0998', '844300', null, '76.72372', '39.48801'); +INSERT INTO `yoshop_region` VALUES ('3278', '3267', '巴楚', '巴楚县', '中国,新疆维吾尔自治区,喀什地区,巴楚县', '3', 'bachu', '0998', '843800', 'B', '78.54888', '39.7855'); +INSERT INTO `yoshop_region` VALUES ('3279', '3267', '塔什库尔干塔吉克', '塔什库尔干塔吉克自治县', '中国,新疆维吾尔自治区,喀什地区,塔什库尔干塔吉克自治县', '3', 'tashikuergantajike', '0998', '845250', 'T', '75.23196', '37.77893'); +INSERT INTO `yoshop_region` VALUES ('3280', '3206', '和田', '和田地区', '中国,新疆维吾尔自治区,和田地区', '2', 'hotan', '0903', '848000', 'H', '79.92533', '37.110687'); +INSERT INTO `yoshop_region` VALUES ('3281', '3280', '和田市', '和田市', '中国,新疆维吾尔自治区,和田地区,和田市', '3', 'hetianshi', '0903', '848000', 'H', '79.91353', '37.11214'); +INSERT INTO `yoshop_region` VALUES ('3282', '3280', '和田县', '和田县', '中国,新疆维吾尔自治区,和田地区,和田县', '3', 'hetianxian', '0903', '848000', 'H', '79.82874', '37.08922'); +INSERT INTO `yoshop_region` VALUES ('3283', '3280', '墨玉', '墨玉县', '中国,新疆维吾尔自治区,和田地区,墨玉县', '3', 'moyu', '0903', '848100', 'M', '79.74035', '37.27248'); +INSERT INTO `yoshop_region` VALUES ('3284', '3280', '皮山', '皮山县', '中国,新疆维吾尔自治区,和田地区,皮山县', '3', 'pishan', '0903', '845150', 'P', '78.28125', '37.62007'); +INSERT INTO `yoshop_region` VALUES ('3285', '3280', '洛浦', '洛浦县', '中国,新疆维吾尔自治区,和田地区,洛浦县', '3', 'luopu', '0903', '848200', 'L', '80.18536', '37.07364'); +INSERT INTO `yoshop_region` VALUES ('3286', '3280', '策勒', '策勒县', '中国,新疆维吾尔自治区,和田地区,策勒县', '3', 'cele', '0903', '848300', 'C', '80.80999', '36.99843'); +INSERT INTO `yoshop_region` VALUES ('3287', '3280', '于田', '于田县', '中国,新疆维吾尔自治区,和田地区,于田县', '3', 'yutian', '0903', '848400', 'Y', '81.66717', '36.854'); +INSERT INTO `yoshop_region` VALUES ('3288', '3280', '民丰', '民丰县', '中国,新疆维吾尔自治区,和田地区,民丰县', '3', 'minfeng', '0903', '848500', 'M', '82.68444', '37.06577'); +INSERT INTO `yoshop_region` VALUES ('3289', '3206', '伊犁', '伊犁哈萨克自治州', '中国,新疆维吾尔自治区,伊犁哈萨克自治州', '2', 'ili', '0999', '835100', 'Y', '81.317946', '43.92186'); +INSERT INTO `yoshop_region` VALUES ('3290', '3289', '伊宁', '伊宁市', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,伊宁市', '3', 'yining', '0999', '835000', 'Y', '81.32932', '43.91294'); +INSERT INTO `yoshop_region` VALUES ('3291', '3289', '奎屯', '奎屯市', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,奎屯市', '3', 'kuitun', '0992', '833200', 'K', '84.90228', '44.425'); +INSERT INTO `yoshop_region` VALUES ('3292', '3289', '霍尔果斯', '霍尔果斯市', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,霍尔果斯市', '3', 'huoerguosi', '0999', '835221', 'H', '80.418189', '44.205778'); +INSERT INTO `yoshop_region` VALUES ('3293', '3289', '伊宁', '伊宁县', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,伊宁县', '3', 'yining', '0999', '835100', 'Y', '81.52764', '43.97863'); +INSERT INTO `yoshop_region` VALUES ('3294', '3289', '察布查尔锡伯', '察布查尔锡伯自治县', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,察布查尔锡伯自治县', '3', 'chabuchaerxibo', '0999', '835300', 'C', '81.14956', '43.84023'); +INSERT INTO `yoshop_region` VALUES ('3295', '3289', '霍城', '霍城县', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,霍城县', '3', 'huocheng', '0999', '835200', 'H', '80.87826', '44.0533'); +INSERT INTO `yoshop_region` VALUES ('3296', '3289', '巩留', '巩留县', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,巩留县', '3', 'gongliu', '0999', '835400', 'G', '82.22851', '43.48429'); +INSERT INTO `yoshop_region` VALUES ('3297', '3289', '新源', '新源县', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,新源县', '3', 'xinyuan', '0999', '835800', 'X', '83.26095', '43.4284'); +INSERT INTO `yoshop_region` VALUES ('3298', '3289', '昭苏', '昭苏县', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,昭苏县', '3', 'zhaosu', '0999', '835600', 'Z', '81.1307', '43.15828'); +INSERT INTO `yoshop_region` VALUES ('3299', '3289', '特克斯', '特克斯县', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,特克斯县', '3', 'tekesi', '0999', '835500', 'T', '81.84005', '43.21938'); +INSERT INTO `yoshop_region` VALUES ('3300', '3289', '尼勒克', '尼勒克县', '中国,新疆维吾尔自治区,伊犁哈萨克自治州,尼勒克县', '3', 'nileke', '0999', '835700', 'N', '82.51184', '43.79901'); +INSERT INTO `yoshop_region` VALUES ('3301', '3206', '塔城', '塔城地区', '中国,新疆维吾尔自治区,塔城地区', '2', 'qoqek', '0901', '834700', 'T', '82.985732', '46.746301'); +INSERT INTO `yoshop_region` VALUES ('3302', '3301', '塔城', '塔城市', '中国,新疆维吾尔自治区,塔城地区,塔城市', '3', 'tacheng', '0901', '834700', 'T', '82.97892', '46.74852'); +INSERT INTO `yoshop_region` VALUES ('3303', '3301', '乌苏', '乌苏市', '中国,新疆维吾尔自治区,塔城地区,乌苏市', '3', 'wusu', '0992', '833000', 'W', '84.68258', '44.43729'); +INSERT INTO `yoshop_region` VALUES ('3304', '3301', '额敏', '额敏县', '中国,新疆维吾尔自治区,塔城地区,额敏县', '3', 'emin', '0901', '834600', 'E', '83.62872', '46.5284'); +INSERT INTO `yoshop_region` VALUES ('3305', '3301', '沙湾', '沙湾县', '中国,新疆维吾尔自治区,塔城地区,沙湾县', '3', 'shawan', '0993', '832100', 'S', '85.61932', '44.33144'); +INSERT INTO `yoshop_region` VALUES ('3306', '3301', '托里', '托里县', '中国,新疆维吾尔自治区,塔城地区,托里县', '3', 'tuoli', '0901', '834500', 'T', '83.60592', '45.93623'); +INSERT INTO `yoshop_region` VALUES ('3307', '3301', '裕民', '裕民县', '中国,新疆维吾尔自治区,塔城地区,裕民县', '3', 'yumin', '0901', '834800', 'Y', '82.99002', '46.20377'); +INSERT INTO `yoshop_region` VALUES ('3308', '3301', '和布克赛尔', '和布克赛尔蒙古自治县', '中国,新疆维吾尔自治区,塔城地区,和布克赛尔蒙古自治县', '3', 'hebukesaier', '0990', '834400', 'H', '85.72662', '46.79362'); +INSERT INTO `yoshop_region` VALUES ('3309', '3206', '阿勒泰', '阿勒泰地区', '中国,新疆维吾尔自治区,阿勒泰地区', '2', 'altay', '0906', '836500', 'A', '88.13963', '47.848393'); +INSERT INTO `yoshop_region` VALUES ('3310', '3309', '阿勒泰', '阿勒泰市', '中国,新疆维吾尔自治区,阿勒泰地区,阿勒泰市', '3', 'aletai', '0906', '836500', 'A', '88.13913', '47.8317'); +INSERT INTO `yoshop_region` VALUES ('3311', '3309', '布尔津', '布尔津县', '中国,新疆维吾尔自治区,阿勒泰地区,布尔津县', '3', 'buerjin', '0906', '836600', 'B', '86.85751', '47.70062'); +INSERT INTO `yoshop_region` VALUES ('3312', '3309', '富蕴', '富蕴县', '中国,新疆维吾尔自治区,阿勒泰地区,富蕴县', '3', 'fuyun', '0906', '836100', 'F', '89.52679', '46.99444'); +INSERT INTO `yoshop_region` VALUES ('3313', '3309', '福海', '福海县', '中国,新疆维吾尔自治区,阿勒泰地区,福海县', '3', 'fuhai', '0906', '836400', 'F', '87.49508', '47.11065'); +INSERT INTO `yoshop_region` VALUES ('3314', '3309', '哈巴河', '哈巴河县', '中国,新疆维吾尔自治区,阿勒泰地区,哈巴河县', '3', 'habahe', '0906', '836700', 'H', '86.42092', '48.06046'); +INSERT INTO `yoshop_region` VALUES ('3315', '3309', '青河', '青河县', '中国,新疆维吾尔自治区,阿勒泰地区,青河县', '3', 'qinghe', '0906', '836200', 'Q', '90.38305', '46.67382'); +INSERT INTO `yoshop_region` VALUES ('3316', '3309', '吉木乃', '吉木乃县', '中国,新疆维吾尔自治区,阿勒泰地区,吉木乃县', '3', 'jimunai', '0906', '836800', 'J', '85.87814', '47.43359'); +INSERT INTO `yoshop_region` VALUES ('3317', '3206', ' ', '直辖县级', '中国,新疆维吾尔自治区,直辖县级', '2', '', '', '', 'Z', '91.132212', '29.660361'); +INSERT INTO `yoshop_region` VALUES ('3318', '3317', '石河子', '石河子市', '中国,新疆维吾尔自治区,直辖县级,石河子市', '3', 'shihezi', '0993', '832000', 'S', '86.041075', '44.305886'); +INSERT INTO `yoshop_region` VALUES ('3319', '3317', '阿拉尔', '阿拉尔市', '中国,新疆维吾尔自治区,直辖县级,阿拉尔市', '3', 'aral', '0997', '843300', 'A', '81.285884', '40.541914'); +INSERT INTO `yoshop_region` VALUES ('3320', '3317', '图木舒克', '图木舒克市', '中国,新疆维吾尔自治区,直辖县级,图木舒克市', '3', 'tumxuk', '0998', '843806', 'T', '79.077978', '39.867316'); +INSERT INTO `yoshop_region` VALUES ('3321', '3317', '五家渠', '五家渠市', '中国,新疆维吾尔自治区,直辖县级,五家渠市', '3', 'wujiaqu', '0994', '831300', 'W', '87.526884', '44.167401'); +INSERT INTO `yoshop_region` VALUES ('3322', '3317', '北屯', '北屯市', '中国,新疆维吾尔自治区,直辖县级,北屯市', '3', 'beitun', '0906', '836000', 'B', '87.808456', '47.362308'); +INSERT INTO `yoshop_region` VALUES ('3323', '3317', '铁门关', '铁门关市', '中国,新疆维吾尔自治区,直辖县级,铁门关市', '3', 'tiemenguan', '0906', '836000', 'T', '86.194687', '41.811007'); +INSERT INTO `yoshop_region` VALUES ('3324', '3317', '双河', '双河市', '中国,新疆维吾尔自治区,直辖县级,双河市', '3', 'shuanghe', '0909', '833408', 'S', '91.132212', '29.660361'); +INSERT INTO `yoshop_region` VALUES ('3325', '0', '台湾', '台湾省', '中国,台湾省', '1', 'taiwan', '', '', 'T', '121.509062', '25.044332'); +INSERT INTO `yoshop_region` VALUES ('3326', '3325', '台北', '台北市', '中国,台湾省,台北市', '2', 'taipei', '02', '1', 'T', '121.565170', '25.037798'); +INSERT INTO `yoshop_region` VALUES ('3327', '3326', '松山', '松山区', '中国,台湾省,台北市,松山区', '3', 'songshan', '02', '105', 'S', '121.577206', '25.049698'); +INSERT INTO `yoshop_region` VALUES ('3328', '3326', '信义', '信义区', '中国,台湾省,台北市,信义区', '3', 'xinyi', '02', '110', 'X', '121.751381', '25.129407'); +INSERT INTO `yoshop_region` VALUES ('3329', '3326', '大安', '大安区', '中国,台湾省,台北市,大安区', '3', 'da\'an', '02', '106', 'D', '121.534648', '25.026406'); +INSERT INTO `yoshop_region` VALUES ('3330', '3326', '中山', '中山区', '中国,台湾省,台北市,中山区', '3', 'zhongshan', '02', '104', 'Z', '121.533468', '25.064361'); +INSERT INTO `yoshop_region` VALUES ('3331', '3326', '中正', '中正区', '中国,台湾省,台北市,中正区', '3', 'zhongzheng', '02', '100', 'Z', '121.518267', '25.032361'); +INSERT INTO `yoshop_region` VALUES ('3332', '3326', '大同', '大同区', '中国,台湾省,台北市,大同区', '3', 'datong', '02', '103', 'D', '121.515514', '25.065986'); +INSERT INTO `yoshop_region` VALUES ('3333', '3326', '万华', '万华区', '中国,台湾省,台北市,万华区', '3', 'wanhua', '02', '108', 'W', '121.499332', '25.031933'); +INSERT INTO `yoshop_region` VALUES ('3334', '3326', '文山', '文山区', '中国,台湾省,台北市,文山区', '3', 'wenshan', '02', '116', 'W', '121.570458', '24.989786'); +INSERT INTO `yoshop_region` VALUES ('3335', '3326', '南港', '南港区', '中国,台湾省,台北市,南港区', '3', 'nangang', '02', '115', 'N', '121.606858', '25.054656'); +INSERT INTO `yoshop_region` VALUES ('3336', '3326', '内湖', '内湖区', '中国,台湾省,台北市,内湖区', '3', 'nahu', '02', '114', 'N', '121.588998', '25.069664'); +INSERT INTO `yoshop_region` VALUES ('3337', '3326', '士林', '士林区', '中国,台湾省,台北市,士林区', '3', 'shilin', '02', '111', 'S', '121.519874', '25.092822'); +INSERT INTO `yoshop_region` VALUES ('3338', '3326', '北投', '北投区', '中国,台湾省,台北市,北投区', '3', 'beitou', '02', '112', 'B', '121.501379', '25.132419'); +INSERT INTO `yoshop_region` VALUES ('3339', '3325', '高雄', '高雄市', '中国,台湾省,高雄市', '2', 'kaohsiung', '07', '8', 'G', '120.311922', '22.620856'); +INSERT INTO `yoshop_region` VALUES ('3340', '3339', '盐埕', '盐埕区', '中国,台湾省,高雄市,盐埕区', '3', 'yancheng', '07', '803', 'Y', '120.286795', '22.624666'); +INSERT INTO `yoshop_region` VALUES ('3341', '3339', '鼓山', '鼓山区', '中国,台湾省,高雄市,鼓山区', '3', 'gushan', '07', '804', 'G', '120.281154', '22.636797'); +INSERT INTO `yoshop_region` VALUES ('3342', '3339', '左营', '左营区', '中国,台湾省,高雄市,左营区', '3', 'zuoying', '07', '813', 'Z', '120.294958', '22.690124'); +INSERT INTO `yoshop_region` VALUES ('3343', '3339', '楠梓', '楠梓区', '中国,台湾省,高雄市,楠梓区', '3', 'nanzi', '07', '811', null, '120.326314', '22.728401'); +INSERT INTO `yoshop_region` VALUES ('3344', '3339', '三民', '三民区', '中国,台湾省,高雄市,三民区', '3', 'sanmin', '07', '807', 'S', '120.299622', '22.647695'); +INSERT INTO `yoshop_region` VALUES ('3345', '3339', '新兴', '新兴区', '中国,台湾省,高雄市,新兴区', '3', 'xinxing', '07', '800', 'X', '120.309535', '22.631147'); +INSERT INTO `yoshop_region` VALUES ('3346', '3339', '前金', '前金区', '中国,台湾省,高雄市,前金区', '3', 'qianjin', '07', '801', 'Q', '120.294159', '22.627421'); +INSERT INTO `yoshop_region` VALUES ('3347', '3339', '苓雅', '苓雅区', '中国,台湾省,高雄市,苓雅区', '3', 'lingya', '07', '802', null, '120.312347', '22.621770'); +INSERT INTO `yoshop_region` VALUES ('3348', '3339', '前镇', '前镇区', '中国,台湾省,高雄市,前镇区', '3', 'qianzhen', '07', '806', 'Q', '120.318583', '22.586425'); +INSERT INTO `yoshop_region` VALUES ('3349', '3339', '旗津', '旗津区', '中国,台湾省,高雄市,旗津区', '3', 'qijin', '07', '805', 'Q', '120.284429', '22.590565'); +INSERT INTO `yoshop_region` VALUES ('3350', '3339', '小港', '小港区', '中国,台湾省,高雄市,小港区', '3', 'xiaogang', '07', '812', 'X', '120.337970', '22.565354'); +INSERT INTO `yoshop_region` VALUES ('3351', '3339', '凤山', '凤山区', '中国,台湾省,高雄市,凤山区', '3', 'fengshan', '07', '830', 'F', '120.356892', '22.626945'); +INSERT INTO `yoshop_region` VALUES ('3352', '3339', '林园', '林园区', '中国,台湾省,高雄市,林园区', '3', 'linyuan', '07', '832', 'L', '120.395977', '22.501490'); +INSERT INTO `yoshop_region` VALUES ('3353', '3339', '大寮', '大寮区', '中国,台湾省,高雄市,大寮区', '3', 'daliao', '07', '831', 'D', '120.395422', '22.605386'); +INSERT INTO `yoshop_region` VALUES ('3354', '3339', '大树', '大树区', '中国,台湾省,高雄市,大树区', '3', 'dashu', '07', '840', 'D', '120.433095', '22.693394'); +INSERT INTO `yoshop_region` VALUES ('3355', '3339', '大社', '大社区', '中国,台湾省,高雄市,大社区', '3', 'dashe', '07', '815', 'D', '120.346635', '22.729966'); +INSERT INTO `yoshop_region` VALUES ('3356', '3339', '仁武', '仁武区', '中国,台湾省,高雄市,仁武区', '3', 'renwu', '07', '814', 'R', '120.347779', '22.701901'); +INSERT INTO `yoshop_region` VALUES ('3357', '3339', '鸟松', '鸟松区', '中国,台湾省,高雄市,鸟松区', '3', 'niaosong', '07', '833', 'N', '120.364402', '22.659340'); +INSERT INTO `yoshop_region` VALUES ('3358', '3339', '冈山', '冈山区', '中国,台湾省,高雄市,冈山区', '3', 'gangshan', '07', '820', 'G', '120.295820', '22.796762'); +INSERT INTO `yoshop_region` VALUES ('3359', '3339', '桥头', '桥头区', '中国,台湾省,高雄市,桥头区', '3', 'qiaotou', '07', '825', 'Q', '120.305741', '22.757501'); +INSERT INTO `yoshop_region` VALUES ('3360', '3339', '燕巢', '燕巢区', '中国,台湾省,高雄市,燕巢区', '3', 'yanchao', '07', '824', 'Y', '120.361956', '22.793370'); +INSERT INTO `yoshop_region` VALUES ('3361', '3339', '田寮', '田寮区', '中国,台湾省,高雄市,田寮区', '3', 'tianliao', '07', '823', 'T', '120.359636', '22.869307'); +INSERT INTO `yoshop_region` VALUES ('3362', '3339', '阿莲', '阿莲区', '中国,台湾省,高雄市,阿莲区', '3', 'alian', '07', '822', 'A', '120.327036', '22.883703'); +INSERT INTO `yoshop_region` VALUES ('3363', '3339', '路竹', '路竹区', '中国,台湾省,高雄市,路竹区', '3', 'luzhu', '07', '821', 'L', '120.261828', '22.856851'); +INSERT INTO `yoshop_region` VALUES ('3364', '3339', '湖内', '湖内区', '中国,台湾省,高雄市,湖内区', '3', 'huna', '07', '829', 'H', '120.211530', '22.908188'); +INSERT INTO `yoshop_region` VALUES ('3365', '3339', '茄萣', '茄萣区', '中国,台湾省,高雄市,茄萣区', '3', 'qieding', '07', '852', null, '120.182815', '22.906556'); +INSERT INTO `yoshop_region` VALUES ('3366', '3339', '永安', '永安区', '中国,台湾省,高雄市,永安区', '3', 'yong\'an', '07', '828', 'Y', '120.225308', '22.818580'); +INSERT INTO `yoshop_region` VALUES ('3367', '3339', '弥陀', '弥陀区', '中国,台湾省,高雄市,弥陀区', '3', 'mituo', '07', '827', 'M', '120.247344', '22.782879'); +INSERT INTO `yoshop_region` VALUES ('3368', '3339', '梓官', '梓官区', '中国,台湾省,高雄市,梓官区', '3', 'ziguan', '07', '826', null, '120.267322', '22.760475'); +INSERT INTO `yoshop_region` VALUES ('3369', '3339', '旗山', '旗山区', '中国,台湾省,高雄市,旗山区', '3', 'qishan', '07', '842', 'Q', '120.483550', '22.888491'); +INSERT INTO `yoshop_region` VALUES ('3370', '3339', '美浓', '美浓区', '中国,台湾省,高雄市,美浓区', '3', 'meinong', '07', '843', 'M', '120.541530', '22.897880'); +INSERT INTO `yoshop_region` VALUES ('3371', '3339', '六龟', '六龟区', '中国,台湾省,高雄市,六龟区', '3', 'liugui', '07', '844', 'L', '120.633418', '22.997914'); +INSERT INTO `yoshop_region` VALUES ('3372', '3339', '甲仙', '甲仙区', '中国,台湾省,高雄市,甲仙区', '3', 'jiaxian', '07', '847', 'J', '120.591185', '23.084688'); +INSERT INTO `yoshop_region` VALUES ('3373', '3339', '杉林', '杉林区', '中国,台湾省,高雄市,杉林区', '3', 'shanlin', '07', '846', 'S', '120.538980', '22.970712'); +INSERT INTO `yoshop_region` VALUES ('3374', '3339', '内门', '内门区', '中国,台湾省,高雄市,内门区', '3', 'namen', '07', '845', 'N', '120.462351', '22.943437'); +INSERT INTO `yoshop_region` VALUES ('3375', '3339', '茂林', '茂林区', '中国,台湾省,高雄市,茂林区', '3', 'maolin', '07', '851', 'M', '120.663217', '22.886248'); +INSERT INTO `yoshop_region` VALUES ('3376', '3339', '桃源', '桃源区', '中国,台湾省,高雄市,桃源区', '3', 'taoyuan', '07', '848', 'T', '120.760049', '23.159137'); +INSERT INTO `yoshop_region` VALUES ('3377', '3339', '那玛夏', '那玛夏区', '中国,台湾省,高雄市,那玛夏区', '3', 'namaxia', '07', '849', 'N', '120.692799', '23.216964'); +INSERT INTO `yoshop_region` VALUES ('3378', '3325', '基隆', '基隆市', '中国,台湾省,基隆市', '2', 'keelung', '02', '2', 'J', '121.746248', '25.130741'); +INSERT INTO `yoshop_region` VALUES ('3379', '3378', '中正', '中正区', '中国,台湾省,基隆市,中正区', '3', 'zhongzheng', '02', '202', 'Z', '121.518267', '25.032361'); +INSERT INTO `yoshop_region` VALUES ('3380', '3378', '七堵', '七堵区', '中国,台湾省,基隆市,七堵区', '3', 'qidu', '02', '206', 'Q', '121.713032', '25.095739'); +INSERT INTO `yoshop_region` VALUES ('3381', '3378', '暖暖', '暖暖区', '中国,台湾省,基隆市,暖暖区', '3', 'nuannuan', '02', '205', 'N', '121.736102', '25.099777'); +INSERT INTO `yoshop_region` VALUES ('3382', '3378', '仁爱', '仁爱区', '中国,台湾省,基隆市,仁爱区', '3', 'renai', '02', '200', 'R', '121.740940', '25.127526'); +INSERT INTO `yoshop_region` VALUES ('3383', '3378', '中山', '中山区', '中国,台湾省,基隆市,中山区', '3', 'zhongshan', '02', '203', 'Z', '121.739132', '25.133991'); +INSERT INTO `yoshop_region` VALUES ('3384', '3378', '安乐', '安乐区', '中国,台湾省,基隆市,安乐区', '3', 'anle', '02', '204', 'A', '121.723203', '25.120910'); +INSERT INTO `yoshop_region` VALUES ('3385', '3378', '信义', '信义区', '中国,台湾省,基隆市,信义区', '3', 'xinyi', '02', '201', 'X', '121.751381', '25.129407'); +INSERT INTO `yoshop_region` VALUES ('3386', '3325', '台中', '台中市', '中国,台湾省,台中市', '2', 'taichung', '04', '4', 'T', '120.679040', '24.138620'); +INSERT INTO `yoshop_region` VALUES ('3387', '3386', '中区', '中区', '中国,台湾省,台中市,中区', '3', 'zhongqu', '04', '400', 'Z', '120.679510', '24.143830'); +INSERT INTO `yoshop_region` VALUES ('3388', '3386', '东区', '东区', '中国,台湾省,台中市,东区', '3', 'dongqu', '04', '401', 'D', '120.704', '24.13662'); +INSERT INTO `yoshop_region` VALUES ('3389', '3386', '南区', '南区', '中国,台湾省,台中市,南区', '3', 'nanqu', '04', '402', 'N', '120.188648', '22.960944'); +INSERT INTO `yoshop_region` VALUES ('3390', '3386', '西区', '西区', '中国,台湾省,台中市,西区', '3', 'xiqu', '04', '403', 'X', '120.67104', '24.14138'); +INSERT INTO `yoshop_region` VALUES ('3391', '3386', '北区', '北区', '中国,台湾省,台中市,北区', '3', 'beiqu', '04', '404', 'B', '120.682410', '24.166040'); +INSERT INTO `yoshop_region` VALUES ('3392', '3386', '西屯', '西屯区', '中国,台湾省,台中市,西屯区', '3', 'xitun', '04', '407', 'X', '120.639820', '24.181340'); +INSERT INTO `yoshop_region` VALUES ('3393', '3386', '南屯', '南屯区', '中国,台湾省,台中市,南屯区', '3', 'nantun', '04', '408', 'N', '120.643080', '24.138270'); +INSERT INTO `yoshop_region` VALUES ('3394', '3386', '北屯', '北屯区', '中国,台湾省,台中市,北屯区', '3', 'beitun', '04', '406', 'B', '120.686250', '24.182220'); +INSERT INTO `yoshop_region` VALUES ('3395', '3386', '丰原', '丰原区', '中国,台湾省,台中市,丰原区', '3', 'fengyuan', '04', '420', 'F', '120.718460', '24.242190'); +INSERT INTO `yoshop_region` VALUES ('3396', '3386', '东势', '东势区', '中国,台湾省,台中市,东势区', '3', 'dongshi', '04', '423', 'D', '120.827770', '24.258610'); +INSERT INTO `yoshop_region` VALUES ('3397', '3386', '大甲', '大甲区', '中国,台湾省,台中市,大甲区', '3', 'dajia', '04', '437', 'D', '120.622390', '24.348920'); +INSERT INTO `yoshop_region` VALUES ('3398', '3386', '清水', '清水区', '中国,台湾省,台中市,清水区', '3', 'qingshui', '04', '436', 'Q', '120.559780', '24.268650'); +INSERT INTO `yoshop_region` VALUES ('3399', '3386', '沙鹿', '沙鹿区', '中国,台湾省,台中市,沙鹿区', '3', 'shalu', '04', '433', 'S', '120.565700', '24.233480'); +INSERT INTO `yoshop_region` VALUES ('3400', '3386', '梧栖', '梧栖区', '中国,台湾省,台中市,梧栖区', '3', 'wuqi', '04', '435', 'W', '120.531520', '24.254960'); +INSERT INTO `yoshop_region` VALUES ('3401', '3386', '后里', '后里区', '中国,台湾省,台中市,后里区', '3', 'houli', '04', '421', 'H', '120.710710', '24.304910'); +INSERT INTO `yoshop_region` VALUES ('3402', '3386', '神冈', '神冈区', '中国,台湾省,台中市,神冈区', '3', 'shengang', '04', '429', 'S', '120.661550', '24.257770'); +INSERT INTO `yoshop_region` VALUES ('3403', '3386', '潭子', '潭子区', '中国,台湾省,台中市,潭子区', '3', 'tanzi', '04', '427', 'T', '120.705160', '24.209530'); +INSERT INTO `yoshop_region` VALUES ('3404', '3386', '大雅', '大雅区', '中国,台湾省,台中市,大雅区', '3', 'daya', '04', '428', 'D', '120.647780', '24.229230'); +INSERT INTO `yoshop_region` VALUES ('3405', '3386', '新社', '新社区', '中国,台湾省,台中市,新社区', '3', 'xinshe', '04', '426', 'X', '120.809500', '24.234140'); +INSERT INTO `yoshop_region` VALUES ('3406', '3386', '石冈', '石冈区', '中国,台湾省,台中市,石冈区', '3', 'shigang', '04', '422', 'S', '120.780410', '24.274980'); +INSERT INTO `yoshop_region` VALUES ('3407', '3386', '外埔', '外埔区', '中国,台湾省,台中市,外埔区', '3', 'waipu', '04', '438', 'W', '120.654370', '24.332010'); +INSERT INTO `yoshop_region` VALUES ('3408', '3386', '大安', '大安区', '中国,台湾省,台中市,大安区', '3', 'da\'an', '04', '439', 'D', '120.58652', '24.34607'); +INSERT INTO `yoshop_region` VALUES ('3409', '3386', '乌日', '乌日区', '中国,台湾省,台中市,乌日区', '3', 'wuri', '04', '414', 'W', '120.623810', '24.104500'); +INSERT INTO `yoshop_region` VALUES ('3410', '3386', '大肚', '大肚区', '中国,台湾省,台中市,大肚区', '3', 'dadu', '04', '432', 'D', '120.540960', '24.153660'); +INSERT INTO `yoshop_region` VALUES ('3411', '3386', '龙井', '龙井区', '中国,台湾省,台中市,龙井区', '3', 'longjing', '04', '434', 'L', '120.545940', '24.192710'); +INSERT INTO `yoshop_region` VALUES ('3412', '3386', '雾峰', '雾峰区', '中国,台湾省,台中市,雾峰区', '3', 'wufeng', '04', '413', 'W', '120.700200', '24.061520'); +INSERT INTO `yoshop_region` VALUES ('3413', '3386', '太平', '太平区', '中国,台湾省,台中市,太平区', '3', 'taiping', '04', '411', 'T', '120.718523', '24.126472'); +INSERT INTO `yoshop_region` VALUES ('3414', '3386', '大里', '大里区', '中国,台湾省,台中市,大里区', '3', 'dali', '04', '412', 'D', '120.677860', '24.099390'); +INSERT INTO `yoshop_region` VALUES ('3415', '3386', '和平', '和平区', '中国,台湾省,台中市,和平区', '3', 'heping', '04', '424', 'H', '120.88349', '24.17477'); +INSERT INTO `yoshop_region` VALUES ('3416', '3325', '台南', '台南市', '中国,台湾省,台南市', '2', 'tainan', '06', '7', 'T', '120.279363', '23.172478'); +INSERT INTO `yoshop_region` VALUES ('3417', '3416', '东区', '东区', '中国,台湾省,台南市,东区', '3', 'dongqu', '06', '701', 'D', '120.224198', '22.980072'); +INSERT INTO `yoshop_region` VALUES ('3418', '3416', '南区', '南区', '中国,台湾省,台南市,南区', '3', 'nanqu', '06', '702', 'N', '120.188648', '22.960944'); +INSERT INTO `yoshop_region` VALUES ('3419', '3416', '北区', '北区', '中国,台湾省,台南市,北区', '3', 'beiqu', '06', '704', 'B', '120.682410', '24.166040'); +INSERT INTO `yoshop_region` VALUES ('3420', '3416', '安南', '安南区', '中国,台湾省,台南市,安南区', '3', 'annan', '06', '709', 'A', '120.184617', '23.047230'); +INSERT INTO `yoshop_region` VALUES ('3421', '3416', '安平', '安平区', '中国,台湾省,台南市,安平区', '3', 'anping', '06', '708', 'A', '120.166810', '23.000763'); +INSERT INTO `yoshop_region` VALUES ('3422', '3416', '中西', '中西区', '中国,台湾省,台南市,中西区', '3', 'zhongxi', '06', '700', 'Z', '120.205957', '22.992152'); +INSERT INTO `yoshop_region` VALUES ('3423', '3416', '新营', '新营区', '中国,台湾省,台南市,新营区', '3', 'xinying', '06', '730', 'X', '120.316698', '23.310274'); +INSERT INTO `yoshop_region` VALUES ('3424', '3416', '盐水', '盐水区', '中国,台湾省,台南市,盐水区', '3', 'yanshui', '06', '737', 'Y', '120.266398', '23.319828'); +INSERT INTO `yoshop_region` VALUES ('3425', '3416', '白河', '白河区', '中国,台湾省,台南市,白河区', '3', 'baihe', '06', '732', 'B', '120.415810', '23.351221'); +INSERT INTO `yoshop_region` VALUES ('3426', '3416', '柳营', '柳营区', '中国,台湾省,台南市,柳营区', '3', 'liuying', '06', '736', 'L', '120.311286', '23.278133'); +INSERT INTO `yoshop_region` VALUES ('3427', '3416', '后壁', '后壁区', '中国,台湾省,台南市,后壁区', '3', 'houbi', '06', '731', 'H', '120.362726', '23.366721'); +INSERT INTO `yoshop_region` VALUES ('3428', '3416', '东山', '东山区', '中国,台湾省,台南市,东山区', '3', 'dongshan', '06', '733', 'D', '120.403984', '23.326092'); +INSERT INTO `yoshop_region` VALUES ('3429', '3416', '麻豆', '麻豆区', '中国,台湾省,台南市,麻豆区', '3', 'madou', '06', '721', 'M', '120.248179', '23.181680'); +INSERT INTO `yoshop_region` VALUES ('3430', '3416', '下营', '下营区', '中国,台湾省,台南市,下营区', '3', 'xiaying', '06', '735', 'X', '120.264484', '23.235413'); +INSERT INTO `yoshop_region` VALUES ('3431', '3416', '六甲', '六甲区', '中国,台湾省,台南市,六甲区', '3', 'liujia', '06', '734', 'L', '120.347600', '23.231931'); +INSERT INTO `yoshop_region` VALUES ('3432', '3416', '官田', '官田区', '中国,台湾省,台南市,官田区', '3', 'guantian', '06', '720', 'G', '120.314374', '23.194652'); +INSERT INTO `yoshop_region` VALUES ('3433', '3416', '大内', '大内区', '中国,台湾省,台南市,大内区', '3', 'dana', '06', '742', 'D', '120.348853', '23.119460'); +INSERT INTO `yoshop_region` VALUES ('3434', '3416', '佳里', '佳里区', '中国,台湾省,台南市,佳里区', '3', 'jiali', '06', '722', 'J', '120.177211', '23.165121'); +INSERT INTO `yoshop_region` VALUES ('3435', '3416', '学甲', '学甲区', '中国,台湾省,台南市,学甲区', '3', 'xuejia', '06', '726', 'X', '120.180255', '23.232348'); +INSERT INTO `yoshop_region` VALUES ('3436', '3416', '西港', '西港区', '中国,台湾省,台南市,西港区', '3', 'xigang', '06', '723', 'X', '120.203618', '23.123057'); +INSERT INTO `yoshop_region` VALUES ('3437', '3416', '七股', '七股区', '中国,台湾省,台南市,七股区', '3', 'qigu', '06', '724', 'Q', '120.140003', '23.140545'); +INSERT INTO `yoshop_region` VALUES ('3438', '3416', '将军', '将军区', '中国,台湾省,台南市,将军区', '3', 'jiangjun', '06', '725', 'J', '120.156871', '23.199543'); +INSERT INTO `yoshop_region` VALUES ('3439', '3416', '北门', '北门区', '中国,台湾省,台南市,北门区', '3', 'beimen', '06', '727', 'B', '120.125821', '23.267148'); +INSERT INTO `yoshop_region` VALUES ('3440', '3416', '新化', '新化区', '中国,台湾省,台南市,新化区', '3', 'xinhua', '06', '712', 'X', '120.310807', '23.038533'); +INSERT INTO `yoshop_region` VALUES ('3441', '3416', '善化', '善化区', '中国,台湾省,台南市,善化区', '3', 'shanhua', '06', '741', 'S', '120.296895', '23.132261'); +INSERT INTO `yoshop_region` VALUES ('3442', '3416', '新市', '新市区', '中国,台湾省,台南市,新市区', '3', 'xinshi', '06', '744', 'X', '120.295138', '23.07897'); +INSERT INTO `yoshop_region` VALUES ('3443', '3416', '安定', '安定区', '中国,台湾省,台南市,安定区', '3', 'anding', '06', '745', 'A', '120.237083', '23.121498'); +INSERT INTO `yoshop_region` VALUES ('3444', '3416', '山上', '山上区', '中国,台湾省,台南市,山上区', '3', 'shanshang', '06', '743', 'S', '120.352908', '23.103223'); +INSERT INTO `yoshop_region` VALUES ('3445', '3416', '玉井', '玉井区', '中国,台湾省,台南市,玉井区', '3', 'yujing', '06', '714', 'Y', '120.460110', '23.123859'); +INSERT INTO `yoshop_region` VALUES ('3446', '3416', '楠西', '楠西区', '中国,台湾省,台南市,楠西区', '3', 'nanxi', '06', '715', null, '120.485396', '23.173454'); +INSERT INTO `yoshop_region` VALUES ('3447', '3416', '南化', '南化区', '中国,台湾省,台南市,南化区', '3', 'nanhua', '06', '716', 'N', '120.477116', '23.042614'); +INSERT INTO `yoshop_region` VALUES ('3448', '3416', '左镇', '左镇区', '中国,台湾省,台南市,左镇区', '3', 'zuozhen', '06', '713', 'Z', '120.407309', '23.057955'); +INSERT INTO `yoshop_region` VALUES ('3449', '3416', '仁德', '仁德区', '中国,台湾省,台南市,仁德区', '3', 'rende', '06', '717', 'R', '120.251520', '22.972212'); +INSERT INTO `yoshop_region` VALUES ('3450', '3416', '归仁', '归仁区', '中国,台湾省,台南市,归仁区', '3', 'guiren', '06', '711', 'G', '120.293791', '22.967081'); +INSERT INTO `yoshop_region` VALUES ('3451', '3416', '关庙', '关庙区', '中国,台湾省,台南市,关庙区', '3', 'guanmiao', '06', '718', 'G', '120.327689', '22.962949'); +INSERT INTO `yoshop_region` VALUES ('3452', '3416', '龙崎', '龙崎区', '中国,台湾省,台南市,龙崎区', '3', 'longqi', '06', '719', 'L', '120.360824', '22.965681'); +INSERT INTO `yoshop_region` VALUES ('3453', '3416', '永康', '永康区', '中国,台湾省,台南市,永康区', '3', 'yongkang', '06', '710', 'Y', '120.257069', '23.026061'); +INSERT INTO `yoshop_region` VALUES ('3454', '3325', '新竹', '新竹市', '中国,台湾省,新竹市', '2', 'hsinchu', '03', '3', 'X', '120.968798', '24.806738'); +INSERT INTO `yoshop_region` VALUES ('3455', '3454', '东区', '东区', '中国,台湾省,新竹市,东区', '3', 'dongqu', '03', '300', 'D', '120.970239', '24.801337'); +INSERT INTO `yoshop_region` VALUES ('3456', '3454', '北区', '北区', '中国,台湾省,新竹市,北区', '3', 'beiqu', '03', '', 'B', '120.682410', '24.166040'); +INSERT INTO `yoshop_region` VALUES ('3457', '3454', '香山', '香山区', '中国,台湾省,新竹市,香山区', '3', 'xiangshan', '03', '', 'X', '120.956679', '24.768933'); +INSERT INTO `yoshop_region` VALUES ('3458', '3325', '嘉义', '嘉义市', '中国,台湾省,嘉义市', '2', 'chiayi', '05', '6', 'J', '120.452538', '23.481568'); +INSERT INTO `yoshop_region` VALUES ('3459', '3458', '东区', '东区', '中国,台湾省,嘉义市,东区', '3', 'dongqu', '05', '600', 'D', '120.458009', '23.486213'); +INSERT INTO `yoshop_region` VALUES ('3460', '3458', '西区', '西区', '中国,台湾省,嘉义市,西区', '3', 'xiqu', '05', '600', 'X', '120.437493', '23.473029'); +INSERT INTO `yoshop_region` VALUES ('3461', '3325', '新北', '新北市', '中国,台湾省,新北市', '2', 'newtaipei', '02', '2', 'X', '121.465746', '25.012366'); +INSERT INTO `yoshop_region` VALUES ('3462', '3461', '板桥', '板桥区', '中国,台湾省,新北市,板桥区', '3', 'banqiao', '02', '220', 'B', '121.459084', '25.009578'); +INSERT INTO `yoshop_region` VALUES ('3463', '3461', '三重', '三重区', '中国,台湾省,新北市,三重区', '3', 'sanzhong', '02', '241', 'S', '121.488102', '25.061486'); +INSERT INTO `yoshop_region` VALUES ('3464', '3461', '中和', '中和区', '中国,台湾省,新北市,中和区', '3', 'zhonghe', '02', '235', 'Z', '121.498980', '24.999397'); +INSERT INTO `yoshop_region` VALUES ('3465', '3461', '永和', '永和区', '中国,台湾省,新北市,永和区', '3', 'yonghe', '02', '234', 'Y', '121.513660', '25.007802'); +INSERT INTO `yoshop_region` VALUES ('3466', '3461', '新庄', '新庄区', '中国,台湾省,新北市,新庄区', '3', 'xinzhuang', '02', '242', 'X', '121.450413', '25.035947'); +INSERT INTO `yoshop_region` VALUES ('3467', '3461', '新店', '新店区', '中国,台湾省,新北市,新店区', '3', 'xindian', '02', '231', 'X', '121.541750', '24.967558'); +INSERT INTO `yoshop_region` VALUES ('3468', '3461', '树林', '树林区', '中国,台湾省,新北市,树林区', '3', 'shulin', '02', '238', 'S', '121.420533', '24.990706'); +INSERT INTO `yoshop_region` VALUES ('3469', '3461', '莺歌', '莺歌区', '中国,台湾省,新北市,莺歌区', '3', 'yingge', '02', '239', null, '121.354573', '24.955413'); +INSERT INTO `yoshop_region` VALUES ('3470', '3461', '三峡', '三峡区', '中国,台湾省,新北市,三峡区', '3', 'sanxia', '02', '237', 'S', '121.368905', '24.934339'); +INSERT INTO `yoshop_region` VALUES ('3471', '3461', '淡水', '淡水区', '中国,台湾省,新北市,淡水区', '3', 'danshui', '02', '251', 'D', '121.440915', '25.169452'); +INSERT INTO `yoshop_region` VALUES ('3472', '3461', '汐止', '汐止区', '中国,台湾省,新北市,汐止区', '3', 'xizhi', '02', '221', 'X', '121.629470', '25.062999'); +INSERT INTO `yoshop_region` VALUES ('3473', '3461', '瑞芳', '瑞芳区', '中国,台湾省,新北市,瑞芳区', '3', 'ruifang', '02', '224', 'R', '121.810061', '25.108895'); +INSERT INTO `yoshop_region` VALUES ('3474', '3461', '土城', '土城区', '中国,台湾省,新北市,土城区', '3', 'tucheng', '02', '236', 'T', '121.443348', '24.972201'); +INSERT INTO `yoshop_region` VALUES ('3475', '3461', '芦洲', '芦洲区', '中国,台湾省,新北市,芦洲区', '3', 'luzhou', '02', '247', 'L', '121.473700', '25.084923'); +INSERT INTO `yoshop_region` VALUES ('3476', '3461', '五股', '五股区', '中国,台湾省,新北市,五股区', '3', 'wugu', '02', '248', 'W', '121.438156', '25.082743'); +INSERT INTO `yoshop_region` VALUES ('3477', '3461', '泰山', '泰山区', '中国,台湾省,新北市,泰山区', '3', 'taishan', '02', '243', 'T', '121.430811', '25.058864'); +INSERT INTO `yoshop_region` VALUES ('3478', '3461', '林口', '林口区', '中国,台湾省,新北市,林口区', '3', 'linkou', '02', '244', 'L', '121.391602', '25.077531'); +INSERT INTO `yoshop_region` VALUES ('3479', '3461', '深坑', '深坑区', '中国,台湾省,新北市,深坑区', '3', 'shenkeng', '02', '222', 'S', '121.615670', '25.002329'); +INSERT INTO `yoshop_region` VALUES ('3480', '3461', '石碇', '石碇区', '中国,台湾省,新北市,石碇区', '3', 'shiding', '02', '223', 'S', '121.658567', '24.991679'); +INSERT INTO `yoshop_region` VALUES ('3481', '3461', '坪林', '坪林区', '中国,台湾省,新北市,坪林区', '3', 'pinglin', '02', '232', 'P', '121.711185', '24.937388'); +INSERT INTO `yoshop_region` VALUES ('3482', '3461', '三芝', '三芝区', '中国,台湾省,新北市,三芝区', '3', 'sanzhi', '02', '252', 'S', '121.500866', '25.258047'); +INSERT INTO `yoshop_region` VALUES ('3483', '3461', '石门', '石门区', '中国,台湾省,新北市,石门区', '3', 'shimen', '02', '253', 'S', '121.568491', '25.290412'); +INSERT INTO `yoshop_region` VALUES ('3484', '3461', '八里', '八里区', '中国,台湾省,新北市,八里区', '3', 'bali', '02', '249', 'B', '121.398227', '25.146680'); +INSERT INTO `yoshop_region` VALUES ('3485', '3461', '平溪', '平溪区', '中国,台湾省,新北市,平溪区', '3', 'pingxi', '02', '226', 'P', '121.738255', '25.025725'); +INSERT INTO `yoshop_region` VALUES ('3486', '3461', '双溪', '双溪区', '中国,台湾省,新北市,双溪区', '3', 'shuangxi', '02', '227', 'S', '121.865676', '25.033409'); +INSERT INTO `yoshop_region` VALUES ('3487', '3461', '贡寮', '贡寮区', '中国,台湾省,新北市,贡寮区', '3', 'gongliao', '02', '228', 'G', '121.908185', '25.022388'); +INSERT INTO `yoshop_region` VALUES ('3488', '3461', '金山', '金山区', '中国,台湾省,新北市,金山区', '3', 'jinshan', '02', '208', 'J', '121.636427', '25.221883'); +INSERT INTO `yoshop_region` VALUES ('3489', '3461', '万里', '万里区', '中国,台湾省,新北市,万里区', '3', 'wanli', '02', '207', 'W', '121.688687', '25.181234'); +INSERT INTO `yoshop_region` VALUES ('3490', '3461', '乌来', '乌来区', '中国,台湾省,新北市,乌来区', '3', 'wulai', '02', '233', 'W', '121.550531', '24.865296'); +INSERT INTO `yoshop_region` VALUES ('3491', '3325', '宜兰', '宜兰县', '中国,台湾省,宜兰县', '2', 'yilan', '03', '2', 'Y', '121.500000', '24.600000'); +INSERT INTO `yoshop_region` VALUES ('3492', '3491', '宜兰', '宜兰市', '中国,台湾省,宜兰县,宜兰市', '3', 'yilan', '03', '260', 'Y', '121.753476', '24.751682'); +INSERT INTO `yoshop_region` VALUES ('3493', '3491', '罗东', '罗东镇', '中国,台湾省,宜兰县,罗东镇', '3', 'luodong', '03', '265', 'L', '121.766919', '24.677033'); +INSERT INTO `yoshop_region` VALUES ('3494', '3491', '苏澳', '苏澳镇', '中国,台湾省,宜兰县,苏澳镇', '3', 'suao', '03', '270', 'S', '121.842656', '24.594622'); +INSERT INTO `yoshop_region` VALUES ('3495', '3491', '头城', '头城镇', '中国,台湾省,宜兰县,头城镇', '3', 'toucheng', '03', '261', 'T', '121.823307', '24.859217'); +INSERT INTO `yoshop_region` VALUES ('3496', '3491', '礁溪', '礁溪乡', '中国,台湾省,宜兰县,礁溪乡', '3', 'jiaoxi', '03', '262', 'J', '121.766680', '24.822345'); +INSERT INTO `yoshop_region` VALUES ('3497', '3491', '壮围', '壮围乡', '中国,台湾省,宜兰县,壮围乡', '3', 'zhuangwei', '03', '263', 'Z', '121.781619', '24.744949'); +INSERT INTO `yoshop_region` VALUES ('3498', '3491', '员山', '员山乡', '中国,台湾省,宜兰县,员山乡', '3', 'yuanshan', '03', '264', 'Y', '121.721733', '24.741771'); +INSERT INTO `yoshop_region` VALUES ('3499', '3491', '冬山', '冬山乡', '中国,台湾省,宜兰县,冬山乡', '3', 'dongshan', '03', '269', 'D', '121.792280', '24.634514'); +INSERT INTO `yoshop_region` VALUES ('3500', '3491', '五结', '五结乡', '中国,台湾省,宜兰县,五结乡', '3', 'wujie', '03', '268', 'W', '121.798297', '24.684640'); +INSERT INTO `yoshop_region` VALUES ('3501', '3491', '三星', '三星乡', '中国,台湾省,宜兰县,三星乡', '3', 'sanxing', '03', '266', 'S', '121.003418', '23.775291'); +INSERT INTO `yoshop_region` VALUES ('3502', '3491', '大同', '大同乡', '中国,台湾省,宜兰县,大同乡', '3', 'datong', '03', '267', 'D', '121.605557', '24.675997'); +INSERT INTO `yoshop_region` VALUES ('3503', '3491', '南澳', '南澳乡', '中国,台湾省,宜兰县,南澳乡', '3', 'nanao', '03', '272', 'N', '121.799810', '24.465393'); +INSERT INTO `yoshop_region` VALUES ('3504', '3325', '桃园', '桃园县', '中国,台湾省,桃园县', '2', 'taoyuan', '03', '3', 'T', '121.083000', '25.000000'); +INSERT INTO `yoshop_region` VALUES ('3505', '3504', '桃园', '桃园市', '中国,台湾省,桃园县,桃园市', '3', 'taoyuan', '03', '330', 'T', '121.301337', '24.993777'); +INSERT INTO `yoshop_region` VALUES ('3506', '3504', '中坜', '中坜市', '中国,台湾省,桃园县,中坜市', '3', 'zhongli', '03', '320', 'Z', '121.224926', '24.965353'); +INSERT INTO `yoshop_region` VALUES ('3507', '3504', '平镇', '平镇市', '中国,台湾省,桃园县,平镇市', '3', 'pingzhen', '03', '324', 'P', '121.218359', '24.945752'); +INSERT INTO `yoshop_region` VALUES ('3508', '3504', '八德', '八德市', '中国,台湾省,桃园县,八德市', '3', 'bade', '03', '334', 'B', '121.284655', '24.928651'); +INSERT INTO `yoshop_region` VALUES ('3509', '3504', '杨梅', '杨梅市', '中国,台湾省,桃园县,杨梅市', '3', 'yangmei', '03', '326', 'Y', '121.145873', '24.907575'); +INSERT INTO `yoshop_region` VALUES ('3510', '3504', '芦竹', '芦竹市', '中国,台湾省,桃园县,芦竹市', '3', 'luzhu', '03', '338', 'L', '121.292064', '25.045392'); +INSERT INTO `yoshop_region` VALUES ('3511', '3504', '大溪', '大溪镇', '中国,台湾省,桃园县,大溪镇', '3', 'daxi', '03', '335', 'D', '121.286962', '24.880584'); +INSERT INTO `yoshop_region` VALUES ('3512', '3504', '大园', '大园乡', '中国,台湾省,桃园县,大园乡', '3', 'dayuan', '03', '337', 'D', '121.196292', '25.064471'); +INSERT INTO `yoshop_region` VALUES ('3513', '3504', '龟山', '龟山乡', '中国,台湾省,桃园县,龟山乡', '3', 'guishan', '03', '333', 'G', '121.337767', '24.992517'); +INSERT INTO `yoshop_region` VALUES ('3514', '3504', '龙潭', '龙潭乡', '中国,台湾省,桃园县,龙潭乡', '3', 'longtan', '03', '325', 'L', '121.216392', '24.863851'); +INSERT INTO `yoshop_region` VALUES ('3515', '3504', '新屋', '新屋乡', '中国,台湾省,桃园县,新屋乡', '3', 'xinwu', '03', '327', 'X', '121.105801', '24.972203'); +INSERT INTO `yoshop_region` VALUES ('3516', '3504', '观音', '观音乡', '中国,台湾省,桃园县,观音乡', '3', 'guanyin', '03', '328', 'G', '121.077519', '25.033303'); +INSERT INTO `yoshop_region` VALUES ('3517', '3504', '复兴', '复兴乡', '中国,台湾省,桃园县,复兴乡', '3', 'fuxing', '03', '336', 'F', '121.352613', '24.820908'); +INSERT INTO `yoshop_region` VALUES ('3518', '3325', '新竹', '新竹县', '中国,台湾省,新竹县', '2', 'hsinchu', '03', '3', 'X', '121.160000', '24.600000'); +INSERT INTO `yoshop_region` VALUES ('3519', '3518', '竹北', '竹北市', '中国,台湾省,新竹县,竹北市', '3', 'zhubei', '03', '302', 'Z', '121.004317', '24.839652'); +INSERT INTO `yoshop_region` VALUES ('3520', '3518', '竹东', '竹东镇', '中国,台湾省,新竹县,竹东镇', '3', 'zhudong', '03', '310', 'Z', '121.086418', '24.733601'); +INSERT INTO `yoshop_region` VALUES ('3521', '3518', '新埔', '新埔镇', '中国,台湾省,新竹县,新埔镇', '3', 'xinpu', '03', '305', 'X', '121.072804', '24.824820'); +INSERT INTO `yoshop_region` VALUES ('3522', '3518', '关西', '关西镇', '中国,台湾省,新竹县,关西镇', '3', 'guanxi', '03', '306', 'G', '121.177301', '24.788842'); +INSERT INTO `yoshop_region` VALUES ('3523', '3518', '湖口', '湖口乡', '中国,台湾省,新竹县,湖口乡', '3', 'hukou', '03', '303', 'H', '121.043691', '24.903943'); +INSERT INTO `yoshop_region` VALUES ('3524', '3518', '新丰', '新丰乡', '中国,台湾省,新竹县,新丰乡', '3', 'xinfeng', '03', '304', 'X', '120.983006', '24.899600'); +INSERT INTO `yoshop_region` VALUES ('3525', '3518', '芎林', '芎林乡', '中国,台湾省,新竹县,芎林乡', '3', 'xionglin', '03', '307', null, '121.076924', '24.774436'); +INSERT INTO `yoshop_region` VALUES ('3526', '3518', '横山', '横山乡', '中国,台湾省,新竹县,横山乡', '3', 'hengshan', '03', '312', 'H', '121.116244', '24.720807'); +INSERT INTO `yoshop_region` VALUES ('3527', '3518', '北埔', '北埔乡', '中国,台湾省,新竹县,北埔乡', '3', 'beipu', '03', '314', 'B', '121.053156', '24.697126'); +INSERT INTO `yoshop_region` VALUES ('3528', '3518', '宝山', '宝山乡', '中国,台湾省,新竹县,宝山乡', '3', 'baoshan', '03', '308', 'B', '120.985752', '24.760975'); +INSERT INTO `yoshop_region` VALUES ('3529', '3518', '峨眉', '峨眉乡', '中国,台湾省,新竹县,峨眉乡', '3', 'emei', '03', '315', 'E', '121.015291', '24.686127'); +INSERT INTO `yoshop_region` VALUES ('3530', '3518', '尖石', '尖石乡', '中国,台湾省,新竹县,尖石乡', '3', 'jianshi', '03', '313', 'J', '121.197802', '24.704360'); +INSERT INTO `yoshop_region` VALUES ('3531', '3518', '五峰', '五峰乡', '中国,台湾省,新竹县,五峰乡', '3', 'wufeng', '03', '311', 'W', '121.003418', '23.775291'); +INSERT INTO `yoshop_region` VALUES ('3532', '3325', '苗栗', '苗栗县', '中国,台湾省,苗栗县', '2', 'miaoli', '037', '3', 'M', '120.750000', '24.500000'); +INSERT INTO `yoshop_region` VALUES ('3533', '3532', '苗栗', '苗栗市', '中国,台湾省,苗栗县,苗栗市', '3', 'miaoli', '037', '360', 'M', '120.818869', '24.561472'); +INSERT INTO `yoshop_region` VALUES ('3534', '3532', '苑里', '苑里镇', '中国,台湾省,苗栗县,苑里镇', '3', 'yuanli', '037', '358', 'Y', '120.648907', '24.441750'); +INSERT INTO `yoshop_region` VALUES ('3535', '3532', '通霄', '通霄镇', '中国,台湾省,苗栗县,通霄镇', '3', 'tongxiao', '037', '357', 'T', '120.676700', '24.489087'); +INSERT INTO `yoshop_region` VALUES ('3536', '3532', '竹南', '竹南镇', '中国,台湾省,苗栗县,竹南镇', '3', 'zhunan', '037', '350', 'Z', '120.872641', '24.685513'); +INSERT INTO `yoshop_region` VALUES ('3537', '3532', '头份', '头份镇', '中国,台湾省,苗栗县,头份镇', '3', 'toufen', '037', '351', 'T', '120.895188', '24.687993'); +INSERT INTO `yoshop_region` VALUES ('3538', '3532', '后龙', '后龙镇', '中国,台湾省,苗栗县,后龙镇', '3', 'houlong', '037', '356', 'H', '120.786480', '24.612617'); +INSERT INTO `yoshop_region` VALUES ('3539', '3532', '卓兰', '卓兰镇', '中国,台湾省,苗栗县,卓兰镇', '3', 'zhuolan', '037', '369', 'Z', '120.823441', '24.309509'); +INSERT INTO `yoshop_region` VALUES ('3540', '3532', '大湖', '大湖乡', '中国,台湾省,苗栗县,大湖乡', '3', 'dahu', '037', '364', 'D', '120.863641', '24.422547'); +INSERT INTO `yoshop_region` VALUES ('3541', '3532', '公馆', '公馆乡', '中国,台湾省,苗栗县,公馆乡', '3', 'gongguan', '037', '363', 'G', '120.822983', '24.499108'); +INSERT INTO `yoshop_region` VALUES ('3542', '3532', '铜锣', '铜锣乡', '中国,台湾省,苗栗县,铜锣乡', '3', 'tongluo', '037', '366', 'T', '121.003418', '23.775291'); +INSERT INTO `yoshop_region` VALUES ('3543', '3532', '南庄', '南庄乡', '中国,台湾省,苗栗县,南庄乡', '3', 'nanzhuang', '037', '353', 'N', '120.994957', '24.596835'); +INSERT INTO `yoshop_region` VALUES ('3544', '3532', '头屋', '头屋乡', '中国,台湾省,苗栗县,头屋乡', '3', 'touwu', '037', '362', 'T', '120.846616', '24.574249'); +INSERT INTO `yoshop_region` VALUES ('3545', '3532', '三义', '三义乡', '中国,台湾省,苗栗县,三义乡', '3', 'sanyi', '037', '367', 'S', '120.742340', '24.350270'); +INSERT INTO `yoshop_region` VALUES ('3546', '3532', '西湖', '西湖乡', '中国,台湾省,苗栗县,西湖乡', '3', 'xihu', '037', '368', 'X', '121.003418', '23.775291'); +INSERT INTO `yoshop_region` VALUES ('3547', '3532', '造桥', '造桥乡', '中国,台湾省,苗栗县,造桥乡', '3', 'zaoqiao', '037', '361', 'Z', '120.862399', '24.637537'); +INSERT INTO `yoshop_region` VALUES ('3548', '3532', '三湾', '三湾乡', '中国,台湾省,苗栗县,三湾乡', '3', 'sanwan', '037', '352', 'S', '120.951484', '24.651051'); +INSERT INTO `yoshop_region` VALUES ('3549', '3532', '狮潭', '狮潭乡', '中国,台湾省,苗栗县,狮潭乡', '3', 'shitan', '037', '354', 'S', '120.918024', '24.540004'); +INSERT INTO `yoshop_region` VALUES ('3550', '3532', '泰安', '泰安乡', '中国,台湾省,苗栗县,泰安乡', '3', 'tai\'an', '037', '365', 'T', '120.904441', '24.442600'); +INSERT INTO `yoshop_region` VALUES ('3551', '3325', '彰化', '彰化县', '中国,台湾省,彰化县', '2', 'changhua', '04', '5', 'Z', '120.416000', '24.000000'); +INSERT INTO `yoshop_region` VALUES ('3552', '3551', '彰化市', '彰化市', '中国,台湾省,彰化县,彰化市', '3', 'zhanghuashi', '04', '500', 'Z', '120.542294', '24.080911'); +INSERT INTO `yoshop_region` VALUES ('3553', '3551', '鹿港', '鹿港镇', '中国,台湾省,彰化县,鹿港镇', '3', 'lugang', '04', '505', 'L', '120.435392', '24.056937'); +INSERT INTO `yoshop_region` VALUES ('3554', '3551', '和美', '和美镇', '中国,台湾省,彰化县,和美镇', '3', 'hemei', '04', '508', 'H', '120.500265', '24.110904'); +INSERT INTO `yoshop_region` VALUES ('3555', '3551', '线西', '线西乡', '中国,台湾省,彰化县,线西乡', '3', 'xianxi', '04', '507', 'X', '120.465921', '24.128653'); +INSERT INTO `yoshop_region` VALUES ('3556', '3551', '伸港', '伸港乡', '中国,台湾省,彰化县,伸港乡', '3', 'shengang', '04', '509', 'S', '120.484224', '24.146081'); +INSERT INTO `yoshop_region` VALUES ('3557', '3551', '福兴', '福兴乡', '中国,台湾省,彰化县,福兴乡', '3', 'fuxing', '04', '506', 'F', '120.443682', '24.047883'); +INSERT INTO `yoshop_region` VALUES ('3558', '3551', '秀水', '秀水乡', '中国,台湾省,彰化县,秀水乡', '3', 'xiushui', '04', '504', 'X', '120.502658', '24.035267'); +INSERT INTO `yoshop_region` VALUES ('3559', '3551', '花坛', '花坛乡', '中国,台湾省,彰化县,花坛乡', '3', 'huatan', '04', '503', 'H', '120.538403', '24.029399'); +INSERT INTO `yoshop_region` VALUES ('3560', '3551', '芬园', '芬园乡', '中国,台湾省,彰化县,芬园乡', '3', 'fenyuan', '04', '502', 'F', '120.629024', '24.013658'); +INSERT INTO `yoshop_region` VALUES ('3561', '3551', '员林', '员林镇', '中国,台湾省,彰化县,员林镇', '3', 'yuanlin', '04', '510', 'Y', '120.574625', '23.958999'); +INSERT INTO `yoshop_region` VALUES ('3562', '3551', '溪湖', '溪湖镇', '中国,台湾省,彰化县,溪湖镇', '3', 'xihu', '04', '514', 'X', '120.479144', '23.962315'); +INSERT INTO `yoshop_region` VALUES ('3563', '3551', '田中', '田中镇', '中国,台湾省,彰化县,田中镇', '3', 'tianzhong', '04', '520', 'T', '120.580629', '23.861718'); +INSERT INTO `yoshop_region` VALUES ('3564', '3551', '大村', '大村乡', '中国,台湾省,彰化县,大村乡', '3', 'dacun', '04', '515', 'D', '120.540713', '23.993726'); +INSERT INTO `yoshop_region` VALUES ('3565', '3551', '埔盐', '埔盐乡', '中国,台湾省,彰化县,埔盐乡', '3', 'puyan', '04', '516', 'P', '120.464044', '24.000346'); +INSERT INTO `yoshop_region` VALUES ('3566', '3551', '埔心', '埔心乡', '中国,台湾省,彰化县,埔心乡', '3', 'puxin', '04', '513', 'P', '120.543568', '23.953019'); +INSERT INTO `yoshop_region` VALUES ('3567', '3551', '永靖', '永靖乡', '中国,台湾省,彰化县,永靖乡', '3', 'yongjing', '04', '512', 'Y', '120.547775', '23.924703'); +INSERT INTO `yoshop_region` VALUES ('3568', '3551', '社头', '社头乡', '中国,台湾省,彰化县,社头乡', '3', 'shetou', '04', '511', 'S', '120.582681', '23.896686'); +INSERT INTO `yoshop_region` VALUES ('3569', '3551', '二水', '二水乡', '中国,台湾省,彰化县,二水乡', '3', 'ershui', '04', '530', 'E', '120.618788', '23.806995'); +INSERT INTO `yoshop_region` VALUES ('3570', '3551', '北斗', '北斗镇', '中国,台湾省,彰化县,北斗镇', '3', 'beidou', '04', '521', 'B', '120.520449', '23.870911'); +INSERT INTO `yoshop_region` VALUES ('3571', '3551', '二林', '二林镇', '中国,台湾省,彰化县,二林镇', '3', 'erlin', '04', '526', 'E', '120.374468', '23.899751'); +INSERT INTO `yoshop_region` VALUES ('3572', '3551', '田尾', '田尾乡', '中国,台湾省,彰化县,田尾乡', '3', 'tianwei', '04', '522', 'T', '120.524717', '23.890735'); +INSERT INTO `yoshop_region` VALUES ('3573', '3551', '埤头', '埤头乡', '中国,台湾省,彰化县,埤头乡', '3', 'pitou', '04', '523', null, '120.462599', '23.891324'); +INSERT INTO `yoshop_region` VALUES ('3574', '3551', '芳苑', '芳苑乡', '中国,台湾省,彰化县,芳苑乡', '3', 'fangyuan', '04', '528', 'F', '120.320329', '23.924222'); +INSERT INTO `yoshop_region` VALUES ('3575', '3551', '大城', '大城乡', '中国,台湾省,彰化县,大城乡', '3', 'dacheng', '04', '527', 'D', '120.320934', '23.852382'); +INSERT INTO `yoshop_region` VALUES ('3576', '3551', '竹塘', '竹塘乡', '中国,台湾省,彰化县,竹塘乡', '3', 'zhutang', '04', '525', 'Z', '120.427499', '23.860112'); +INSERT INTO `yoshop_region` VALUES ('3577', '3551', '溪州', '溪州乡', '中国,台湾省,彰化县,溪州乡', '3', 'xizhou', '04', '524', 'X', '120.498706', '23.851229'); +INSERT INTO `yoshop_region` VALUES ('3578', '3325', '南投', '南投县', '中国,台湾省,南投县', '2', 'nantou', '049', '5', 'N', '120.830000', '23.830000'); +INSERT INTO `yoshop_region` VALUES ('3579', '3578', '南投市', '南投市', '中国,台湾省,南投县,南投市', '3', 'nantoushi', '049', '540', 'N', '120.683706', '23.909956'); +INSERT INTO `yoshop_region` VALUES ('3580', '3578', '埔里', '埔里镇', '中国,台湾省,南投县,埔里镇', '3', 'puli', '049', '545', 'P', '120.964648', '23.964789'); +INSERT INTO `yoshop_region` VALUES ('3581', '3578', '草屯', '草屯镇', '中国,台湾省,南投县,草屯镇', '3', 'caotun', '049', '542', 'C', '120.680343', '23.973947'); +INSERT INTO `yoshop_region` VALUES ('3582', '3578', '竹山', '竹山镇', '中国,台湾省,南投县,竹山镇', '3', 'zhushan', '049', '557', 'Z', '120.672007', '23.757655'); +INSERT INTO `yoshop_region` VALUES ('3583', '3578', '集集', '集集镇', '中国,台湾省,南投县,集集镇', '3', 'jiji', '049', '552', 'J', '120.783673', '23.829013'); +INSERT INTO `yoshop_region` VALUES ('3584', '3578', '名间', '名间乡', '中国,台湾省,南投县,名间乡', '3', 'mingjian', '049', '551', 'M', '120.702797', '23.838427'); +INSERT INTO `yoshop_region` VALUES ('3585', '3578', '鹿谷', '鹿谷乡', '中国,台湾省,南投县,鹿谷乡', '3', 'lugu', '049', '558', 'L', '120.752796', '23.744471'); +INSERT INTO `yoshop_region` VALUES ('3586', '3578', '中寮', '中寮乡', '中国,台湾省,南投县,中寮乡', '3', 'zhongliao', '049', '541', 'Z', '120.766654', '23.878935'); +INSERT INTO `yoshop_region` VALUES ('3587', '3578', '鱼池', '鱼池乡', '中国,台湾省,南投县,鱼池乡', '3', 'yuchi', '049', '555', 'Y', '120.936060', '23.896356'); +INSERT INTO `yoshop_region` VALUES ('3588', '3578', '国姓', '国姓乡', '中国,台湾省,南投县,国姓乡', '3', 'guoxing', '049', '544', 'G', '120.858541', '24.042298'); +INSERT INTO `yoshop_region` VALUES ('3589', '3578', '水里', '水里乡', '中国,台湾省,南投县,水里乡', '3', 'shuili', '049', '553', 'S', '120.855912', '23.812086'); +INSERT INTO `yoshop_region` VALUES ('3590', '3578', '信义', '信义乡', '中国,台湾省,南投县,信义乡', '3', 'xinyi', '049', '556', 'X', '120.855257', '23.699922'); +INSERT INTO `yoshop_region` VALUES ('3591', '3578', '仁爱', '仁爱乡', '中国,台湾省,南投县,仁爱乡', '3', 'renai', '049', '546', 'R', '121.133543', '24.024429'); +INSERT INTO `yoshop_region` VALUES ('3592', '3325', '云林', '云林县', '中国,台湾省,云林县', '2', 'yunlin', '05', '6', 'Y', '120.250000', '23.750000'); +INSERT INTO `yoshop_region` VALUES ('3593', '3592', '斗六', '斗六市', '中国,台湾省,云林县,斗六市', '3', 'douliu', '05', '640', 'D', '120.527360', '23.697651'); +INSERT INTO `yoshop_region` VALUES ('3594', '3592', '斗南', '斗南镇', '中国,台湾省,云林县,斗南镇', '3', 'dounan', '05', '630', 'D', '120.479075', '23.679731'); +INSERT INTO `yoshop_region` VALUES ('3595', '3592', '虎尾', '虎尾镇', '中国,台湾省,云林县,虎尾镇', '3', 'huwei', '05', '632', 'H', '120.445339', '23.708182'); +INSERT INTO `yoshop_region` VALUES ('3596', '3592', '西螺', '西螺镇', '中国,台湾省,云林县,西螺镇', '3', 'xiluo', '05', '648', 'X', '120.466010', '23.797984'); +INSERT INTO `yoshop_region` VALUES ('3597', '3592', '土库', '土库镇', '中国,台湾省,云林县,土库镇', '3', 'tuku', '05', '633', 'T', '120.392572', '23.677822'); +INSERT INTO `yoshop_region` VALUES ('3598', '3592', '北港', '北港镇', '中国,台湾省,云林县,北港镇', '3', 'beigang', '05', '651', 'B', '120.302393', '23.575525'); +INSERT INTO `yoshop_region` VALUES ('3599', '3592', '古坑', '古坑乡', '中国,台湾省,云林县,古坑乡', '3', 'gukeng', '05', '646', 'G', '120.562043', '23.642568'); +INSERT INTO `yoshop_region` VALUES ('3600', '3592', '大埤', '大埤乡', '中国,台湾省,云林县,大埤乡', '3', 'dapi', '05', '631', 'D', '120.430516', '23.645908'); +INSERT INTO `yoshop_region` VALUES ('3601', '3592', '莿桐', '莿桐乡', '中国,台湾省,云林县,莿桐乡', '3', 'citong', '05', '647', null, '120.502374', '23.760784'); +INSERT INTO `yoshop_region` VALUES ('3602', '3592', '林内', '林内乡', '中国,台湾省,云林县,林内乡', '3', 'linna', '05', '643', 'L', '120.611365', '23.758712'); +INSERT INTO `yoshop_region` VALUES ('3603', '3592', '二仑', '二仑乡', '中国,台湾省,云林县,二仑乡', '3', 'erlun', '05', '649', 'E', '120.415077', '23.771273'); +INSERT INTO `yoshop_region` VALUES ('3604', '3592', '仑背', '仑背乡', '中国,台湾省,云林县,仑背乡', '3', 'lunbei', '05', '637', 'L', '120.353895', '23.758840'); +INSERT INTO `yoshop_region` VALUES ('3605', '3592', '麦寮', '麦寮乡', '中国,台湾省,云林县,麦寮乡', '3', 'mailiao', '05', '638', 'M', '120.252043', '23.753841'); +INSERT INTO `yoshop_region` VALUES ('3606', '3592', '东势', '东势乡', '中国,台湾省,云林县,东势乡', '3', 'dongshi', '05', '635', 'D', '120.252672', '23.674679'); +INSERT INTO `yoshop_region` VALUES ('3607', '3592', '褒忠', '褒忠乡', '中国,台湾省,云林县,褒忠乡', '3', 'baozhong', '05', '634', 'B', '120.310488', '23.694245'); +INSERT INTO `yoshop_region` VALUES ('3608', '3592', '台西', '台西乡', '中国,台湾省,云林县,台西乡', '3', 'taixi', '05', '636', 'T', '120.196141', '23.702819'); +INSERT INTO `yoshop_region` VALUES ('3609', '3592', '元长', '元长乡', '中国,台湾省,云林县,元长乡', '3', 'yuanchang', '05', '655', 'Y', '120.315124', '23.649458'); +INSERT INTO `yoshop_region` VALUES ('3610', '3592', '四湖', '四湖乡', '中国,台湾省,云林县,四湖乡', '3', 'sihu', '05', '654', 'S', '120.225741', '23.637740'); +INSERT INTO `yoshop_region` VALUES ('3611', '3592', '口湖', '口湖乡', '中国,台湾省,云林县,口湖乡', '3', 'kouhu', '05', '653', 'K', '120.185370', '23.582406'); +INSERT INTO `yoshop_region` VALUES ('3612', '3592', '水林', '水林乡', '中国,台湾省,云林县,水林乡', '3', 'shuilin', '05', '652', 'S', '120.245948', '23.572634'); +INSERT INTO `yoshop_region` VALUES ('3613', '3325', '嘉义', '嘉义县', '中国,台湾省,嘉义县', '2', 'chiayi', '05', '6', 'J', '120.300000', '23.500000'); +INSERT INTO `yoshop_region` VALUES ('3614', '3613', '太保', '太保市', '中国,台湾省,嘉义县,太保市', '3', 'taibao', '05', '612', 'T', '120.332876', '23.459647'); +INSERT INTO `yoshop_region` VALUES ('3615', '3613', '朴子', '朴子市', '中国,台湾省,嘉义县,朴子市', '3', 'puzi', '05', '613', 'P', '120.247014', '23.464961'); +INSERT INTO `yoshop_region` VALUES ('3616', '3613', '布袋', '布袋镇', '中国,台湾省,嘉义县,布袋镇', '3', 'budai', '05', '625', 'B', '120.166936', '23.377979'); +INSERT INTO `yoshop_region` VALUES ('3617', '3613', '大林', '大林镇', '中国,台湾省,嘉义县,大林镇', '3', 'dalin', '05', '622', 'D', '120.471336', '23.603815'); +INSERT INTO `yoshop_region` VALUES ('3618', '3613', '民雄', '民雄乡', '中国,台湾省,嘉义县,民雄乡', '3', 'minxiong', '05', '621', 'M', '120.428577', '23.551456'); +INSERT INTO `yoshop_region` VALUES ('3619', '3613', '溪口', '溪口乡', '中国,台湾省,嘉义县,溪口乡', '3', 'xikou', '05', '623', 'X', '120.393822', '23.602223'); +INSERT INTO `yoshop_region` VALUES ('3620', '3613', '新港', '新港乡', '中国,台湾省,嘉义县,新港乡', '3', 'xingang', '05', '616', 'X', '120.347647', '23.551806'); +INSERT INTO `yoshop_region` VALUES ('3621', '3613', '六脚', '六脚乡', '中国,台湾省,嘉义县,六脚乡', '3', 'liujiao', '05', '615', 'L', '120.291083', '23.493942'); +INSERT INTO `yoshop_region` VALUES ('3622', '3613', '东石', '东石乡', '中国,台湾省,嘉义县,东石乡', '3', 'dongshi', '05', '614', 'D', '120.153822', '23.459235'); +INSERT INTO `yoshop_region` VALUES ('3623', '3613', '义竹', '义竹乡', '中国,台湾省,嘉义县,义竹乡', '3', 'yizhu', '05', '624', 'Y', '120.243423', '23.336277'); +INSERT INTO `yoshop_region` VALUES ('3624', '3613', '鹿草', '鹿草乡', '中国,台湾省,嘉义县,鹿草乡', '3', 'lucao', '05', '611', 'L', '120.308370', '23.410784'); +INSERT INTO `yoshop_region` VALUES ('3625', '3613', '水上', '水上乡', '中国,台湾省,嘉义县,水上乡', '3', 'shuishang', '05', '608', 'S', '120.397936', '23.428104'); +INSERT INTO `yoshop_region` VALUES ('3626', '3613', '中埔', '中埔乡', '中国,台湾省,嘉义县,中埔乡', '3', 'zhongpu', '05', '606', 'Z', '120.522948', '23.425148'); +INSERT INTO `yoshop_region` VALUES ('3627', '3613', '竹崎', '竹崎乡', '中国,台湾省,嘉义县,竹崎乡', '3', 'zhuqi', '05', '604', 'Z', '120.551466', '23.523184'); +INSERT INTO `yoshop_region` VALUES ('3628', '3613', '梅山', '梅山乡', '中国,台湾省,嘉义县,梅山乡', '3', 'meishan', '05', '603', 'M', '120.557192', '23.584915'); +INSERT INTO `yoshop_region` VALUES ('3629', '3613', '番路', '番路乡', '中国,台湾省,嘉义县,番路乡', '3', 'fanlu', '05', '602', 'F', '120.555043', '23.465222'); +INSERT INTO `yoshop_region` VALUES ('3630', '3613', '大埔', '大埔乡', '中国,台湾省,嘉义县,大埔乡', '3', 'dapu', '05', '607', 'D', '120.593795', '23.296715'); +INSERT INTO `yoshop_region` VALUES ('3631', '3613', '阿里山', '阿里山乡', '中国,台湾省,嘉义县,阿里山乡', '3', 'alishan', '05', '605', 'A', '120.732520', '23.467950'); +INSERT INTO `yoshop_region` VALUES ('3632', '3325', '屏东', '屏东县', '中国,台湾省,屏东县', '2', 'pingtung', '08', '9', 'P', '120.487928', '22.682802'); +INSERT INTO `yoshop_region` VALUES ('3633', '3632', '屏东', '屏东市', '中国,台湾省,屏东县,屏东市', '3', 'pingdong', '08', '900', 'P', '120.488465', '22.669723'); +INSERT INTO `yoshop_region` VALUES ('3634', '3632', '潮州', '潮州镇', '中国,台湾省,屏东县,潮州镇', '3', 'chaozhou', '08', '920', 'C', '120.542854', '22.550536'); +INSERT INTO `yoshop_region` VALUES ('3635', '3632', '东港', '东港镇', '中国,台湾省,屏东县,东港镇', '3', 'donggang', '08', '928', 'D', '120.454489', '22.466626'); +INSERT INTO `yoshop_region` VALUES ('3636', '3632', '恒春', '恒春镇', '中国,台湾省,屏东县,恒春镇', '3', 'hengchun', '08', '946', 'H', '120.745451', '22.002373'); +INSERT INTO `yoshop_region` VALUES ('3637', '3632', '万丹', '万丹乡', '中国,台湾省,屏东县,万丹乡', '3', 'wandan', '08', '913', 'W', '120.484533', '22.589839'); +INSERT INTO `yoshop_region` VALUES ('3638', '3632', '长治', '长治乡', '中国,台湾省,屏东县,长治乡', '3', 'changzhi', '08', '908', 'C', '120.527614', '22.677062'); +INSERT INTO `yoshop_region` VALUES ('3639', '3632', '麟洛', '麟洛乡', '中国,台湾省,屏东县,麟洛乡', '3', 'linluo', '08', '909', null, '120.527283', '22.650604'); +INSERT INTO `yoshop_region` VALUES ('3640', '3632', '九如', '九如乡', '中国,台湾省,屏东县,九如乡', '3', 'jiuru', '08', '904', 'J', '120.490142', '22.739778'); +INSERT INTO `yoshop_region` VALUES ('3641', '3632', '里港', '里港乡', '中国,台湾省,屏东县,里港乡', '3', 'ligang', '08', '905', 'L', '120.494490', '22.779220'); +INSERT INTO `yoshop_region` VALUES ('3642', '3632', '盐埔', '盐埔乡', '中国,台湾省,屏东县,盐埔乡', '3', 'yanpu', '08', '907', 'Y', '120.572849', '22.754783'); +INSERT INTO `yoshop_region` VALUES ('3643', '3632', '高树', '高树乡', '中国,台湾省,屏东县,高树乡', '3', 'gaoshu', '08', '906', 'G', '120.600214', '22.826789'); +INSERT INTO `yoshop_region` VALUES ('3644', '3632', '万峦', '万峦乡', '中国,台湾省,屏东县,万峦乡', '3', 'wanluan', '08', '923', 'W', '120.566477', '22.571965'); +INSERT INTO `yoshop_region` VALUES ('3645', '3632', '内埔', '内埔乡', '中国,台湾省,屏东县,内埔乡', '3', 'napu', '08', '912', 'N', '120.566865', '22.611967'); +INSERT INTO `yoshop_region` VALUES ('3646', '3632', '竹田', '竹田乡', '中国,台湾省,屏东县,竹田乡', '3', 'zhutian', '08', '911', 'Z', '120.544038', '22.584678'); +INSERT INTO `yoshop_region` VALUES ('3647', '3632', '新埤', '新埤乡', '中国,台湾省,屏东县,新埤乡', '3', 'xinpi', '08', '925', 'X', '120.549546', '22.469976'); +INSERT INTO `yoshop_region` VALUES ('3648', '3632', '枋寮', '枋寮乡', '中国,台湾省,屏东县,枋寮乡', '3', 'fangliao', '08', '940', null, '120.593438', '22.365560'); +INSERT INTO `yoshop_region` VALUES ('3649', '3632', '新园', '新园乡', '中国,台湾省,屏东县,新园乡', '3', 'xinyuan', '08', '932', 'X', '120.461739', '22.543952'); +INSERT INTO `yoshop_region` VALUES ('3650', '3632', '崁顶', '崁顶乡', '中国,台湾省,屏东县,崁顶乡', '3', 'kanding', '08', '924', null, '120.514571', '22.514795'); +INSERT INTO `yoshop_region` VALUES ('3651', '3632', '林边', '林边乡', '中国,台湾省,屏东县,林边乡', '3', 'linbian', '08', '927', 'L', '120.515091', '22.434015'); +INSERT INTO `yoshop_region` VALUES ('3652', '3632', '南州', '南州乡', '中国,台湾省,屏东县,南州乡', '3', 'nanzhou', '08', '926', 'N', '120.509808', '22.490192'); +INSERT INTO `yoshop_region` VALUES ('3653', '3632', '佳冬', '佳冬乡', '中国,台湾省,屏东县,佳冬乡', '3', 'jiadong', '08', '931', 'J', '120.551544', '22.417653'); +INSERT INTO `yoshop_region` VALUES ('3654', '3632', '琉球', '琉球乡', '中国,台湾省,屏东县,琉球乡', '3', 'liuqiu', '08', '929', 'L', '120.369020', '22.342366'); +INSERT INTO `yoshop_region` VALUES ('3655', '3632', '车城', '车城乡', '中国,台湾省,屏东县,车城乡', '3', 'checheng', '08', '944', 'C', '120.710979', '22.072077'); +INSERT INTO `yoshop_region` VALUES ('3656', '3632', '满州', '满州乡', '中国,台湾省,屏东县,满州乡', '3', 'manzhou', '08', '947', 'M', '120.838843', '22.020853'); +INSERT INTO `yoshop_region` VALUES ('3657', '3632', '枋山', '枋山乡', '中国,台湾省,屏东县,枋山乡', '3', 'fangshan', '08', '941', null, '120.656356', '22.260338'); +INSERT INTO `yoshop_region` VALUES ('3658', '3632', '三地门', '三地门乡', '中国,台湾省,屏东县,三地门乡', '3', 'sandimen', '08', '901', 'S', '120.654486', '22.713877'); +INSERT INTO `yoshop_region` VALUES ('3659', '3632', '雾台', '雾台乡', '中国,台湾省,屏东县,雾台乡', '3', 'wutai', '08', '902', 'W', '120.732318', '22.744877'); +INSERT INTO `yoshop_region` VALUES ('3660', '3632', '玛家', '玛家乡', '中国,台湾省,屏东县,玛家乡', '3', 'majia', '08', '903', 'M', '120.644130', '22.706718'); +INSERT INTO `yoshop_region` VALUES ('3661', '3632', '泰武', '泰武乡', '中国,台湾省,屏东县,泰武乡', '3', 'taiwu', '08', '921', 'T', '120.632856', '22.591819'); +INSERT INTO `yoshop_region` VALUES ('3662', '3632', '来义', '来义乡', '中国,台湾省,屏东县,来义乡', '3', 'laiyi', '08', '922', 'L', '120.633601', '22.525866'); +INSERT INTO `yoshop_region` VALUES ('3663', '3632', '春日', '春日乡', '中国,台湾省,屏东县,春日乡', '3', 'chunri', '08', '942', 'C', '120.628793', '22.370672'); +INSERT INTO `yoshop_region` VALUES ('3664', '3632', '狮子', '狮子乡', '中国,台湾省,屏东县,狮子乡', '3', 'shizi', '08', '943', 'S', '120.704617', '22.201917'); +INSERT INTO `yoshop_region` VALUES ('3665', '3632', '牡丹', '牡丹乡', '中国,台湾省,屏东县,牡丹乡', '3', 'mudan', '08', '945', 'M', '120.770108', '22.125687'); +INSERT INTO `yoshop_region` VALUES ('3666', '3325', '台东', '台东县', '中国,台湾省,台东县', '2', 'taitung', '089', '9', 'T', '120.916000', '23.000000'); +INSERT INTO `yoshop_region` VALUES ('3667', '3666', '台东', '台东市', '中国,台湾省,台东县,台东市', '3', 'taidong', '089', '950', 'T', '121.145654', '22.756045'); +INSERT INTO `yoshop_region` VALUES ('3668', '3666', '成功', '成功镇', '中国,台湾省,台东县,成功镇', '3', 'chenggong', '089', '961', 'C', '121.379571', '23.100223'); +INSERT INTO `yoshop_region` VALUES ('3669', '3666', '关山', '关山镇', '中国,台湾省,台东县,关山镇', '3', 'guanshan', '089', '956', 'G', '121.163134', '23.047450'); +INSERT INTO `yoshop_region` VALUES ('3670', '3666', '卑南', '卑南乡', '中国,台湾省,台东县,卑南乡', '3', 'beinan', '089', '954', 'B', '121.083503', '22.786039'); +INSERT INTO `yoshop_region` VALUES ('3671', '3666', '鹿野', '鹿野乡', '中国,台湾省,台东县,鹿野乡', '3', 'luye', '089', '955', 'L', '121.135982', '22.913951'); +INSERT INTO `yoshop_region` VALUES ('3672', '3666', '池上', '池上乡', '中国,台湾省,台东县,池上乡', '3', 'chishang', '089', '958', 'C', '121.215139', '23.122393'); +INSERT INTO `yoshop_region` VALUES ('3673', '3666', '东河', '东河乡', '中国,台湾省,台东县,东河乡', '3', 'donghe', '089', '959', 'D', '121.300334', '22.969934'); +INSERT INTO `yoshop_region` VALUES ('3674', '3666', '长滨', '长滨乡', '中国,台湾省,台东县,长滨乡', '3', 'changbin', '089', '962', 'C', '121.451522', '23.315041'); +INSERT INTO `yoshop_region` VALUES ('3675', '3666', '太麻里', '太麻里乡', '中国,台湾省,台东县,太麻里乡', '3', 'taimali', '089', '963', 'T', '121.007394', '22.615383'); +INSERT INTO `yoshop_region` VALUES ('3676', '3666', '大武', '大武乡', '中国,台湾省,台东县,大武乡', '3', 'dawu', '089', '965', 'D', '120.889938', '22.339919'); +INSERT INTO `yoshop_region` VALUES ('3677', '3666', '绿岛', '绿岛乡', '中国,台湾省,台东县,绿岛乡', '3', 'lvdao', '089', '951', 'L', '121.492596', '22.661676'); +INSERT INTO `yoshop_region` VALUES ('3678', '3666', '海端', '海端乡', '中国,台湾省,台东县,海端乡', '3', 'haiduan', '089', '957', 'H', '121.172008', '23.101074'); +INSERT INTO `yoshop_region` VALUES ('3679', '3666', '延平', '延平乡', '中国,台湾省,台东县,延平乡', '3', 'yanping', '089', '953', 'Y', '121.084499', '22.902358'); +INSERT INTO `yoshop_region` VALUES ('3680', '3666', '金峰', '金峰乡', '中国,台湾省,台东县,金峰乡', '3', 'jinfeng', '089', '964', 'J', '120.971292', '22.595511'); +INSERT INTO `yoshop_region` VALUES ('3681', '3666', '达仁', '达仁乡', '中国,台湾省,台东县,达仁乡', '3', 'daren', '089', '966', 'D', '120.884131', '22.294869'); +INSERT INTO `yoshop_region` VALUES ('3682', '3666', '兰屿', '兰屿乡', '中国,台湾省,台东县,兰屿乡', '3', 'lanyu', '089', '952', 'L', '121.532473', '22.056736'); +INSERT INTO `yoshop_region` VALUES ('3683', '3325', '花莲', '花莲县', '中国,台湾省,花莲县', '2', 'hualien', '03', '9', 'H', '121.300000', '23.830000'); +INSERT INTO `yoshop_region` VALUES ('3684', '3683', '花莲', '花莲市', '中国,台湾省,花莲县,花莲市', '3', 'hualian', '03', '970', 'H', '121.606810', '23.982074'); +INSERT INTO `yoshop_region` VALUES ('3685', '3683', '凤林', '凤林镇', '中国,台湾省,花莲县,凤林镇', '3', 'fenglin', '03', '975', 'F', '121.451687', '23.744648'); +INSERT INTO `yoshop_region` VALUES ('3686', '3683', '玉里', '玉里镇', '中国,台湾省,花莲县,玉里镇', '3', 'yuli', '03', '981', 'Y', '121.316445', '23.336509'); +INSERT INTO `yoshop_region` VALUES ('3687', '3683', '新城', '新城乡', '中国,台湾省,花莲县,新城乡', '3', 'xincheng', '03', '971', 'X', '121.640512', '24.128133'); +INSERT INTO `yoshop_region` VALUES ('3688', '3683', '吉安', '吉安乡', '中国,台湾省,花莲县,吉安乡', '3', 'ji\'an', '03', '973', 'J', '121.568005', '23.961635'); +INSERT INTO `yoshop_region` VALUES ('3689', '3683', '寿丰', '寿丰乡', '中国,台湾省,花莲县,寿丰乡', '3', 'shoufeng', '03', '974', 'S', '121.508955', '23.870680'); +INSERT INTO `yoshop_region` VALUES ('3690', '3683', '光复', '光复乡', '中国,台湾省,花莲县,光复乡', '3', 'guangfu', '03', '976', 'G', '121.423496', '23.669084'); +INSERT INTO `yoshop_region` VALUES ('3691', '3683', '丰滨', '丰滨乡', '中国,台湾省,花莲县,丰滨乡', '3', 'fengbin', '03', '977', 'F', '121.518639', '23.597080'); +INSERT INTO `yoshop_region` VALUES ('3692', '3683', '瑞穗', '瑞穗乡', '中国,台湾省,花莲县,瑞穗乡', '3', 'ruisui', '03', '978', 'R', '121.375992', '23.496817'); +INSERT INTO `yoshop_region` VALUES ('3693', '3683', '富里', '富里乡', '中国,台湾省,花莲县,富里乡', '3', 'fuli', '03', '983', 'F', '121.250124', '23.179984'); +INSERT INTO `yoshop_region` VALUES ('3694', '3683', '秀林', '秀林乡', '中国,台湾省,花莲县,秀林乡', '3', 'xiulin', '03', '972', 'X', '121.620381', '24.116642'); +INSERT INTO `yoshop_region` VALUES ('3695', '3683', '万荣', '万荣乡', '中国,台湾省,花莲县,万荣乡', '3', 'wanrong', '03', '979', 'W', '121.407493', '23.715346'); +INSERT INTO `yoshop_region` VALUES ('3696', '3683', '卓溪', '卓溪乡', '中国,台湾省,花莲县,卓溪乡', '3', 'zhuoxi', '03', '982', 'Z', '121.303422', '23.346369'); +INSERT INTO `yoshop_region` VALUES ('3697', '3325', '澎湖', '澎湖县', '中国,台湾省,澎湖县', '2', 'penghu', '06', '8', 'P', '119.566417', '23.569733'); +INSERT INTO `yoshop_region` VALUES ('3698', '3697', '马公', '马公市', '中国,台湾省,澎湖县,马公市', '3', 'magong', '06', '880', 'M', '119.566499', '23.565845'); +INSERT INTO `yoshop_region` VALUES ('3699', '3697', '湖西', '湖西乡', '中国,台湾省,澎湖县,湖西乡', '3', 'huxi', '06', '885', 'H', '119.659666', '23.583358'); +INSERT INTO `yoshop_region` VALUES ('3700', '3697', '白沙', '白沙乡', '中国,台湾省,澎湖县,白沙乡', '3', 'baisha', '06', '884', 'B', '119.597919', '23.666060'); +INSERT INTO `yoshop_region` VALUES ('3701', '3697', '西屿', '西屿乡', '中国,台湾省,澎湖县,西屿乡', '3', 'xiyu', '06', '881', 'X', '119.506974', '23.600836'); +INSERT INTO `yoshop_region` VALUES ('3702', '3697', '望安', '望安乡', '中国,台湾省,澎湖县,望安乡', '3', 'wang\'an', '06', '882', 'W', '119.500538', '23.357531'); +INSERT INTO `yoshop_region` VALUES ('3703', '3697', '七美', '七美乡', '中国,台湾省,澎湖县,七美乡', '3', 'qimei', '06', '883', 'Q', '119.423929', '23.206018'); +INSERT INTO `yoshop_region` VALUES ('3704', '3325', '金门', '金门县', '中国,台湾省,金门县', '2', 'jinmen', '082', '8', 'J', '118.317089', '24.432706'); +INSERT INTO `yoshop_region` VALUES ('3705', '3704', '金城', '金城镇', '中国,台湾省,金门县,金城镇', '3', 'jincheng', '082', '893', 'J', '118.316667', '24.416667'); +INSERT INTO `yoshop_region` VALUES ('3706', '3704', '金湖', '金湖镇', '中国,台湾省,金门县,金湖镇', '3', 'jinhu', '082', '891', 'J', '118.419743', '24.438633'); +INSERT INTO `yoshop_region` VALUES ('3707', '3704', '金沙', '金沙镇', '中国,台湾省,金门县,金沙镇', '3', 'jinsha', '082', '890', 'J', '118.427993', '24.481109'); +INSERT INTO `yoshop_region` VALUES ('3708', '3704', '金宁', '金宁乡', '中国,台湾省,金门县,金宁乡', '3', 'jinning', '082', '892', 'J', '118.334506', '24.45672'); +INSERT INTO `yoshop_region` VALUES ('3709', '3704', '烈屿', '烈屿乡', '中国,台湾省,金门县,烈屿乡', '3', 'lieyu', '082', '894', 'L', '118.247255', '24.433102'); +INSERT INTO `yoshop_region` VALUES ('3710', '3704', '乌丘', '乌丘乡', '中国,台湾省,金门县,乌丘乡', '3', 'wuqiu', '082', '896', 'W', '118.319578', '24.435038'); +INSERT INTO `yoshop_region` VALUES ('3711', '3325', '连江', '连江县', '中国,台湾省,连江县', '2', 'lienchiang', '0836', '2', 'L', '119.539704', '26.197364'); +INSERT INTO `yoshop_region` VALUES ('3712', '3711', '南竿', '南竿乡', '中国,台湾省,连江县,南竿乡', '3', 'nangan', '0836', '209', 'N', '119.944267', '26.144035'); +INSERT INTO `yoshop_region` VALUES ('3713', '3711', '北竿', '北竿乡', '中国,台湾省,连江县,北竿乡', '3', 'beigan', '0836', '210', 'B', '120.000572', '26.221983'); +INSERT INTO `yoshop_region` VALUES ('3714', '3711', '莒光', '莒光乡', '中国,台湾省,连江县,莒光乡', '3', 'juguang', '0836', '211', null, '119.940405', '25.976256'); +INSERT INTO `yoshop_region` VALUES ('3715', '3711', '东引', '东引乡', '中国,台湾省,连江县,东引乡', '3', 'dongyin', '0836', '212', 'D', '120.493955', '26.366164'); +INSERT INTO `yoshop_region` VALUES ('3716', '0', '香港', '香港特别行政区', '中国,香港特别行政区', '1', 'hongkong', '', '', 'X', '114.173355', '22.320048'); +INSERT INTO `yoshop_region` VALUES ('3738', '0', '澳门', '澳门特别行政区', '中国,澳门特别行政区', '1', 'macau', '', '', 'A', '113.54909', '22.198951'); +INSERT INTO `yoshop_region` VALUES ('3739', '3738', '澳门半岛', '澳门半岛', '中国,澳门特别行政区,澳门半岛', '2', 'macaupeninsula', '00853', '999078', 'A', '113.549134', '22.198751'); +INSERT INTO `yoshop_region` VALUES ('3740', '3739', '花地玛堂区', '花地玛堂区', '中国,澳门特别行政区,澳门半岛,花地玛堂区', '3', 'nossasenhoradefatima', '00853', '999078', 'H', '113.552284', '22.208067'); +INSERT INTO `yoshop_region` VALUES ('3741', '3739', '圣安多尼堂区', '圣安多尼堂区', '中国,澳门特别行政区,澳门半岛,圣安多尼堂区', '3', 'santoantonio', '00853', '999078', 'S', '113.564301', '22.12381'); +INSERT INTO `yoshop_region` VALUES ('3742', '3739', '大堂', '大堂区', '中国,澳门特别行政区,澳门半岛,大堂区', '3', 'sé', '00853', '999078', 'D', '113.552971', '22.188359'); +INSERT INTO `yoshop_region` VALUES ('3743', '3739', '望德堂区', '望德堂区', '中国,澳门特别行政区,澳门半岛,望德堂区', '3', 'saolazaro', '00853', '999078', 'W', '113.550568', '22.194081'); +INSERT INTO `yoshop_region` VALUES ('3744', '3739', '风顺堂区', '风顺堂区', '中国,澳门特别行政区,澳门半岛,风顺堂区', '3', 'saolourenco', '00853', '999078', 'F', '113.541928', '22.187368'); +INSERT INTO `yoshop_region` VALUES ('3745', '3738', '氹仔岛', '氹仔岛', '中国,澳门特别行政区,氹仔岛', '2', 'taipa', '00853', '999078', null, '113.577669', '22.156838'); +INSERT INTO `yoshop_region` VALUES ('3746', '3745', '嘉模堂区', '嘉模堂区', '中国,澳门特别行政区,氹仔岛,嘉模堂区', '3', 'ourladyofcarmel\'sparish', '00853', '999078', 'J', '113.565303', '22.149029'); +INSERT INTO `yoshop_region` VALUES ('3747', '3738', '路环岛', '路环岛', '中国,澳门特别行政区,路环岛', '2', 'coloane', '00853', '999078', 'L', '113.564857', '22.116226'); +INSERT INTO `yoshop_region` VALUES ('3748', '3747', '圣方济各堂区', '圣方济各堂区', '中国,澳门特别行政区,路环岛,圣方济各堂区', '3', 'stfrancisxavier\'sparish', '00853', '999078', 'S', '113.559954', '22.123486'); +INSERT INTO `yoshop_region` VALUES ('3999', '3716', '香港', '香港特别行政区', '中国,香港特别行政区', '2', 'hongkong', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4000', '3999', '中西区', '中西区', '中国,香港特别行政区,中西区', '3', 'zhongxin', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4001', '3999', '东区', '东区', '中国,香港特别行政区,东区', '3', 'dong', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4002', '3999', '九龙城区', '九龙城区', '中国,香港特别行政区,九龙城区', '3', 'jiulong', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4003', '3999', '观塘区', '观塘区', '中国,香港特别行政区,观塘区', '3', 'guantang', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4004', '3999', '南区', '南区', '中国,香港特别行政区,南区', '3', 'nan', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4005', '3999', '深水埗区', '深水埗区', '中国,香港特别行政区,深水埗区', '3', 'shenshuibu', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4006', '3999', '湾仔区', '湾仔区', '中国,香港特别行政区,湾仔区', '3', 'wanzi', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4007', '3999', '黄大仙区', '黄大仙区', '中国,香港特别行政区,黄大仙区', '3', 'huangdaxian', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4008', '3999', '油尖旺区', '油尖旺区', '中国,香港特别行政区,油尖旺区', '3', 'youjianwang', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4009', '3999', '离岛区', '离岛区', '中国,香港特别行政区,离岛区', '3', 'lidao', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4010', '3999', '葵青区', '葵青区', '中国,香港特别行政区,葵青区', '3', 'kuiqing', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4011', '3999', '北区', '北区', '中国,香港特别行政区,北区', '3', 'bei', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4012', '3999', '西贡区', '西贡区', '中国,香港特别行政区,西贡区', '3', 'xigong', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4013', '3999', '沙田区', '沙田区', '中国,香港特别行政区,沙田区', '3', 'shatian', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4014', '3999', '屯门区', '屯门区', '中国,香港特别行政区,屯门区', '3', 'tunmen', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4015', '3999', '大埔区', '大埔区', '中国,香港特别行政区,大埔区', '3', 'dapu', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4016', '3999', '荃湾区', '荃湾区', '中国,香港特别行政区,荃湾区', '3', 'quanwan', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4017', '3999', '元朗区', '元朗区', '中国,香港特别行政区,元朗区', '3', 'yuanlang', null, null, null, null, null); + +CREATE TABLE `yoshop_setting` ( + `key` varchar(30) NOT NULL COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商城设置记录表'; + +INSERT INTO `yoshop_setting` VALUES ('storage', '上传设置', '{\"default\":\"local\",\"engine\":{\"qiniu\":{\"bucket\":\"\",\"access_key\":\"\",\"secret_key\":\"\",\"domain\":\"http:\\/\\/\"},\"aliyun\":{\"bucket\":\"\",\"access_key_id\":\"\",\"access_key_secret\":\"\",\"domain\":\"http:\\/\\/\"},\"qcloud\":{\"bucket\":\"\",\"region\":\"\",\"secret_id\":\"\",\"secret_key\":\"\",\"domain\":\"http:\\/\\/\"}}}', '10001', '1535945598'); + + +CREATE TABLE `yoshop_spec` ( + `spec_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '规格组id', + `spec_name` varchar(255) NOT NULL DEFAULT '' COMMENT '规格组名称', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`spec_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品规格组记录表'; + + +CREATE TABLE `yoshop_spec_value` ( + `spec_value_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '规格值id', + `spec_value` varchar(255) NOT NULL COMMENT '规格值', + `spec_id` int(11) NOT NULL COMMENT '规格组id', + `wxapp_id` int(11) NOT NULL COMMENT '小程序id', + `create_time` int(11) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`spec_value_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品规格值记录表'; + + +CREATE TABLE `yoshop_store_user` ( + `store_user_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名', + `password` varchar(255) NOT NULL DEFAULT '' COMMENT '登录密码', + `real_name` varchar(255) NOT NULL DEFAULT '' COMMENT '姓名', + `is_super` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否为超级管理员', + `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`store_user_id`), + KEY `user_name` (`user_name`) +) ENGINE=InnoDB AUTO_INCREMENT=10002 DEFAULT CHARSET=utf8 COMMENT='商家用户记录表'; + +INSERT INTO `yoshop_store_user` VALUES ('10001', 'admin', '9ae7b2e6f25c907a1fc81b503b16e25f', '管理员', '1', '0', '10001', '1529926348', '1531027042'); + + +CREATE TABLE `yoshop_upload_file` ( + `file_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '文件id', + `storage` varchar(20) NOT NULL DEFAULT '' COMMENT '存储方式', + `group_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文件分组id', + `file_url` varchar(255) NOT NULL DEFAULT '' COMMENT '存储域名', + `file_name` varchar(255) NOT NULL DEFAULT '' COMMENT '文件路径', + `file_size` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文件大小(字节)', + `file_type` varchar(20) NOT NULL DEFAULT '' COMMENT '文件类型', + `extension` varchar(20) NOT NULL DEFAULT '' COMMENT '文件扩展名', + `is_user` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否为c端用户上传', + `is_recycle` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否已回收', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '软删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`file_id`), + UNIQUE KEY `path_idx` (`file_name`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='文件库记录表'; + + +CREATE TABLE `yoshop_upload_group` ( + `group_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '分类id', + `group_type` varchar(10) NOT NULL DEFAULT '' COMMENT '文件类型', + `group_name` varchar(30) NOT NULL DEFAULT '' COMMENT '分类名称', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分类排序(数字越小越靠前)', + `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`group_id`), + KEY `type_index` (`group_type`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='文件库分组记录表'; + + +CREATE TABLE `yoshop_user` ( + `user_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户id', + `open_id` varchar(255) NOT NULL DEFAULT '' COMMENT '微信openid(唯一标示)', + `nickName` varchar(255) NOT NULL DEFAULT '' COMMENT '微信昵称', + `avatarUrl` varchar(255) NOT NULL DEFAULT '' COMMENT '微信头像', + `gender` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '性别', + `country` varchar(50) NOT NULL DEFAULT '' COMMENT '国家', + `province` varchar(50) NOT NULL DEFAULT '' COMMENT '省份', + `city` varchar(50) NOT NULL DEFAULT '' COMMENT '城市', + `address_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '默认收货地址', + `balance` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '用户可用余额', + `points` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户可用积分', + `pay_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '用户总支付的金额', + `expend_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际消费的金额(不含退款)', + `grade_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '会员等级id', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`user_id`), + KEY `openid` (`open_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户记录表'; + + +CREATE TABLE `yoshop_user_address` ( + `address_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `province_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在省份id', + `city_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在城市id', + `region_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在区id', + `district` varchar(255) NULL DEFAULT '' COMMENT '新市辖区(该字段用于记录region表中没有的市辖区)', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`address_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户收货地址表'; + + +CREATE TABLE `yoshop_user_coupon` ( + `user_coupon_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `coupon_id` int(11) unsigned NOT NULL COMMENT '优惠券id', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT '优惠券名称', + `color` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '优惠券颜色(10蓝 20红 30紫 40黄)', + `coupon_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '优惠券类型(10满减券 20折扣券)', + `reduce_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '满减券-减免金额', + `discount` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '折扣券-折扣率(0-100)', + `min_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '最低消费金额', + `expire_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '到期类型(10领取后生效 20固定时间)', + `expire_day` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '领取后生效-有效天数', + `start_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '有效期开始时间', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '有效期结束时间', + `apply_range` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '适用范围(10全部商品 20指定商品)', + `is_expire` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否过期(0未过期 1已过期)', + `is_use` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否已使用(0未使用 1已使用)', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`user_coupon_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户优惠券记录表'; + + +CREATE TABLE `yoshop_wxapp` ( + `wxapp_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '小程序id', + `app_id` varchar(50) NOT NULL DEFAULT '' COMMENT '小程序AppID', + `app_secret` varchar(50) NOT NULL DEFAULT '' COMMENT '小程序AppSecret', + `mchid` varchar(50) NOT NULL DEFAULT '' COMMENT '微信商户号id', + `apikey` varchar(255) NOT NULL DEFAULT '' COMMENT '微信支付密钥', + `cert_pem` longtext COMMENT '证书文件cert', + `key_pem` longtext COMMENT '证书文件key', + `is_recycle` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否回收', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`wxapp_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10002 DEFAULT CHARSET=utf8 COMMENT='微信小程序记录表'; + +INSERT INTO `yoshop_wxapp` VALUES ('10001', '', '', '', '', '', '', 0, 0, '1529926348', '1532766653'); + + +CREATE TABLE `yoshop_wxapp_help` ( + `help_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `title` varchar(255) NOT NULL DEFAULT '' COMMENT '帮助标题', + `content` text NOT NULL COMMENT '帮助内容', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序(数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`help_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10002 DEFAULT CHARSET=utf8 COMMENT='微信小程序帮助'; + +INSERT INTO `yoshop_wxapp_help` VALUES ('10001', '关于小程序', '小程序本身无需下载,无需注册,不占用手机内存,可以跨平台使用,响应迅速,体验接近原生APP。', '100', '10001', '1535942481', '1535942481'); + + + +CREATE TABLE `yoshop_wxapp_page` ( + `page_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '页面id', + `page_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '页面类型(10首页 20自定义页)', + `page_name` varchar(255) NOT NULL DEFAULT '' COMMENT '页面名称', + `page_data` longtext NOT NULL COMMENT '页面数据', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '微信小程序id', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '软删除', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`page_id`), + KEY `wxapp_id` (`wxapp_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10004 DEFAULT CHARSET=utf8 COMMENT='微信小程序diy页面表'; + +INSERT INTO `yoshop_wxapp_page` VALUES ('10001', '10', '小程序首页', '{\"items\":{\"page\":{\"id\":\"page\",\"type\":\"page\",\"name\":\"\\u9875\\u9762\\u8bbe\\u7f6e\",\"params\":{\"name\":\"\\u9875\\u9762\\u540d\\u79f0\",\"title\":\"\\u8424\\u706b\\u5c0f\\u7a0b\\u5e8f\\u5546\\u57ce\"},\"style\":{\"titleTextColor\":\"white\",\"titleBackgroundColor\":\"#ff8000\"}},\"n50214144672381\":{\"id\":\"n50214144672381\",\"type\":\"search\",\"name\":\"\\u641c\\u7d22\\u6846\",\"params\":{\"placeholder\":\"\\u8bf7\\u8f93\\u5165\\u5173\\u952e\\u5b57\\u8fdb\\u884c\\u641c\\u7d22\"},\"style\":{\"textAlign\":\"left\",\"searchStyle\":\"\"}},\"n33356112682143\":{\"id\":\"n33356112682143\",\"type\":\"coupon\",\"name\":\"\\u4f18\\u60e0\\u5238\\u7ec4\",\"style\":{\"paddingTop\":\"10\",\"background\":\"#ffffff\"},\"params\":{\"limit\":\"5\"},\"data\":{\"n214578430230592\":{\"color\":\"red\",\"reduce_price\":\"10\",\"min_price\":\"100.00\"},\"n818030369705776\":{\"color\":\"violet\",\"reduce_price\":\"10\",\"min_price\":\"100.00\"}}}}}', '10001', 0, '1536197290', '1536197290'); + + +# 微信小程序分类页模板表 +CREATE TABLE `yoshop_wxapp_category` ( + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `category_style` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '分类页样式(10一级分类[大图] 11一级分类[小图] 20二级分类)', + `share_title` varchar(100) NOT NULL DEFAULT '' COMMENT '分享标题', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信小程序分类页模板'; + +INSERT INTO `yoshop_wxapp_category` (`wxapp_id`, `category_style`, `share_title`, `create_time`, `update_time`) VALUES ('10001', '10', '', '1536373988', '1536375112'); + + +# 小程序prepay_id记录表 +CREATE TABLE `yoshop_wxapp_prepay_id` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_type` tinyint(3) unsigned NOT NULL DEFAULT 10 COMMENT '订单类型(10商城订单 20拼团订单)', + `prepay_id` varchar(50) NOT NULL DEFAULT '' COMMENT '微信支付prepay_id', + `can_use_times` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '可使用次数', + `used_times` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '已使用次数', + `pay_status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '支付状态(1已支付)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `expiry_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '过期时间', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `order_id` (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='小程序prepay_id记录'; + + +# 分销商申请记录表 +CREATE TABLE `yoshop_dealer_apply` ( + `apply_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `real_name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名', + `mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号', + `referee_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '推荐人用户id', + `apply_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '申请方式(10需后台审核 20无需审核)', + `apply_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '申请时间', + `apply_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '审核状态 (10待审核 20审核通过 30驳回)', + `audit_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '审核时间', + `reject_reason` varchar(500) NOT NULL DEFAULT '' COMMENT '驳回原因', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`apply_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商申请记录表'; + + +# 分销商资金明细表 +CREATE TABLE `yoshop_dealer_capital` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id', + `flow_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '资金流动类型 (10佣金收入 20提现支出)', + `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '金额', + `describe` varchar(500) NOT NULL DEFAULT '' COMMENT '描述', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商资金明细表'; + + +# 销商订单记录表 +CREATE TABLE `yoshop_dealer_order` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id (买家)', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_type` TINYINT (3) UNSIGNED NOT NULL DEFAULT '10' COMMENT '订单类型(10商城订单 20拼团订单)', + `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号(废弃,勿用)', + `order_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '订单总金额(不含运费)', + `first_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id(一级)', + `second_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id(二级)', + `third_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id(三级)', + `first_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)', + `second_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)', + `third_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)', + `is_invalid` tinyint(3) NOT NULL DEFAULT 0 COMMENT '订单是否失效(0未失效 1已失效)', + `is_settled` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否已结算佣金(0未结算 1已结算)', + `settle_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '结算时间', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `order_id` (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商订单记录表'; + + +# 分销商推荐关系表 +CREATE TABLE `yoshop_dealer_referee` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `dealer_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id(被推荐人)', + `level` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '推荐关系层级(1,2,3)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `dealer_id` (`dealer_id`), + KEY `user_id` (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商推荐关系表'; + + +# 分销商设置表 +CREATE TABLE `yoshop_dealer_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分销商设置表'; + + +# 分销商用户记录表 +CREATE TABLE `yoshop_dealer_user` ( + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id', + `real_name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名', + `mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号', + `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '当前可提现佣金', + `freeze_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '已冻结佣金', + `total_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '累积提现佣金', + `referee_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '推荐人用户id', + `first_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成员数量(一级)', + `second_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成员数量(二级)', + `third_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成员数量(三级)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分销商用户记录表'; + + +# 分销商提现明细表 +CREATE TABLE `yoshop_dealer_withdraw` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id', + `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '提现金额', + `pay_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '打款方式 (10微信 20支付宝 30银行卡)', + `alipay_name` varchar(30) NOT NULL DEFAULT '' COMMENT '支付宝姓名', + `alipay_account` varchar(30) NOT NULL DEFAULT '' COMMENT '支付宝账号', + `bank_name` varchar(30) NOT NULL DEFAULT '' COMMENT '开户行名称', + `bank_account` varchar(30) NOT NULL DEFAULT '' COMMENT '银行开户名', + `bank_card` varchar(30) NOT NULL DEFAULT '' COMMENT '银行卡号', + `apply_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '申请状态 (10待审核 20审核通过 30驳回 40已打款)', + `audit_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '审核时间', + `reject_reason` varchar(500) NOT NULL DEFAULT '' COMMENT '驳回原因', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商提现明细表'; + + +# 小程序form_id记录表 +CREATE TABLE `yoshop_wxapp_formid` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `form_id` varchar(50) NOT NULL DEFAULT '' COMMENT '小程序form_id', + `expiry_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '过期时间', + `is_used` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否已使用', + `used_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '使用时间', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='小程序form_id记录表'; + + +# 超管用户记录表 +CREATE TABLE `yoshop_admin_user` ( + `admin_user_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名', + `password` varchar(255) NOT NULL DEFAULT '' COMMENT '登录密码', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`admin_user_id`), + KEY `user_name` (`user_name`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='超管用户记录表'; + +INSERT INTO `yoshop_admin_user` (`user_name`, `password`, `create_time`, `update_time`) VALUES ('admin', '9ae7b2e6f25c907a1fc81b503b16e25f', '1529926348', '1540194026'); + + +# 商家用户权限表 +CREATE TABLE `yoshop_store_access` ( + `access_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT '权限名称', + `url` varchar(255) NOT NULL DEFAULT '' COMMENT '权限url', + `parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父级id', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '100' COMMENT '排序(数字越小越靠前)', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`access_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10114 DEFAULT CHARSET=utf8 COMMENT='商家用户权限表'; + +INSERT INTO `yoshop_store_access` VALUES ('10001', '首页', 'index/index', '0', '100', '1540628721', '1540781975'); +INSERT INTO `yoshop_store_access` VALUES ('10002', '管理员', 'store', '0', '105', '1540628721', '1552635011'); +INSERT INTO `yoshop_store_access` VALUES ('10003', '管理员管理', 'store.user', '10002', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10004', '管理员列表', 'store.user/index', '10003', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10005', '添加管理员', 'store.user/add', '10003', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10006', '编辑管理员', 'store.user/edit', '10003', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10007', '删除管理员', 'store.user/delete', '10003', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10008', '角色管理', 'store.role', '10002', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10009', '角色列表', 'store.role/index', '10008', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10010', '添加角色', 'store.role/add', '10008', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10011', '编辑角色', 'store.role/edit', '10008', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10012', '删除角色', 'store.role/delete', '10008', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10018', '商品管理', 'goods', '0', '110', '1540628721', '1552635017'); +INSERT INTO `yoshop_store_access` VALUES ('10019', '商品管理', 'goods', '10018', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10020', '商品列表', 'goods/index', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10021', '添加商品', 'goods/add', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10022', '编辑商品', 'goods/edit', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10023', '复制商品', 'goods/copy', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10024', '删除商品', 'goods/delete', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10025', '商品上下架', 'goods/state', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10026', '商品分类', 'goods.category', '10018', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10027', '分类列表', 'goods.category/index', '10026', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10028', '添加分类', 'goods.category/add', '10026', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10029', '编辑分类', 'goods.category/edit', '10026', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10030', '删除分类', 'goods.category/delete', '10026', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10031', '商品评价', 'goods.comment', '10018', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10032', '评价列表', 'goods.comment/index', '10031', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10033', '评价详情', 'goods.comment/detail', '10031', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10034', '删除评价', 'goods.comment/delete', '10031', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10035', '订单管理', 'order', '0', '115', '1540628721', '1552635035'); +INSERT INTO `yoshop_store_access` VALUES ('10036', '订单列表', '', '10035', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10037', '待发货', 'order/delivery_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10038', '待收货', 'order/receipt_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10039', '待付款', 'order/pay_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10040', '已完成', 'order/complete_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10041', '已取消', 'order/cancel_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10042', '全部订单', 'order/all_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10043', '订单详情', '', '10035', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10044', '详情信息', 'order/detail', '10043', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10045', '确认发货', 'order/delivery', '10043', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10046', '修改订单价格', 'order/updateprice', '10043', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10047', '订单导出', 'order.operate/export', '10035', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10048', '批量发货', 'order.operate/batchdelivery', '10035', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10049', '用户管理', 'user', '0', '120', '1540628721', '1552635042'); +INSERT INTO `yoshop_store_access` VALUES ('10050', '用户列表', 'user/index', '10049', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10051', '删除用户', 'user/delete', '10049', '105', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10052', '营销管理', 'market', '0', '135', '1540628721', '1552635080'); +INSERT INTO `yoshop_store_access` VALUES ('10053', '优惠券', 'coupon', '10052', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10054', '优惠券列表', 'market.coupon/index', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10055', '新增优惠券', 'market.coupon/add', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10056', '编辑优惠券', 'market.coupon/edit', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10057', '删除优惠券', 'market.coupon/delete', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10058', '领取记录', 'market.coupon/receive', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10059', '小程序', 'wxapp', '0', '140', '1540628721', '1552635087'); +INSERT INTO `yoshop_store_access` VALUES ('10060', '小程序设置', 'wxapp/setting', '10059', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10061', '页面管理', 'wxapp.page', '10059', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10062', '页面设计', '', '10061', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10063', '页面列表', 'wxapp.page/index', '10062', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10064', '新增页面', 'wxapp.page/add', '10062', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10065', '编辑页面', 'wxapp.page/edit', '10062', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10066', '设为首页', 'wxapp.page/sethome', '10062', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10067', '分类页模板', 'wxapp.page/category', '10061', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10068', '页面链接', 'wxapp.page/links', '10061', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10069', '帮助中心', 'wxapp.help', '10059', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10070', '帮助列表', 'wxapp.help/index', '10069', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10071', '新增帮助', 'wxapp.help/add', '10069', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10072', '编辑帮助', 'wxapp.help/edit', '10069', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10073', '删除帮助', 'wxapp.help/delete', '10069', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10074', '应用中心', 'apps', '0', '145', '1540628721', '1552635094'); +INSERT INTO `yoshop_store_access` VALUES ('10075', '分销中心', 'apps.dealer', '10074', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10076', '入驻申请', 'apps.dealer.apply', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10077', '申请列表', 'apps.dealer.apply/index', '10076', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10078', '分销商审核', 'apps.dealer.apply/submit', '10076', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10079', '分销商用户', 'apps.dealer.user', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10080', '分销商列表', 'apps.dealer.user/index', '10079', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10081', '删除分销商', 'apps.dealer.user/delete', '10079', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10082', '分销商二维码', 'apps.dealer.user/qrcode', '10079', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10083', '分销订单', 'apps.dealer.order/index', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10084', '提现申请', 'apps.dealer.withdraw', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10085', '申请列表', 'apps.dealer.withdraw/index', '10084', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10086', '提现审核', 'apps.dealer.withdraw/submit', '10084', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10087', '确认打款', 'apps.dealer.withdraw/money', '10084', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10088', '分销设置', 'apps.dealer.setting/index', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10089', '分销海报', 'apps.dealer.setting/qrcode', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10090', '设置', 'setting', '0', '150', '1540628721', '1552635100'); +INSERT INTO `yoshop_store_access` VALUES ('10091', '商城设置', 'setting/store', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10092', '交易设置', 'setting/trade', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10093', '运费模板', 'setting.delivery', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10094', '运费模板列表', 'setting.delivery/index', '10093', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10095', '新增运费模板', 'setting.delivery/add', '10093', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10096', '编辑运费模板', 'setting.delivery/edit', '10093', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10097', '删除运费模板', 'setting.delivery/delete', '10093', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10098', '物流公司', 'setting.express', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10099', '物流公司列表', 'setting.express/index', '10098', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10100', '新增物流公司', 'setting.express/add', '10098', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10101', '编辑物流公司', 'setting.express/edit', '10098', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10102', '删除物流公司', 'setting.express/delete', '10098', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10103', '短信通知', 'setting/sms', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10104', '模板消息', 'setting/tplmsg', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10105', '上传设置', 'setting/storage', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10106', '其他', '', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10107', '清理缓存', 'setting.cache/clear', '10106', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10108', '审核用户取消申请', 'order.operate/confirmcancel', '10043', '100', '1542163260', '1542163260'); +INSERT INTO `yoshop_store_access` VALUES ('10109', '售后管理', 'order.refund', '10035', '100', '1542161684', '1542161684'); +INSERT INTO `yoshop_store_access` VALUES ('10110', '售后列表', 'order.refund/index', '10109', '100', '1542161714', '1542161714'); +INSERT INTO `yoshop_store_access` VALUES ('10111', '售后详情', 'order.refund/detail', '10109', '100', '1542161736', '1542161736'); +INSERT INTO `yoshop_store_access` VALUES ('10112', '审核售后单', 'order.refund/audit', '10109', '100', '1542162196', '1542162196'); +INSERT INTO `yoshop_store_access` VALUES ('10113', '确认收货并退款', 'order.refund/receipt', '10109', '100', '1542162231', '1542162231'); +INSERT INTO `yoshop_store_access` VALUES ('10114', '退货地址管理', 'setting.address', '10090', '100', '1542609573', '1542609573'); +INSERT INTO `yoshop_store_access` VALUES ('10115', '退货地址列表', 'setting.address/index', '10114', '100', '1542609598', '1542609598'); +INSERT INTO `yoshop_store_access` VALUES ('10116', '新增退货地址', 'setting.address/add', '10114', '100', '1542609630', '1542609630'); +INSERT INTO `yoshop_store_access` VALUES ('10117', '编辑退货地址', 'setting.address/edit', '10114', '100', '1542609656', '1542609656'); +INSERT INTO `yoshop_store_access` VALUES ('10118', '删除退货地址', 'setting.address/delete', '10114', '100', '1542609680', '1542609680'); +INSERT INTO `yoshop_store_access` VALUES ('10300', '拼团管理', 'apps.sharing', '10074', '100', '1544601161', '1544601161'); +INSERT INTO `yoshop_store_access` VALUES ('10301', '商品分类', 'apps.sharing.category', '10300', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10302', '分类列表', 'apps.sharing.category/index', '10301', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10303', '添加分类', 'apps.sharing.category/add', '10301', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10304', '编辑分类', 'apps.sharing.category/edit', '10301', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10305', '删除分类', 'apps.sharing.category/delete', '10301', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10306', '商品管理', 'apps.sharing.goods', '10300', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10307', '商品列表', 'apps.sharing.goods/index', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10308', '添加商品', 'apps.sharing.goods/add', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10309', '编辑商品', 'apps.sharing.goods/edit', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10310', '复制商品', 'apps.sharing.goods/copy', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10311', '删除商品', 'apps.sharing.goods/delete', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10312', '商品上下架', 'apps.sharing.goods/state', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10313', '拼单管理', 'apps.sharing.active', '10300', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10314', '拼单列表', 'apps.sharing.active/index', '10313', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10315', '拼单成员列表', 'apps.sharing.active/users', '10313', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10316', '订单管理', 'apps.sharing.order', '10300', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10317', '订单列表', 'apps.sharing.order/index', '10316', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10318', '订单详情', '', '10316', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10319', '详情信息', 'apps.sharing.order/detail', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10320', '确认发货', 'apps.sharing.order/delivery', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10321', '修改订单价格', 'apps.sharing.order/updateprice', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10322', '审核用户取消订单', 'apps.sharing.order/confirmcancel', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10323', '拼团失败手动退款', 'apps.sharing.order/refund', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10324', '订单导出', 'apps.sharing.order.operate/export', '10316', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10325', '批量发货', 'apps.sharing.order.operate/batchdelivery', '10316', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10326', '售后管理', 'apps.sharing.order.refund', '10300', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10327', '售后列表', 'apps.sharing.order.refund/index', '10326', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10328', '售后详情', 'apps.sharing.order.refund/detail', '10326', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10329', '审核售后单', 'apps.sharing.order.refund/audit', '10326', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10330', '确认收货并退款', 'apps.sharing.order.refund/receipt', '10326', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10331', '商品评价', 'apps.sharing.comment', '10300', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10332', '评价列表', 'apps.sharing.comment/index', '10331', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10333', '评价详情', 'apps.sharing.comment/detail', '10331', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10334', '删除评价', 'apps.sharing.comment/delete', '10331', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10335', '拼团设置', 'apps.sharing.setting/index', '10300', '105', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10336', '下级用户列表', 'apps.dealer.user/fans', '10079', '100', '1545189676', '1545189676'); +INSERT INTO `yoshop_store_access` VALUES ('10337', '内容管理', 'content', '0', '130', '1547018818', '1552635075'); +INSERT INTO `yoshop_store_access` VALUES ('10338', '文章管理', 'content.article', '10337', '100', '1547018849', '1547018869'); +INSERT INTO `yoshop_store_access` VALUES ('10339', '文章列表', 'content.article/index', '10338', '100', '1547018885', '1547018885'); +INSERT INTO `yoshop_store_access` VALUES ('10340', '添加文章', 'content.article/add', '10338', '100', '1547018901', '1547018901'); +INSERT INTO `yoshop_store_access` VALUES ('10341', '编辑文章', 'content.article/edit', '10338', '100', '1547018922', '1547018922'); +INSERT INTO `yoshop_store_access` VALUES ('10342', '删除文章', 'content.article/delete', '10338', '100', '1547018937', '1547018937'); +INSERT INTO `yoshop_store_access` VALUES ('10343', '文章分类', 'content.article.category', '10337', '100', '1547018972', '1547018972'); +INSERT INTO `yoshop_store_access` VALUES ('10344', '分类列表', 'content.article.category/index', '10343', '100', '1547018992', '1547018992'); +INSERT INTO `yoshop_store_access` VALUES ('10345', '添加分类', 'content.article.category/add', '10343', '100', '1547019008', '1547019017'); +INSERT INTO `yoshop_store_access` VALUES ('10346', '编辑分类', 'content.article.category/edit', '10343', '100', '1547019008', '1547019017'); +INSERT INTO `yoshop_store_access` VALUES ('10347', '删除分类', 'content.article.category/delete', '10343', '100', '1547019008', '1547019017'); +INSERT INTO `yoshop_store_access` VALUES ('10348', '微信付款', 'apps.dealer.withdraw/wechat_pay', '10084', '100', '1548232045', '1548232045'); +INSERT INTO `yoshop_store_access` VALUES ('10349', '小票打印机', 'setting.printer', '10090', '100', '1548738285', '1548738285'); +INSERT INTO `yoshop_store_access` VALUES ('10350', '打印机管理', 'setting.printer', '10349', '100', '1548738718', '1548738718'); +INSERT INTO `yoshop_store_access` VALUES ('10351', '小票打印设置', 'setting/printer', '10349', '100', '1548738720', '1548738720'); +INSERT INTO `yoshop_store_access` VALUES ('10352', '小票打印机列表', 'setting.printer/index', '10350', '100', '1548738420', '1548738420'); +INSERT INTO `yoshop_store_access` VALUES ('10353', '新增小票打印机', 'setting.printer/add', '10350', '100', '1548738443', '1548738443'); +INSERT INTO `yoshop_store_access` VALUES ('10354', '编辑小票打印机', 'setting.printer/edit', '10350', '100', '1548738443', '1548738443'); +INSERT INTO `yoshop_store_access` VALUES ('10355', '删除小票打印机', 'setting.printer/delete', '10350', '100', '1548738443', '1548738443'); +INSERT INTO `yoshop_store_access` VALUES ('10356', '门店管理', 'shop', '0', '125', '1551504862', '1552635061'); +INSERT INTO `yoshop_store_access` VALUES ('10357', '门店管理', 'shop', '10356', '105', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` VALUES ('10358', '门店列表', 'shop/index', '10357', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` VALUES ('10359', '添加门店', 'shop/add', '10357', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` VALUES ('10360', '编辑门店', 'shop/edit', '10357', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` VALUES ('10361', '删除门店', 'shop/delete', '10357', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` VALUES ('10362', '店员管理', 'shop.clerk', '10356', '110', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` VALUES ('10363', '店员列表', 'shop.clerk/index', '10362', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` VALUES ('10364', '添加店员', 'shop.clerk/add', '10362', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` VALUES ('10365', '编辑店员', 'shop.clerk/edit', '10362', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` VALUES ('10366', '删除店员', 'shop.clerk/delete', '10362', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` VALUES ('10367', '订单核销记录', 'shop.order/index', '10356', '115', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` VALUES ('10368', '门店自提核销', 'order.operate/extract', '10043', '100', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` VALUES ('10369', '门店自提核销', 'apps.sharing.order.operate/extract', '10318', '100', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` VALUES ('10370', '满额包邮', 'market.basic/full_free', '10052', '125', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` VALUES ('10375', '文件库管理', 'content.files.group', '10337', '105', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10376', '文件分组', 'content.files.group', '10375', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10377', '分组列表', 'content.files.group/index', '10376', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10378', '添加分组', 'content.files.group/add', '10376', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10379', '编辑分组', 'content.files.group/edit', '10376', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10380', '删除分组', 'content.files.group/delete', '10376', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10381', '文件管理', 'content.files.group', '10375', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10382', '文件列表', 'content.files.group/index', '10381', '105', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10383', '回收站列表', 'content.files.group/recycle', '10381', '110', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10384', '移入回收站', 'content.files.group/add', '10381', '115', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10385', '回收站还原', 'content.files.group/edit', '10381', '120', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10386', '删除文件', 'content.files.group/delete', '10381', '125', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` VALUES ('10387', '余额记录', 'user.balance', '10049', '125', '1554685953', '1554685965'); +INSERT INTO `yoshop_store_access` VALUES ('10388', '充值记录', 'user.recharge/order', '10387', '100', '1554686010', '1554686010'); +INSERT INTO `yoshop_store_access` VALUES ('10389', '余额明细', 'user.balance/log', '10387', '105', '1554686031', '1554686031'); +INSERT INTO `yoshop_store_access` VALUES ('10390', '用户充值', 'market.recharge', '10052', '110', '1554686283', '1554686339'); +INSERT INTO `yoshop_store_access` VALUES ('10391', '充值套餐', 'market.recharge.plan', '10390', '100', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` VALUES ('10392', '套餐列表', 'market.recharge.plan/index', '10391', '100', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` VALUES ('10393', '添加套餐', 'market.recharge.plan/add', '10391', '105', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` VALUES ('10394', '编辑套餐', 'market.recharge.plan/edit', '10391', '110', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` VALUES ('10395', '删除套餐', 'market.recharge.plan/delete', '10391', '115', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` VALUES ('10396', '充值设置', 'market.recharge/setting', '10390', '105', '1554686647', '1554686647'); +INSERT INTO `yoshop_store_access` VALUES ('10400', '好物圈', 'apps.wow', '10074', '110', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10401', '商品收藏', 'apps.wow.shoping', '10400', '100', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10402', '订单信息', 'apps.wow.order', '10400', '105', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10403', '基础设置', 'apps.wow.setting/index', '10400', '110', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10404', '商品收藏记录', 'apps.wow.shoping/index', '10401', '100', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10405', '取消同步', 'apps.wow.shoping/delete', '10401', '105', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10406', '订单同步记录', 'apps.wow.order/index', '10402', '100', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10407', '取消同步', 'apps.wow.order/delete', '10402', '105', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10408', '用户充值', 'user/recharge', '10049', '110', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` VALUES ('10411', '修改会员等级', 'user/grade', '10049', '115', '1558317213', '1558317226'); +INSERT INTO `yoshop_store_access` VALUES ('10412', '会员等级管理', 'user.grade', '10049', '120', '1558317440', '1558317440'); +INSERT INTO `yoshop_store_access` VALUES ('10413', '会员等级列表', 'user.grade/index', '10412', '100', '1558317464', '1558317464'); +INSERT INTO `yoshop_store_access` VALUES ('10414', '新增等级', 'user.grade/add', '10412', '105', '1558317464', '1558317464'); +INSERT INTO `yoshop_store_access` VALUES ('10415', '编辑等级', 'user.grade/edit', '10412', '110', '1558317464', '1558317464'); +INSERT INTO `yoshop_store_access` VALUES ('10416', '删除等级', 'user.grade/delete', '10412', '115', '1558317464', '1558317464'); + +INSERT INTO `yoshop_store_access` VALUES ('10426', '砍价活动', 'apps.bargain', '10074', '100', '1559615418', '1559615441'); +INSERT INTO `yoshop_store_access` VALUES ('10427', '砍价活动管理', 'apps.bargain.active', '10426', '100', '1559615566', '1559615566'); +INSERT INTO `yoshop_store_access` VALUES ('10428', '砍价活动列表', 'apps.bargain.active/index', '10427', '100', '1559615601', '1559615601'); +INSERT INTO `yoshop_store_access` VALUES ('10429', '新增砍价活动', 'apps.bargain.active/add', '10427', '105', '1559615601', '1559615601'); +INSERT INTO `yoshop_store_access` VALUES ('10430', '编辑砍价活动', 'apps.bargain.active/edit', '10427', '110', '1559615601', '1559615601'); +INSERT INTO `yoshop_store_access` VALUES ('10431', '删除砍价活动', 'apps.bargain.active/delete', '10427', '115', '1559615601', '1559615601'); +INSERT INTO `yoshop_store_access` VALUES ('10432', '砍价记录', 'apps.bargain.task', '10426', '105', '1559615788', '1559615788'); +INSERT INTO `yoshop_store_access` VALUES ('10433', '砍价记录列表', 'apps.bargain.task/index', '10432', '100', '1559615815', '1559615815'); +INSERT INTO `yoshop_store_access` VALUES ('10434', '砍价助力榜', 'apps.bargain.task/help', '10432', '105', '1559615850', '1559615850'); +INSERT INTO `yoshop_store_access` VALUES ('10435', '删除砍价记录', 'apps.bargain.task/delete', '10432', '110', '1559615878', '1559615878'); +INSERT INTO `yoshop_store_access` VALUES ('10436', '砍价设置', 'apps.bargain.setting/index', '10426', '110', '1559615946', '1559615979'); + +INSERT INTO `yoshop_store_access` VALUES ('10437', '复制主商城商品', 'apps.sharing.goods/copy_master', '10306', '112', '1559615946', '1559615979'); + + +UPDATE `yoshop_store_access` SET `sort`='105' WHERE (`access_id`='10021'); +UPDATE `yoshop_store_access` SET `sort`='110' WHERE (`access_id`='10022'); +UPDATE `yoshop_store_access` SET `sort`='115' WHERE (`access_id`='10023'); +UPDATE `yoshop_store_access` SET `sort`='120' WHERE (`access_id`='10024'); +UPDATE `yoshop_store_access` SET `sort`='125' WHERE (`access_id`='10025'); + +UPDATE `yoshop_store_access` SET `sort`='105' WHERE (`access_id`='10307'); +UPDATE `yoshop_store_access` SET `sort`='110' WHERE (`access_id`='10308'); +UPDATE `yoshop_store_access` SET `sort`='115' WHERE (`access_id`='10309'); +UPDATE `yoshop_store_access` SET `sort`='120' WHERE (`access_id`='10310'); +UPDATE `yoshop_store_access` SET `sort`='125' WHERE (`access_id`='10311'); + +INSERT INTO `yoshop_store_access` VALUES ('10443', '活跃用户', 'market.push/user', '10441', '105', '1561080384', '1561080384'); +INSERT INTO `yoshop_store_access` VALUES ('10442', '发送消息', 'market.push/send', '10441', '100', '1561080384', '1561080384'); +INSERT INTO `yoshop_store_access` VALUES ('10441', '消息推送', 'market.push', '10052', '120', '1561080292', '1561080292'); +INSERT INTO `yoshop_store_access` VALUES ('10440', '积分明细', 'market.points/log', '10438', '105', '1561080384', '1561080384'); +INSERT INTO `yoshop_store_access` VALUES ('10439', '积分设置', 'market.points/setting', '10438', '100', '1561080384', '1561080384'); +INSERT INTO `yoshop_store_access` VALUES ('10438', '积分管理', 'market.points', '10052', '115', '1561080292', '1561080292'); + +INSERT INTO `yoshop_store_access` VALUES ('11003', '数据统计', 'statistics.data/index', '0', '138', '1572507520', '1572507520'); + + + +# 商家用户角色表 +CREATE TABLE `yoshop_store_role` ( + `role_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '角色id', + `role_name` varchar(50) NOT NULL DEFAULT '' COMMENT '角色名称', + `parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父级角色id', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '100' COMMENT '排序(数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`role_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家用户角色表'; + + +# 商家用户角色权限关系表 +CREATE TABLE `yoshop_store_role_access` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `role_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '角色id', + `access_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '权限id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `role_id` (`role_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家用户角色权限关系表'; + + +# 商家用户角色记录表 +CREATE TABLE `yoshop_store_user_role` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `store_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '超管用户id', + `role_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '角色id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `admin_user_id` (`store_user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家用户角色记录表'; + + +# 退货地址记录表 +CREATE TABLE `yoshop_return_address` ( + `address_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '退货地址id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序 (数字越小越靠前)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`address_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='退货地址记录表'; + + +# 售后单记录表 +CREATE TABLE `yoshop_order_refund` ( + `order_refund_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '售后单id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单商品id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '售后类型(10退货退款 20换货)', + `apply_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '用户申请原因(说明)', + `is_agree` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商家审核状态(0待审核 10已同意 20已拒绝)', + `refuse_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '商家拒绝原因(说明)', + `refund_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际退款金额', + `is_user_send` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '用户是否发货(0未发货 1已发货)', + `send_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户发货时间', + `express_id` varchar(32) NOT NULL DEFAULT '' COMMENT '用户发货物流公司id', + `express_no` varchar(32) NOT NULL DEFAULT '' COMMENT '用户发货物流单号', + `is_receipt` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商家收货状态(0未收货 1已收货)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '售后单状态(0进行中 10已拒绝 20已完成 30已取消)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_refund_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='售后单记录表'; + + +# 售后单退货地址记录表 +CREATE TABLE `yoshop_order_refund_address` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_refund_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '售后单id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='售后单退货地址记录表'; + + +# 售后单图片记录表 +CREATE TABLE `yoshop_order_refund_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_refund_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '售后单id', + `image_id` int(11) NOT NULL DEFAULT '0' COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='售后单图片记录表'; + + +# 新增:拼团拼单记录表 +CREATE TABLE `yoshop_sharing_active` ( + `active_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '拼单id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团商品id', + `people` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成团人数', + `actual_people` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '当前已拼人数', + `creator_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '团长用户id', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼单结束时间', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '拼单状态(0未拼单 10拼单中 20拼单成功 30拼单失败)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`active_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团拼单记录表'; + + +# 新增:拼团拼单成员记录表 +CREATE TABLE `yoshop_sharing_active_users` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼单id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `is_creator` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否为创建者', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团拼单成员记录表'; + + +# 新增:拼团商品分类表 +CREATE TABLE `yoshop_sharing_category` ( + `category_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品分类id', + `name` varchar(50) NOT NULL DEFAULT '' COMMENT '分类名称', + `parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '上级分类id', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分类图片id', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序方式(数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品分类表'; + + +# 新增:拼团商品评价表 +CREATE TABLE `yoshop_sharing_comment` ( + `comment_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '评价id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团商品id', + `order_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单商品id', + `score` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '评分(10好评 20中评 30差评)', + `content` text NOT NULL COMMENT '评价内容', + `is_picture` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否为图片评价', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '评价排序', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0隐藏 1显示)', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `is_delete` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '软删除', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`comment_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品评价表'; + + +# 新增:拼团评价图片记录表 +CREATE TABLE `yoshop_sharing_comment_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `comment_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '评价id', + `image_id` int(11) NOT NULL DEFAULT '0' COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团评价图片记录表'; + + +# 新增:拼团商品记录表 +CREATE TABLE `yoshop_sharing_goods` ( + `goods_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '拼团商品id', + `goods_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称', + `category_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品分类id', + `selling_point` varchar(500) NOT NULL DEFAULT '' COMMENT '商品卖点', + `people` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '成团人数', + `group_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成团有效时间(单位:小时)', + `is_alone` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否允许单买(0不允许 1允许)', + `spec_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商品规格(10单规格 20多规格)', + `deduct_stock_type` tinyint(3) unsigned NOT NULL DEFAULT '20' COMMENT '库存计算方式(10下单减库存 20付款减库存)', + `content` longtext NOT NULL COMMENT '商品详情', + `sales_initial` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '初始销量', + `sales_actual` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '实际销量', + `goods_sort` int(11) unsigned NOT NULL DEFAULT '100' COMMENT '商品排序(数字越小越靠前)', + `delivery_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '配送模板id', + `is_points_gift` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否开启积分赠送(1开启 0关闭)', + `is_points_discount` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否允许使用积分抵扣(1允许 0不允许)', + `is_enable_grade` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否开启会员折扣(1开启 0关闭)', + `is_alone_grade` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '会员折扣设置(0默认等级折扣 1单独设置折扣)', + `alone_grade_equity` text COMMENT '单独设置折扣的配置', + `is_ind_dealer` TINYINT (3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启单独分销(0关闭 1开启)', + `dealer_money_type` TINYINT (3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '分销佣金类型(10百分比 20固定金额)', + `first_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)', + `second_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)', + `third_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)', + `goods_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '商品状态(10上架 20下架)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`goods_id`), + KEY `category_id` (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品记录表'; + + +# 新增:商品图片记录表 +CREATE TABLE `yoshop_sharing_goods_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `image_id` int(11) NOT NULL COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品图片记录表'; + + +# 新增:拼团商品规格表 +CREATE TABLE `yoshop_sharing_goods_sku` ( + `goods_sku_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品规格id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '0' COMMENT '商品sku记录索引(由规格id组成)', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格图片id', + `goods_no` varchar(100) NOT NULL DEFAULT '' COMMENT '商品编码', + `sharing_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '拼团价格', + `goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品价格(单买价)', + `line_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品划线价', + `stock_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '当前库存数量', + `goods_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品销量', + `goods_weight` double unsigned NOT NULL DEFAULT '0' COMMENT '商品重量(Kg)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`goods_sku_id`), + UNIQUE KEY `sku_idx` (`goods_id`,`spec_sku_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品规格表'; + + +# 新增:拼团商品与规格值关系记录表 +CREATE TABLE `yoshop_sharing_goods_spec_rel` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `spec_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格组id', + `spec_value_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格值id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品与规格值关系记录表'; + + +# 新增:拼团订单记录表 +CREATE TABLE `yoshop_sharing_order` ( + `order_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单id', + `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号', + `total_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品总金额(不含优惠折扣)', + `order_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '订单金额(含优惠折扣)', + `order_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '订单类型(10单独购买 20拼团)', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼单id', + `coupon_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '优惠券id', + `coupon_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '优惠券抵扣金额', + `points_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '积分抵扣金额', + `points_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '积分抵扣数量', + `pay_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际付款金额(包含运费、优惠)', + `update_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '后台修改的订单金额(差价)', + `buyer_remark` varchar(255) NOT NULL DEFAULT '' COMMENT '买家留言', + `pay_type` tinyint(3) unsigned NOT NULL DEFAULT '20' COMMENT '支付方式(10余额支付 20微信支付)', + `pay_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '付款状态(10未付款 20已付款)', + `pay_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '付款时间', + `delivery_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '配送方式(10快递配送 20上门自提)', + `extract_shop_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '自提门店id', + `extract_clerk_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '核销店员id', + `express_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '运费金额', + `express_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '物流公司id', + `express_company` varchar(50) NOT NULL DEFAULT '' COMMENT '物流公司', + `express_no` varchar(50) NOT NULL DEFAULT '' COMMENT '物流单号', + `delivery_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '发货状态(10未发货 20已发货)', + `delivery_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '发货时间', + `receipt_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '收货状态(10未收货 20已收货)', + `receipt_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '收货时间', + `order_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '订单状态(10进行中 20已取消 21待取消 30已完成)', + `points_bonus` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '赠送的积分数量', + `is_settled` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '订单是否已结算(0未结算 1已结算)', + `is_refund` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '拼团未成功退款(0未退款 1已退款)', + `transaction_id` varchar(30) NOT NULL DEFAULT '' COMMENT '微信支付交易号', + `is_comment` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否已评价(0否 1是)', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_id`), + UNIQUE KEY `order_no` (`order_no`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='订单记录表'; + + +# 新增:拼团订单收货地址记录表 +CREATE TABLE `yoshop_sharing_order_address` ( + `order_address_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '地址id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `province_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在省份id', + `city_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在城市id', + `region_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在区id', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`order_address_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团订单收货地址记录表'; + + +# 新增:拼团订单商品记录表 +CREATE TABLE `yoshop_sharing_order_goods` ( + `order_goods_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团商品id', + `goods_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品封面图id', + `selling_point` varchar(500) NOT NULL DEFAULT '' COMMENT '商品卖点', + `people` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '成团人数', + `group_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成团有效时间(单位:小时)', + `is_alone` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否允许单买(0不允许 1允许)', + `deduct_stock_type` tinyint(3) unsigned NOT NULL DEFAULT '20' COMMENT '库存计算方式(10下单减库存 20付款减库存)', + `spec_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '规格类型(10单规格 20多规格)', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '' COMMENT '商品sku标识', + `goods_sku_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品规格id', + `goods_attr` varchar(500) NOT NULL DEFAULT '' COMMENT '商品规格信息', + `content` longtext NOT NULL COMMENT '商品详情', + `goods_no` varchar(100) NOT NULL DEFAULT '' COMMENT '商品编码', + `goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品价格(单价)', + `line_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品划线价', + `goods_weight` double unsigned NOT NULL DEFAULT '0' COMMENT '商品重量(Kg)', + `is_user_grade` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否存在会员等级折扣', + `grade_ratio` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '会员折扣比例(0-10)', + `grade_goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '会员折扣的商品单价', + `grade_total_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '会员折扣的总额差', + `coupon_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '优惠券折扣金额', + `points_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '积分金额', + `points_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '积分抵扣数量', + `points_bonus` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '赠送的积分数量', + `total_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '购买数量', + `total_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品总价(数量×单价)', + `total_pay_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际付款价(包含优惠、折扣)', + `is_ind_dealer` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否开启单独分销(0关闭 1开启)', + `dealer_money_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '分销佣金类型(10百分比 20固定金额)', + `first_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)', + `second_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)', + `third_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)', + `is_comment` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否已评价(0否 1是)', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`order_goods_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='订单商品记录表'; + + +# 新增:拼团售后单记录表 +CREATE TABLE `yoshop_sharing_order_refund` ( + `order_refund_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '售后单id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `order_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单商品id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '售后类型(10退货退款 20换货)', + `apply_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '用户申请原因(说明)', + `is_agree` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商家审核状态(0待审核 10已同意 20已拒绝)', + `refuse_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '商家拒绝原因(说明)', + `refund_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际退款金额', + `is_user_send` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '用户是否发货(0未发货 1已发货)', + `send_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户发货时间', + `express_id` varchar(32) NOT NULL DEFAULT '' COMMENT '用户发货物流公司id', + `express_no` varchar(32) NOT NULL DEFAULT '' COMMENT '用户发货物流单号', + `is_receipt` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商家收货状态(0未收货 1已收货)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '售后单状态(0进行中 10已拒绝 20已完成 30已取消)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_refund_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团售后单记录表'; + + +# 新增:拼团售后单退货地址记录表 +CREATE TABLE `yoshop_sharing_order_refund_address` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_refund_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '售后单id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团售后单退货地址记录表'; + + +# 新增:拼团售后单图片记录表 +CREATE TABLE `yoshop_sharing_order_refund_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_refund_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '售后单id', + `image_id` int(11) NOT NULL DEFAULT '0' COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团售后单图片记录表'; + + +# 新增:拼团设置表 +CREATE TABLE `yoshop_sharing_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='拼团设置表'; + + +# 新增文章记录表 +CREATE TABLE `yoshop_article` ( + `article_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '文章id', + `article_title` varchar(300) NOT NULL DEFAULT '' COMMENT '文章标题', + `show_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '列表显示方式(10小图展示 20大图展示)', + `category_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文章分类id', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '封面图id', + `article_content` longtext NOT NULL COMMENT '文章内容', + `article_sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文章排序(数字越小越靠前)', + `article_status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '文章状态(0隐藏 1显示)', + `virtual_views` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '虚拟阅读量(仅用作展示)', + `actual_views` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '实际阅读量', + `is_delete` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`article_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='文章记录表'; + + +# 新增文章分类表 +CREATE TABLE `yoshop_article_category` ( + `category_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品分类id', + `name` varchar(50) NOT NULL DEFAULT '' COMMENT '分类名称', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序方式(数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='文章分类表'; + + +# 小票打印机记录表 +CREATE TABLE `yoshop_printer` ( + `printer_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '打印机id', + `printer_name` varchar(255) NOT NULL DEFAULT '' COMMENT '打印机名称', + `printer_type` varchar(255) NOT NULL DEFAULT '' COMMENT '打印机类型', + `printer_config` text NOT NULL COMMENT '打印机配置', + `print_times` smallint(6) unsigned NOT NULL DEFAULT '0' COMMENT '打印联数(次数)', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序 (数字越小越靠前)', + `is_delete` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`printer_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='小票打印机记录表'; + + +# 商家门店记录表 +CREATE TABLE `yoshop_store_shop` ( + `shop_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '门店id', + `shop_name` varchar(255) NOT NULL DEFAULT '' COMMENT '门店名称', + `logo_image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '门店logo图片id', + `linkman` varchar(20) NOT NULL DEFAULT '' COMMENT '联系人', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `shop_hours` varchar(255) NOT NULL DEFAULT '' COMMENT '营业时间', + `province_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在省份id', + `city_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在城市id', + `region_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在辖区id', + `address` varchar(100) NOT NULL DEFAULT '' COMMENT '详细地址', + `longitude` varchar(50) NOT NULL DEFAULT '' COMMENT '门店坐标经度', + `latitude` varchar(50) NOT NULL DEFAULT '' COMMENT '门店坐标纬度', + `geohash` varchar(50) NOT NULL DEFAULT '' COMMENT 'geohash', + `summary` varchar(1000) NOT NULL DEFAULT '0' COMMENT '门店简介', + `sort` tinyint(3) NOT NULL DEFAULT 0 COMMENT '门店排序(数字越小越靠前)', + `is_check` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否支持自提核销(0否 1支持)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '门店状态(0禁用 1启用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`shop_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家门店记录表'; + + +# 商家门店店员表 +CREATE TABLE `yoshop_store_shop_clerk` ( + `clerk_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '店员id', + `shop_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所属门店id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `real_name` varchar(30) NOT NULL DEFAULT '' COMMENT '店员姓名', + `mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '状态(0禁用 1启用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`clerk_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家门店店员表'; + + +#商家门店核销订单记录表 +CREATE TABLE `yoshop_store_shop_order` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '订单类型(10商城订单 20拼团订单)', + `shop_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '门店id', + `clerk_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '核销员id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家门店核销订单记录表'; + + +# 用户充值订单表 +CREATE TABLE `yoshop_recharge_order` ( + `order_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单id', + `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `recharge_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '充值方式(10自定义金额 20套餐充值)', + `plan_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '充值套餐id', + `pay_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '用户支付金额', + `gift_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '赠送金额', + `actual_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际到账金额', + `pay_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '支付状态(10待支付 20已支付)', + `pay_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '付款时间', + `transaction_id` varchar(30) NOT NULL DEFAULT '' COMMENT '微信支付交易号', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户充值订单表'; + +# 用户充值订单套餐快照表 +CREATE TABLE `yoshop_recharge_order_plan` ( + `order_plan_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `plan_id` int(11) unsigned NOT NULL COMMENT '主键id', + `plan_name` varchar(255) NOT NULL DEFAULT '' COMMENT '方案名称', + `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '充值金额', + `gift_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '赠送金额', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_plan_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户充值订单套餐快照表'; + +# 余额充值套餐表 +CREATE TABLE `yoshop_recharge_plan` ( + `plan_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `plan_name` varchar(255) NOT NULL DEFAULT '' COMMENT '套餐名称', + `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '充值金额', + `gift_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '赠送金额', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '排序(数字越小越靠前)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`plan_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='余额充值套餐表'; + +# 用户余额变动明细表 +CREATE TABLE `yoshop_user_balance_log` ( + `log_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `scene` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '余额变动场景(10用户充值 20用户消费 30管理员操作 40订单退款)', + `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变动金额', + `describe` varchar(500) NOT NULL DEFAULT '' COMMENT '描述/说明', + `remark` varchar(500) NOT NULL DEFAULT '' COMMENT '管理员备注', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户余额变动明细表'; + +# 自提订单联系方式记录表 +CREATE TABLE `yoshop_order_extract` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `linkman` varchar(30) NOT NULL DEFAULT '' COMMENT '联系人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='自提订单联系方式记录表'; + +# 自提订单联系方式记录表 +CREATE TABLE `yoshop_sharing_order_extract` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `linkman` varchar(30) NOT NULL DEFAULT '' COMMENT '联系人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='自提订单联系方式记录表'; + + +# 好物圈设置表 +CREATE TABLE `yoshop_wow_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='好物圈设置表'; + + +# 好物圈商品收藏记录表 +CREATE TABLE `yoshop_wow_shoping` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='好物圈商品收藏记录表'; + + +# 好物圈订单同步记录表 +CREATE TABLE `yoshop_wow_order` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '订单类型(10商城订单 20拼团订单)', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '订单状态(3支付完成 4已发货 5已退款 100已完成)', + `last_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后更新时间', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='好物圈订单同步记录表'; + + +# 用户会员等级表 +CREATE TABLE `yoshop_user_grade` ( + `grade_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '等级ID', + `name` varchar(50) NOT NULL DEFAULT '' COMMENT '等级名称', + `weight` int(11) unsigned NOT NULL DEFAULT '1' COMMENT '等级权重(1-9999)', + `upgrade` text NOT NULL COMMENT '升级条件', + `equity` text NOT NULL COMMENT '等级权益(折扣率0-100)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态(1启用 0禁用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`grade_id`), + KEY `wxapp_id` (`wxapp_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户会员等级表'; + + +# 用户会员等级变更记录表 +CREATE TABLE `yoshop_user_grade_log` ( + `log_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `old_grade_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '变更前的等级id', + `new_grade_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '变更后的等级id', + `change_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '变更类型(10后台管理员设置 20自动升级)', + `remark` varchar(500) DEFAULT '' COMMENT '管理员备注', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户会员等级变更记录表'; + +# 砍价活动表 +CREATE TABLE `yoshop_bargain_active` ( + `active_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '砍价活动id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `start_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动开始时间', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动结束时间', + `expiryt_time` int(11) unsigned NOT NULL DEFAULT '1' COMMENT '砍价有效期(单位:小时)', + `floor_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '砍价底价', + `peoples` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '帮砍人数', + `is_self_cut` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '可自砍一刀(0禁止 1允许)', + `is_floor_buy` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '必须底价购买(0否 1是)', + `share_title` varchar(500) NOT NULL DEFAULT '' COMMENT '分享标题', + `prompt_words` varchar(500) NOT NULL DEFAULT '' COMMENT '砍价助力语', + `actual_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动销量(实际的)', + `initial_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '虚拟销量', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序(数字越小越靠前)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '活动状态(1启用 0禁用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`active_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='砍价活动表'; + +# 砍价活动设置表 +CREATE TABLE `yoshop_bargain_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='砍价活动设置表'; + +# 砍价任务表 +CREATE TABLE `yoshop_bargain_task` ( + `task_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '砍价任务id', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '砍价活动id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id(发起人)', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '' COMMENT '商品sku标识', + `goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品原价', + `floor_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '砍价底价', + `peoples` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '帮砍人数', + `cut_people` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '已砍人数', + `section` text NOT NULL COMMENT '砍价金额区间', + `cut_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '已砍金额', + `actual_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际购买金额', + `is_floor` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否已砍到底价(0否 1是)', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '任务截止时间', + `is_buy` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否购买(0未购买 1已购买)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '任务状态 (0已结束 1砍价中)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`task_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='砍价任务表'; + +# 砍价任务助力记录表 +CREATE TABLE `yoshop_bargain_task_help` ( + `help_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '砍价活动id', + `task_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '砍价任务id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `is_creater` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否为发起人(0否 1是)', + `cut_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '砍掉的金额', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`help_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='砍价任务助力记录表'; + +# 用户积分变动明细表 +CREATE TABLE `yoshop_user_points_log` ( + `log_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `value` int(11) NOT NULL DEFAULT '0.00' COMMENT '变动数量', + `describe` varchar(500) NOT NULL DEFAULT '' COMMENT '描述/说明', + `remark` varchar(500) NOT NULL DEFAULT '' COMMENT '管理员备注', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户积分变动明细表'; + + +CREATE TABLE `yoshop_sharp_active` ( + `active_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '活动会场ID', + `active_date` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动日期', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '活动状态(0禁用 1启用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`active_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-活动会场表'; + + +CREATE TABLE `yoshop_sharp_active_goods` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动会场ID', + `active_time_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动场次ID', + `sharp_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '秒杀商品ID', + `sales_actual` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '实际销量', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-活动会场与商品关联表'; + + +CREATE TABLE `yoshop_sharp_active_time` ( + `active_time_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '场次ID', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动会场ID', + `active_time` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '场次时间(0点-23点)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '活动状态(0禁用 1启用)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`active_time_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-活动会场场次表'; + + +CREATE TABLE `yoshop_sharp_goods` ( + `sharp_goods_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '秒杀商品ID', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品ID', + `deduct_stock_type` tinyint(3) unsigned DEFAULT '10' COMMENT '库存计算方式(10下单减库存 20付款减库存)', + `limit_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '限购数量', + `seckill_stock` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品库存总量', + `total_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '累积销量', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品排序(数字越小越靠前)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '商品状态(0下架 1上架)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`sharp_goods_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-商品表'; + +CREATE TABLE `yoshop_sharp_goods_sku` ( + `goods_sku_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品规格id', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '0' COMMENT '商品sku记录索引 (由规格id组成)', + `sharp_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '秒杀商品id', + `seckill_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品价格', + `seckill_stock` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '秒杀库存数量', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`goods_sku_id`), + UNIQUE KEY `sku_idx` (`sharp_goods_id`,`spec_sku_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-秒杀商品sku信息表'; + +CREATE TABLE `yoshop_sharp_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='整点秒杀设置表'; + + + + +ALTER TABLE `yoshop_order_goods` +ADD COLUMN `goods_source_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '来源记录id' AFTER `user_id`; + + +ALTER TABLE `yoshop_order` +MODIFY COLUMN `order_source` tinyint(3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '订单来源(10普通订单 20砍价订单 30秒杀订单)' AFTER `is_comment`; + + +UPDATE `yoshop_store_access` SET `sort`='105' WHERE (`access_id`='10300'); +UPDATE `yoshop_store_access` SET `sort`='110' WHERE (`access_id`='10426'); +UPDATE `yoshop_store_access` SET `sort`='120' WHERE (`access_id`='10400'); + + + +INSERT INTO `yoshop_store_access` VALUES ('10444', '整点秒杀', 'apps.sharp', '10074', '115', '1564449650', '1564449650'); + +INSERT INTO `yoshop_store_access` VALUES ('10445', '秒杀商品', 'apps.sharp.goods', '10444', '100', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10446', '商品列表', 'apps.sharp.goods/index', '10445', '100', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10447', '新增商品', 'apps.sharp.goods/add', '10445', '105', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10448', '编辑商品', 'apps.sharp.goods/edit', '10445', '110', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10449', '删除商品', 'apps.sharp.goods/delete', '10445', '115', '1564449650', '1564449650'); + +INSERT INTO `yoshop_store_access` VALUES ('10450', '活动会场', 'apps.sharp.active', '10444', '105', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10451', '会场列表', 'apps.sharp.active/index', '10450', '100', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10452', '新增会场', 'apps.sharp.active/add', '10450', '105', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10453', '修改活动状态', 'apps.sharp.active/state', '10450', '110', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10454', '删除会场', 'apps.sharp.active/delete', '10450', '115', '1564449650', '1564449650'); + +INSERT INTO `yoshop_store_access` VALUES ('10455', '场次管理', 'apps.sharp.active_time', '10450', '120', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10456', '场次列表', 'apps.sharp.active_time/index', '10455', '100', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10457', '新增场次', 'apps.sharp.active_time/add', '10455', '105', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10458', '编辑场次', 'apps.sharp.active_time/edit', '10455', '110', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10459', '修改活动状态', 'apps.sharp.active_time/state', '10455', '115', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10460', '删除场次', 'apps.sharp.active_time/delete', '10455', '120', '1564449650', '1564449650'); + +INSERT INTO `yoshop_store_access` VALUES ('10461', '基础设置', 'apps.sharp.setting/index', '10444', '125', '1564449650', '1564449650'); + + diff --git a/doc/database/upgrade/v1.1.0.sql b/doc/database/upgrade/v1.1.0.sql new file mode 100644 index 0000000..f59a1ab --- /dev/null +++ b/doc/database/upgrade/v1.1.0.sql @@ -0,0 +1,31 @@ + +# 微信小程序diy页面表:页面标题改为页面名称 +ALTER TABLE `yoshop_wxapp_page` +CHANGE COLUMN `page_title` `page_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '页面名称' AFTER `page_type`; + + +# 微信小程序diy页面表:新增字段 软删除 +ALTER TABLE `yoshop_wxapp_page` +ADD COLUMN `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '软删除' AFTER `wxapp_id`; + + +# 微信小程序diy页面表:添加页面名称 +UPDATE `yoshop_wxapp_page` SET `page_name` = '小程序首页' WHERE page_id = 10001; + + +# 订单记录表:新增字段 后台修改的订单金额(差价) +ALTER TABLE `yoshop_order` +ADD COLUMN `update_price` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '后台修改的订单金额(差价)' AFTER `pay_price`; + + +# 微信小程序分类页模板表 +CREATE TABLE `yoshop_wxapp_category` ( + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `category_style` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '分类页样式(10一级分类[大图] 11一级分类[小图] 20二级分类)', + `share_title` varchar(10) NOT NULL DEFAULT '' COMMENT '分享标题', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信小程序分类页模板'; + +INSERT INTO `yoshop_wxapp_category` (`wxapp_id`, `category_style`, `share_title`, `create_time`, `update_time`) VALUES ('10001', '10', '', '1536373988', '1536375112'); diff --git a/doc/database/upgrade/v1.1.1.sql b/doc/database/upgrade/v1.1.1.sql new file mode 100644 index 0000000..4a2251f --- /dev/null +++ b/doc/database/upgrade/v1.1.1.sql @@ -0,0 +1,22 @@ + +# 订单记录表:买家留言 +ALTER TABLE `yoshop_order` +ADD COLUMN `buyer_remark` varchar(255) NOT NULL DEFAULT '' COMMENT '买家留言' AFTER `update_price`; + + +# 小程序prepay_id记录表 +CREATE TABLE `yoshop_wxapp_prepay_id` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `prepay_id` varchar(50) NOT NULL DEFAULT '' COMMENT '微信支付prepay_id', + `can_use_times` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '可使用次数', + `used_times` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '已使用次数', + `pay_status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '支付状态(1已支付)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `expiry_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '过期时间', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `order_id` (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='小程序prepay_id记录'; diff --git a/doc/database/upgrade/v1.1.12.sql b/doc/database/upgrade/v1.1.12.sql new file mode 100644 index 0000000..1bba6c2 --- /dev/null +++ b/doc/database/upgrade/v1.1.12.sql @@ -0,0 +1,43 @@ + + +# 新增权限url:分销中心下级用户列表 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10336', '下级用户列表', 'apps.dealer.user/fans', '10079', '100', '1545189676', '1545189676'); + + +# 微信小程序分类页模板:分享标题字段增加 varchar长度 +ALTER TABLE `yoshop_wxapp_category` +MODIFY COLUMN `share_title` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '分享标题' AFTER `category_style`; + + + +-- 主商品 -- +ALTER TABLE `yoshop_goods` ADD COLUMN `is_ind_dealer` TINYINT (3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启单独分销(0关闭 1开启)' AFTER `delivery_id`, + ADD COLUMN `dealer_money_type` TINYINT (3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '分销佣金类型(10百分比 20固定金额)' AFTER `is_ind_dealer`, + ADD COLUMN `first_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)' AFTER `dealer_money_type`, + ADD COLUMN `second_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)' AFTER `first_money`, + ADD COLUMN `third_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)' AFTER `second_money`; + + +ALTER TABLE `yoshop_order_goods` ADD COLUMN `is_ind_dealer` TINYINT (3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启单独分销(0关闭 1开启)' AFTER `total_pay_price`, + ADD COLUMN `dealer_money_type` TINYINT (3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '分销佣金类型(10百分比 20固定金额)' AFTER `is_ind_dealer`, + ADD COLUMN `first_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)' AFTER `dealer_money_type`, + ADD COLUMN `second_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)' AFTER `first_money`, + ADD COLUMN `third_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)' AFTER `second_money`; + + + + -- 拼团 -- + + ALTER TABLE `yoshop_sharing_goods` ADD COLUMN `is_ind_dealer` TINYINT (3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启单独分销(0关闭 1开启)' AFTER `delivery_id`, + ADD COLUMN `dealer_money_type` TINYINT (3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '分销佣金类型(10百分比 20固定金额)' AFTER `is_ind_dealer`, + ADD COLUMN `first_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)' AFTER `dealer_money_type`, + ADD COLUMN `second_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)' AFTER `first_money`, + ADD COLUMN `third_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)' AFTER `second_money`; + + +ALTER TABLE `yoshop_sharing_order_goods` ADD COLUMN `is_ind_dealer` TINYINT (3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启单独分销(0关闭 1开启)' AFTER `total_pay_price`, + ADD COLUMN `dealer_money_type` TINYINT (3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '分销佣金类型(10百分比 20固定金额)' AFTER `is_ind_dealer`, + ADD COLUMN `first_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)' AFTER `dealer_money_type`, + ADD COLUMN `second_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)' AFTER `first_money`, + ADD COLUMN `third_money` DECIMAL (10, 2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)' AFTER `second_money`; + diff --git a/doc/database/upgrade/v1.1.13.sql b/doc/database/upgrade/v1.1.13.sql new file mode 100644 index 0000000..5d0d6dd --- /dev/null +++ b/doc/database/upgrade/v1.1.13.sql @@ -0,0 +1,48 @@ + + +# 新增文章记录表 +CREATE TABLE `yoshop_article` ( + `article_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '文章id', + `article_title` varchar(300) NOT NULL DEFAULT '' COMMENT '文章标题', + `show_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '列表显示方式(10小图展示 20大图展示)', + `category_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文章分类id', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '封面图id', + `article_content` longtext NOT NULL COMMENT '文章内容', + `article_sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文章排序(数字越小越靠前)', + `article_status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '文章状态(0隐藏 1显示)', + `virtual_views` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '虚拟阅读量(仅用作展示)', + `actual_views` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '实际阅读量', + `is_delete` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`article_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='文章记录表'; + + +# 新增文章分类表 +CREATE TABLE `yoshop_article_category` ( + `category_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品分类id', + `name` varchar(50) NOT NULL DEFAULT '' COMMENT '分类名称', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序方式(数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='文章分类表'; + + +# 新增权限url:后台文章管理 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10337', '内容管理', 'content', '0', '100', '1547018818', '1547018818'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10338', '文章管理', 'content.article', '10337', '100', '1547018849', '1547018869'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10339', '文章列表', 'content.article/index', '10338', '100', '1547018885', '1547018885'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10340', '添加文章', 'content.article/add', '10338', '100', '1547018901', '1547018901'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10341', '编辑文章', 'content.article/edit', '10338', '100', '1547018922', '1547018922'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10342', '删除文章', 'content.article/delete', '10338', '100', '1547018937', '1547018937'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10343', '文章分类', 'content.article.category', '10337', '100', '1547018972', '1547018972'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10344', '分类列表', 'content.article.category/index', '10343', '100', '1547018992', '1547018992'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10345', '添加分类', 'content.article.category/add', '10343', '100', '1547019008', '1547019017'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10346', '编辑分类', 'content.article.category/edit', '10343', '100', '1547019008', '1547019017'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10347', '删除分类', 'content.article.category/delete', '10343', '100', '1547019008', '1547019017'); + + diff --git a/doc/database/upgrade/v1.1.16.sql b/doc/database/upgrade/v1.1.16.sql new file mode 100644 index 0000000..60c1845 --- /dev/null +++ b/doc/database/upgrade/v1.1.16.sql @@ -0,0 +1,33 @@ + + +# 小票打印机记录表 +CREATE TABLE `yoshop_printer` ( + `printer_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '打印机id', + `printer_name` varchar(255) NOT NULL DEFAULT '' COMMENT '打印机名称', + `printer_type` varchar(255) NOT NULL DEFAULT '' COMMENT '打印机类型', + `printer_config` text NOT NULL COMMENT '打印机配置', + `print_times` smallint(6) unsigned NOT NULL DEFAULT '0' COMMENT '打印联数(次数)', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序 (数字越小越靠前)', + `is_delete` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`printer_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='小票打印机记录表'; + + + +# 新增权限url:分销商提现微信付款 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10348', '微信付款', 'apps.dealer.withdraw/wechat_pay', '10084', '100', '1548232045', '1548232045'); + + +# 新增权限url:小票打印机管理 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10349', '小票打印机', 'setting.printer', '10090', '100', '1548738285', '1548738285'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10350', '打印机管理', 'setting.printer', '10349', '100', '1548738718', '1548738718'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10351', '小票打印设置', 'setting/printer', '10349', '100', '1548738720', '1548738720'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10352', '小票打印机列表', 'setting.printer/index', '10350', '100', '1548738420', '1548738420'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10353', '新增小票打印机', 'setting.printer/add', '10350', '100', '1548738443', '1548738443'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10354', '编辑小票打印机', 'setting.printer/edit', '10350', '100', '1548738443', '1548738443'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10355', '删除小票打印机', 'setting.printer/delete', '10350', '100', '1548738443', '1548738443'); + + diff --git a/doc/database/upgrade/v1.1.17.sql b/doc/database/upgrade/v1.1.17.sql new file mode 100644 index 0000000..69c962b --- /dev/null +++ b/doc/database/upgrade/v1.1.17.sql @@ -0,0 +1,87 @@ + + +ALTER TABLE `yoshop_order` ADD COLUMN `delivery_type` tinyint(3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '配送方式(10快递配送 20上门自提)' AFTER `pay_time`; +ALTER TABLE `yoshop_order` ADD COLUMN `extract_shop_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '自提门店id' AFTER `delivery_type`; +ALTER TABLE `yoshop_order` ADD COLUMN `extract_clerk_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '核销店员id' AFTER `extract_shop_id`; + +ALTER TABLE `yoshop_sharing_order` ADD COLUMN `delivery_type` tinyint(3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '配送方式(10快递配送 20上门自提)' AFTER `pay_time`; +ALTER TABLE `yoshop_sharing_order` ADD COLUMN `extract_shop_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '自提门店id' AFTER `delivery_type`; +ALTER TABLE `yoshop_sharing_order` ADD COLUMN `extract_clerk_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '核销店员id' AFTER `extract_shop_id`; + + +# 商家门店记录表 +CREATE TABLE `yoshop_store_shop` ( + `shop_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '门店id', + `shop_name` varchar(255) NOT NULL DEFAULT '' COMMENT '门店名称', + `logo_image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '门店logo图片id', + `linkman` varchar(20) NOT NULL DEFAULT '' COMMENT '联系人', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `shop_hours` varchar(255) NOT NULL DEFAULT '' COMMENT '营业时间', + `province_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在省份id', + `city_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在城市id', + `region_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在辖区id', + `address` varchar(100) NOT NULL DEFAULT '' COMMENT '详细地址', + `longitude` varchar(50) NOT NULL DEFAULT '' COMMENT '门店坐标经度', + `latitude` varchar(50) NOT NULL DEFAULT '' COMMENT '门店坐标纬度', + `geohash` varchar(50) NOT NULL DEFAULT '' COMMENT 'geohash', + `summary` varchar(1000) NOT NULL DEFAULT '0' COMMENT '门店简介', + `is_check` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否支持自提核销(0否 1支持)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '门店状态(0禁用 1启用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`shop_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家门店记录表'; + + +# 商家门店店员表 +CREATE TABLE `yoshop_store_shop_clerk` ( + `clerk_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '店员id', + `shop_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所属门店id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `real_name` varchar(30) NOT NULL DEFAULT '' COMMENT '店员姓名', + `mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '状态(0禁用 1启用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`clerk_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家门店店员表'; + + +#商家门店核销订单记录表 +CREATE TABLE `yoshop_store_shop_order` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '订单类型(10商城订单 20拼团订单)', + `shop_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '门店id', + `clerk_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '核销员id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家门店核销订单记录表'; + + + +# 新增权限url:门店管理 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10356', '门店管理', 'shop', '0', '100', '1551504862', '1551504862'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10357', '门店管理', 'shop', '10356', '105', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10358', '门店列表', 'shop/index', '10357', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10359', '添加门店', 'shop/add', '10357', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10360', '编辑门店', 'shop/edit', '10357', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10361', '删除门店', 'shop/delete', '10357', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10362', '店员管理', 'shop.clerk', '10356', '110', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10363', '店员列表', 'shop.clerk/index', '10362', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10364', '添加店员', 'shop.clerk/add', '10362', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10365', '编辑店员', 'shop.clerk/edit', '10362', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10366', '删除店员', 'shop.clerk/delete', '10362', '100', '1551505032', '1551505048'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10367', '订单核销记录', 'shop.order/index', '10356', '115', '1551505016', '1551505016'); + +# 新增权限url:订单核销 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10368', '门店自提核销', 'order.operate/extract', '10043', '100', '1551505016', '1551505016'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10369', '门店自提核销', 'apps.sharing.order.operate/extract', '10318', '100', '1551505016', '1551505016'); + +# 新增权限url:满额包邮 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10370', '满额包邮', 'market.basic/full_free', '10052', '100', '1551505016', '1551505016'); diff --git a/doc/database/upgrade/v1.1.18.sql b/doc/database/upgrade/v1.1.18.sql new file mode 100644 index 0000000..311b595 --- /dev/null +++ b/doc/database/upgrade/v1.1.18.sql @@ -0,0 +1,11 @@ + + +# 分销商订单记录表:新增 "订单是否失效" 字段 +ALTER TABLE `yoshop_dealer_order` +ADD COLUMN `is_invalid` tinyint(3) NOT NULL DEFAULT 0 COMMENT '订单是否失效 (0未失效 1已失效)' AFTER `third_money`; + + +# 门店记录表:新增 "排序" 字段 +ALTER TABLE `yoshop_store_shop` +ADD COLUMN `sort` tinyint(3) NOT NULL DEFAULT 0 COMMENT '门店排序(数字越小越靠前)' AFTER `summary`; + diff --git a/doc/database/upgrade/v1.1.19.sql b/doc/database/upgrade/v1.1.19.sql new file mode 100644 index 0000000..8e9c6a9 --- /dev/null +++ b/doc/database/upgrade/v1.1.19.sql @@ -0,0 +1,29 @@ + + +# 文件库记录表:新增 "是否已回收" 字段 +ALTER TABLE `yoshop_upload_file` +ADD COLUMN `is_recycle` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否已回收' AFTER `is_user`; + + +# 文件库分组记录表:新增 "是否删除" 字段 +ALTER TABLE `yoshop_upload_group` +ADD COLUMN `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除' AFTER `sort`; + + +# 新增权限url:后台文件库管理 + +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10375', '文件库管理', 'content.files.group', '10337', '105', '1552634170', '1552634170'); + +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10376', '文件分组', 'content.files.group', '10375', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10377', '分组列表', 'content.files.group/index', '10376', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10378', '添加分组', 'content.files.group/add', '10376', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10379', '编辑分组', 'content.files.group/edit', '10376', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10380', '删除分组', 'content.files.group/delete', '10376', '100', '1552634170', '1552634170'); + +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10381', '文件管理', 'content.files.group', '10375', '100', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10382', '文件列表', 'content.files.group/index', '10381', '105', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10383', '回收站列表', 'content.files.group/recycle', '10381', '110', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10384', '移入回收站', 'content.files.group/add', '10381', '115', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10385', '回收站还原', 'content.files.group/edit', '10381', '120', '1552634170', '1552634170'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10386', '删除文件', 'content.files.group/delete', '10381', '125', '1552634170', '1552634170'); + diff --git a/doc/database/upgrade/v1.1.21.sql b/doc/database/upgrade/v1.1.21.sql new file mode 100644 index 0000000..4c93f38 --- /dev/null +++ b/doc/database/upgrade/v1.1.21.sql @@ -0,0 +1,79 @@ + +ALTER TABLE `yoshop_user` ADD COLUMN `balance` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '用户可用余额' AFTER `address_id`; +ALTER TABLE `yoshop_order` ADD COLUMN `pay_type` tinyint(3) UNSIGNED NOT NULL DEFAULT 20 COMMENT '支付方式(10余额支付 20微信支付)' AFTER `buyer_remark`; +ALTER TABLE `yoshop_sharing_order` ADD COLUMN `pay_type` tinyint(3) UNSIGNED NOT NULL DEFAULT 20 COMMENT '支付方式(10余额支付 20微信支付)' AFTER `buyer_remark`; + + +CREATE TABLE `yoshop_recharge_order` ( + `order_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单id', + `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `recharge_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '充值方式(10自定义金额 20套餐充值)', + `plan_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '充值套餐id', + `pay_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '用户支付金额', + `gift_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '赠送金额', + `actual_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际到账金额', + `pay_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '支付状态(10待支付 20已支付)', + `pay_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '付款时间', + `transaction_id` varchar(30) NOT NULL DEFAULT '' COMMENT '微信支付交易号', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户充值订单表'; + + +CREATE TABLE `yoshop_recharge_order_plan` ( + `order_plan_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `plan_id` int(11) unsigned NOT NULL COMMENT '主键id', + `plan_name` varchar(255) NOT NULL DEFAULT '' COMMENT '方案名称', + `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '充值金额', + `gift_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '赠送金额', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_plan_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户充值订单套餐快照表'; + + +CREATE TABLE `yoshop_recharge_plan` ( + `plan_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `plan_name` varchar(255) NOT NULL DEFAULT '' COMMENT '套餐名称', + `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '充值金额', + `gift_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '赠送金额', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '排序(数字越小越靠前)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`plan_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='余额充值套餐表'; + + +CREATE TABLE `yoshop_user_balance_log` ( + `log_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `scene` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '余额变动场景(10用户充值 20用户消费 30管理员操作 40订单退款)', + `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '变动金额', + `describe` varchar(500) NOT NULL DEFAULT '' COMMENT '描述/说明', + `remark` varchar(500) NOT NULL DEFAULT '' COMMENT '管理员备注', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户余额变动明细表'; + + + +UPDATE `yoshop_store_access` SET `sort`='120' WHERE (`access_id` = '10370'); + +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10387', '余额记录', 'user.balance', '10049', '105', '1554685953', '1554685965'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10388', '充值记录', 'user.recharge/order', '10387', '100', '1554686010', '1554686010'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10389', '余额明细', 'user.balance/log', '10387', '105', '1554686031', '1554686031'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10390', '用户充值', 'market.recharge', '10052', '110', '1554686283', '1554686339'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10391', '充值套餐', 'market.recharge.plan', '10390', '100', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10392', '套餐列表', 'market.recharge.plan/index', '10391', '100', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10393', '添加套餐', 'market.recharge.plan/add', '10391', '105', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10394', '编辑套餐', 'market.recharge.plan/edit', '10391', '110', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10395', '删除套餐', 'market.recharge.plan/delete', '10391', '115', '1554686316', '1554686316'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10396', '充值设置', 'market.recharge/setting', '10390', '105', '1554686647', '1554686647'); diff --git a/doc/database/upgrade/v1.1.22.sql b/doc/database/upgrade/v1.1.22.sql new file mode 100644 index 0000000..76d87a9 --- /dev/null +++ b/doc/database/upgrade/v1.1.22.sql @@ -0,0 +1,27 @@ + +UPDATE `yoshop_store_access` SET `name`='运费模板' WHERE (`access_id`='10093'); + + +CREATE TABLE `yoshop_order_extract` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `linkman` varchar(30) NOT NULL DEFAULT '' COMMENT '联系人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10003 DEFAULT CHARSET=utf8 COMMENT='自提订单联系方式记录表'; + + +CREATE TABLE `yoshop_sharing_order_extract` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `linkman` varchar(30) NOT NULL DEFAULT '' COMMENT '联系人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='自提订单联系方式记录表'; + diff --git a/doc/database/upgrade/v1.1.23.sql b/doc/database/upgrade/v1.1.23.sql new file mode 100644 index 0000000..bcb5ed1 --- /dev/null +++ b/doc/database/upgrade/v1.1.23.sql @@ -0,0 +1,9 @@ + +UPDATE `yoshop_store_access` SET `name`='营销管理' WHERE (`access_id`='10052') + +ALTER TABLE `yoshop_order` +ADD COLUMN `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除' AFTER `user_id`; + +ALTER TABLE `yoshop_sharing_order` +ADD COLUMN `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除' AFTER `user_id`; + diff --git a/doc/database/upgrade/v1.1.24.sql b/doc/database/upgrade/v1.1.24.sql new file mode 100644 index 0000000..2cbce78 --- /dev/null +++ b/doc/database/upgrade/v1.1.24.sql @@ -0,0 +1,100 @@ + +START TRANSACTION; + + +# 好物圈设置表 +CREATE TABLE `yoshop_wow_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='好物圈设置表'; + + +# 好物圈商品收藏记录表 +CREATE TABLE `yoshop_wow_shoping` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='好物圈商品收藏记录表'; + + +# 好物圈订单同步记录表 +CREATE TABLE `yoshop_wow_order` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '订单类型(10商城订单 20拼团订单)', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '订单状态(3支付完成 4已发货 5已退款 100已完成)', + `last_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后更新时间', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='好物圈订单同步记录表'; + + +# 更新权限排序 +UPDATE `yoshop_store_access` SET `sort`='105' WHERE (`access_id`='10335'); +UPDATE `yoshop_store_access` SET `sort`='105' WHERE (`access_id`='10051'); +UPDATE `yoshop_store_access` SET `sort`='115' WHERE (`access_id`='10387'); + + +# 新增好物圈管理管理 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10400', '好物圈', 'apps.wow', '10074', '110', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10401', '商品收藏', 'apps.wow.shoping', '10400', '100', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10402', '订单信息', 'apps.wow.order', '10400', '105', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10403', '基础设置', 'apps.wow.setting/index', '10400', '110', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10404', '商品收藏记录', 'apps.wow.shoping/index', '10401', '100', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10405', '取消同步', 'apps.wow.shoping/delete', '10401', '105', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10406', '订单同步记录', 'apps.wow.order/index', '10402', '100', '1557037952', '1557037952'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10407', '取消同步', 'apps.wow.order/delete', '10402', '105', '1557037952', '1557037952'); + + +# 新增用户充值权限 +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10408', '用户充值', 'user/recharge', '10049', '110', '1557037952', '1557037952'); + + +# 删除冗余的权限 +DELETE FROM `yoshop_store_access` WHERE (`access_id`='10013'); +DELETE FROM `yoshop_store_access` WHERE (`access_id`='10014'); +DELETE FROM `yoshop_store_access` WHERE (`access_id`='10015'); +DELETE FROM `yoshop_store_access` WHERE (`access_id`='10016'); +DELETE FROM `yoshop_store_access` WHERE (`access_id`='10017'); + + +# 整理地区表 +UPDATE `yoshop_region` SET `name`='香港岛(废弃)' WHERE (`id`='3717'); +UPDATE `yoshop_region` SET `name`='九龙(废弃)' WHERE (`id`='3722'); +UPDATE `yoshop_region` SET `name`='新界(废弃)' WHERE (`id`='3728'); + +INSERT INTO `yoshop_region` VALUES ('3999', '3716', '香港', '香港特别行政区', '中国,香港特别行政区', '2', 'hongkong', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4000', '3999', '中西区', '中西区', '中国,香港特别行政区,中西区', '3', 'zhongxin', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4001', '3999', '东区', '东区', '中国,香港特别行政区,东区', '3', 'dong', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4002', '3999', '九龙城区', '九龙城区', '中国,香港特别行政区,九龙城区', '3', 'jiulong', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4003', '3999', '观塘区', '观塘区', '中国,香港特别行政区,观塘区', '3', 'guantang', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4004', '3999', '南区', '南区', '中国,香港特别行政区,南区', '3', 'nan', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4005', '3999', '深水埗区', '深水埗区', '中国,香港特别行政区,深水埗区', '3', 'shenshuibu', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4006', '3999', '湾仔区', '湾仔区', '中国,香港特别行政区,湾仔区', '3', 'wanzi', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4007', '3999', '黄大仙区', '黄大仙区', '中国,香港特别行政区,黄大仙区', '3', 'huangdaxian', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4008', '3999', '油尖旺区', '油尖旺区', '中国,香港特别行政区,油尖旺区', '3', 'youjianwang', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4009', '3999', '离岛区', '离岛区', '中国,香港特别行政区,离岛区', '3', 'lidao', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4010', '3999', '葵青区', '葵青区', '中国,香港特别行政区,葵青区', '3', 'kuiqing', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4011', '3999', '北区', '北区', '中国,香港特别行政区,北区', '3', 'bei', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4012', '3999', '西贡区', '西贡区', '中国,香港特别行政区,西贡区', '3', 'xigong', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4013', '3999', '沙田区', '沙田区', '中国,香港特别行政区,沙田区', '3', 'shatian', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4014', '3999', '屯门区', '屯门区', '中国,香港特别行政区,屯门区', '3', 'tunmen', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4015', '3999', '大埔区', '大埔区', '中国,香港特别行政区,大埔区', '3', 'dapu', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4016', '3999', '荃湾区', '荃湾区', '中国,香港特别行政区,荃湾区', '3', 'quanwan', null, null, null, null, null); +INSERT INTO `yoshop_region` VALUES ('4017', '3999', '元朗区', '元朗区', '中国,香港特别行政区,元朗区', '3', 'yuanlang', null, null, null, null, null); + +ALTER TABLE `yoshop_region` AUTO_INCREMENT=50001; + +COMMIT; \ No newline at end of file diff --git a/doc/database/upgrade/v1.1.25.sql b/doc/database/upgrade/v1.1.25.sql new file mode 100644 index 0000000..9ba1089 --- /dev/null +++ b/doc/database/upgrade/v1.1.25.sql @@ -0,0 +1,168 @@ + +START TRANSACTION; + +# 修改字段:用户表 - 用户总支付的金额 +ALTER TABLE `yoshop_user` +CHANGE COLUMN `money` `pay_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '用户总支付的金额' AFTER `balance`; + +# 新增字段:用户表 - 实际消费的金额 +ALTER TABLE `yoshop_user` +ADD COLUMN `expend_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '实际消费的金额(不含退款)' AFTER `pay_money`; + +# 新增字段:用户表 - 会员等级id +ALTER TABLE `yoshop_user` +ADD COLUMN `grade_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员等级id' AFTER `expend_money`; + + + +# 新增字段:商品表 - 是否开启会员折扣 +ALTER TABLE `yoshop_goods` +ADD COLUMN `is_enable_grade` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否开启会员折扣(1开启 0关闭)' AFTER `delivery_id`; + +# 新增字段:商品表 - 会员折扣设置 +ALTER TABLE `yoshop_goods` +ADD COLUMN `is_alone_grade` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员折扣设置(0默认等级折扣 1单独设置折扣)' AFTER `is_enable_grade`; + +# 新增字段:商品表 - 单独设置折扣的配置 +ALTER TABLE `yoshop_goods` +ADD COLUMN `alone_grade_equity` text NULL COMMENT '单独设置折扣的配置' AFTER `is_alone_grade`; + + +# 新增字段:商品表 - 是否开启会员折扣 +ALTER TABLE `yoshop_sharing_goods` +ADD COLUMN `is_enable_grade` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否开启会员折扣(1开启 0关闭)' AFTER `delivery_id`; + +# 新增字段:商品表 - 会员折扣设置 +ALTER TABLE `yoshop_sharing_goods` +ADD COLUMN `is_alone_grade` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员折扣设置(0默认等级折扣 1单独设置折扣)' AFTER `is_enable_grade`; + +# 新增字段:商品表 - 单独设置折扣的配置 +ALTER TABLE `yoshop_sharing_goods` +ADD COLUMN `alone_grade_equity` text NULL COMMENT '单独设置折扣的配置' AFTER `is_alone_grade`; + + +# 新增字段:订单表 - 标识:累积用户实际消费金额 +ALTER TABLE `yoshop_order` +ADD COLUMN `is_user_expend` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '标识:累积用户实际消费金额' AFTER `user_id`; + +# 修改字段:订单表 - 优惠券抵扣金额 +ALTER TABLE `yoshop_order` +CHANGE COLUMN `coupon_price` `coupon_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '优惠券抵扣金额' AFTER `coupon_id`; + + +# 新增字段:拼团订单表 - 标识:累积用户实际消费金额 +ALTER TABLE `yoshop_sharing_order` +ADD COLUMN `is_user_expend` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '标识:累积用户实际消费金额' AFTER `user_id`; + +# 修改字段:拼团订单表 - 优惠券抵扣金额 +ALTER TABLE `yoshop_sharing_order` +CHANGE COLUMN `coupon_price` `coupon_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '优惠券抵扣金额' AFTER `coupon_id`; + + + + + + +# 新增字段:订单商品记录表 - 会员等级折扣金额 + 优惠券折扣金额 + +ALTER TABLE `yoshop_order` +MODIFY COLUMN `total_price` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '商品总金额(不含优惠折扣)' AFTER `order_no`, +ADD COLUMN `order_price` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '订单金额(含优惠折扣)' AFTER `total_price`; + +ALTER TABLE `yoshop_order_goods` +ADD COLUMN `grade_total_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员等级折扣金额' AFTER `goods_weight`, +ADD COLUMN `coupon_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '优惠券折扣金额' AFTER `grade_total_money`; + +# 新增字段:订单商品记录表 - 是否存在会员等级折扣 +ALTER TABLE `yoshop_order_goods` +ADD COLUMN `is_user_grade` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否存在会员等级折扣' AFTER `goods_weight`; + +# 新增字段:订单商品记录表 - 会员折扣比例(0-10) +ALTER TABLE `yoshop_order_goods` +MODIFY COLUMN `grade_total_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '会员等级折扣金额(总)' AFTER `is_user_grade`, +ADD COLUMN `grade_ratio` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员折扣比例(0-10)' AFTER `is_user_grade`; + +ALTER TABLE `yoshop_order_goods` +MODIFY COLUMN `goods_price` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '商品价格(单价)' AFTER `goods_no`, +MODIFY COLUMN `grade_total_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '会员折扣总金额' AFTER `grade_ratio`, +ADD COLUMN `grade_goods_price` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员折扣的商品单价' AFTER `grade_ratio`; + +ALTER TABLE `yoshop_order_goods` +MODIFY COLUMN `grade_total_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '会员折扣的总额差' AFTER `grade_goods_price`; + + + +# 新增字段:订单商品记录表 - 会员等级折扣金额 + 优惠券折扣金额 + +ALTER TABLE `yoshop_sharing_order` +MODIFY COLUMN `total_price` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '商品总金额(不含优惠折扣)' AFTER `order_no`, +ADD COLUMN `order_price` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '订单金额(含优惠折扣)' AFTER `total_price`; + +ALTER TABLE `yoshop_sharing_order_goods` +ADD COLUMN `grade_total_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员等级折扣金额' AFTER `goods_weight`, +ADD COLUMN `coupon_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '优惠券折扣金额' AFTER `grade_total_money`; + +# 新增字段:订单商品记录表 - 是否存在会员等级折扣 +ALTER TABLE `yoshop_sharing_order_goods` +ADD COLUMN `is_user_grade` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否存在会员等级折扣' AFTER `goods_weight`; + +# 新增字段:订单商品记录表 - 会员折扣比例(0-10) +ALTER TABLE `yoshop_sharing_order_goods` +MODIFY COLUMN `grade_total_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '会员等级折扣金额(总)' AFTER `is_user_grade`, +ADD COLUMN `grade_ratio` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员折扣比例(0-10)' AFTER `is_user_grade`; + +ALTER TABLE `yoshop_sharing_order_goods` +MODIFY COLUMN `goods_price` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '商品价格(单价)' AFTER `goods_no`, +MODIFY COLUMN `grade_total_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '会员折扣总金额' AFTER `grade_ratio`, +ADD COLUMN `grade_goods_price` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '会员折扣的商品单价' AFTER `grade_ratio`; + +ALTER TABLE `yoshop_sharing_order_goods` +MODIFY COLUMN `grade_total_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '会员折扣的总额差' AFTER `grade_goods_price`; + + + + + +# 新增表:用户会员等级表 +CREATE TABLE `yoshop_user_grade` ( + `grade_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '等级ID', + `name` varchar(50) NOT NULL DEFAULT '' COMMENT '等级名称', + `weight` int(11) unsigned NOT NULL DEFAULT '1' COMMENT '等级权重(1-9999)', + `upgrade` text NOT NULL COMMENT '升级条件', + `equity` text NOT NULL COMMENT '等级权益(折扣率0-100)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态(1启用 0禁用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`grade_id`), + KEY `wxapp_id` (`wxapp_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户会员等级表'; + + +# 新增表:用户会员等级变更记录表 +CREATE TABLE `yoshop_user_grade_log` ( + `log_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `old_grade_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '变更前的等级id', + `new_grade_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '变更后的等级id', + `change_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '变更类型(10后台管理员设置 20自动升级)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户会员等级变更记录表'; + + + +UPDATE `yoshop_store_access` SET `sort`='125' WHERE (`access_id`='10387'); + +INSERT INTO `yoshop_store_access` VALUES ('10411', '修改会员等级', 'user/grade', '10049', '115', '1558317213', '1558317226'); +INSERT INTO `yoshop_store_access` VALUES ('10412', '会员等级管理', 'user.grade', '10049', '120', '1558317440', '1558317440'); +INSERT INTO `yoshop_store_access` VALUES ('10413', '会员等级列表', 'user.grade/index', '10412', '100', '1558317464', '1558317464'); +INSERT INTO `yoshop_store_access` VALUES ('10414', '新增等级', 'user.grade/add', '10412', '105', '1558317464', '1558317464'); +INSERT INTO `yoshop_store_access` VALUES ('10415', '编辑等级', 'user.grade/edit', '10412', '110', '1558317464', '1558317464'); +INSERT INTO `yoshop_store_access` VALUES ('10416', '删除等级', 'user.grade/delete', '10412', '115', '1558317464', '1558317464'); + + + +COMMIT; \ No newline at end of file diff --git a/doc/database/upgrade/v1.1.26.sql b/doc/database/upgrade/v1.1.26.sql new file mode 100644 index 0000000..420e21d --- /dev/null +++ b/doc/database/upgrade/v1.1.26.sql @@ -0,0 +1,6 @@ + +START TRANSACTION; + +ALTER TABLE `yoshop_user_grade_log` ADD COLUMN `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '管理员备注' AFTER `change_type`; + +COMMIT; \ No newline at end of file diff --git a/doc/database/upgrade/v1.1.27.sql b/doc/database/upgrade/v1.1.27.sql new file mode 100644 index 0000000..8b9ee6f --- /dev/null +++ b/doc/database/upgrade/v1.1.27.sql @@ -0,0 +1,117 @@ + +START TRANSACTION; + +CREATE TABLE `yoshop_bargain_active` ( + `active_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '砍价活动id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `start_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动开始时间', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动结束时间', + `expiryt_time` int(11) unsigned NOT NULL DEFAULT '1' COMMENT '砍价有效期(单位:小时)', + `floor_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '砍价底价', + `peoples` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '帮砍人数', + `is_self_cut` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '可自砍一刀(0禁止 1允许)', + `is_floor_buy` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '必须底价购买(0否 1是)', + `share_title` varchar(500) NOT NULL DEFAULT '' COMMENT '分享标题', + `prompt_words` varchar(500) NOT NULL DEFAULT '' COMMENT '砍价助力语', + `actual_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动销量(实际的)', + `initial_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '虚拟销量', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序(数字越小越靠前)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '活动状态(1启用 0禁用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`active_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='砍价活动表'; + + +CREATE TABLE `yoshop_bargain_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='砍价活动设置表'; + + +CREATE TABLE `yoshop_bargain_task` ( + `task_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '砍价任务id', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '砍价活动id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id(发起人)', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '' COMMENT '商品sku标识', + `goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品原价', + `floor_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '砍价底价', + `peoples` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '帮砍人数', + `cut_people` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '已砍人数', + `section` text NOT NULL COMMENT '砍价金额区间', + `cut_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '已砍金额', + `actual_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际购买金额', + `is_floor` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否已砍到底价(0否 1是)', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '任务截止时间', + `is_buy` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否购买(0未购买 1已购买)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '任务状态 (0已结束 1砍价中)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`task_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='砍价任务表'; + + +CREATE TABLE `yoshop_bargain_task_help` ( + `help_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '砍价活动id', + `task_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '砍价任务id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `is_creater` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否为发起人(0否 1是)', + `cut_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '砍掉的金额', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`help_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='砍价任务助力记录表'; + + +ALTER TABLE `yoshop_order` +ADD COLUMN `order_source` tinyint(3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '订单来源(10普通订单 20砍价订单)' AFTER `is_comment`; + + +ALTER TABLE `yoshop_order` +ADD COLUMN `order_source_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '来源记录id' AFTER `order_source`; + + +INSERT INTO `yoshop_store_access` VALUES ('10426', '砍价活动', 'apps.bargain', '10074', '100', '1559615418', '1559615441'); +INSERT INTO `yoshop_store_access` VALUES ('10427', '砍价活动管理', 'apps.bargain.active', '10426', '100', '1559615566', '1559615566'); +INSERT INTO `yoshop_store_access` VALUES ('10428', '砍价活动列表', 'apps.bargain.active/index', '10427', '100', '1559615601', '1559615601'); +INSERT INTO `yoshop_store_access` VALUES ('10429', '新增砍价活动', 'apps.bargain.active/add', '10427', '105', '1559615601', '1559615601'); +INSERT INTO `yoshop_store_access` VALUES ('10430', '编辑砍价活动', 'apps.bargain.active/edit', '10427', '110', '1559615601', '1559615601'); +INSERT INTO `yoshop_store_access` VALUES ('10431', '删除砍价活动', 'apps.bargain.active/delete', '10427', '115', '1559615601', '1559615601'); +INSERT INTO `yoshop_store_access` VALUES ('10432', '砍价记录', 'apps.bargain.task', '10426', '105', '1559615788', '1559615788'); +INSERT INTO `yoshop_store_access` VALUES ('10433', '砍价记录列表', 'apps.bargain.task/index', '10432', '100', '1559615815', '1559615815'); +INSERT INTO `yoshop_store_access` VALUES ('10434', '砍价助力榜', 'apps.bargain.task/help', '10432', '105', '1559615850', '1559615850'); +INSERT INTO `yoshop_store_access` VALUES ('10435', '删除砍价记录', 'apps.bargain.task/delete', '10432', '110', '1559615878', '1559615878'); +INSERT INTO `yoshop_store_access` VALUES ('10436', '砍价设置', 'apps.bargain.setting/index', '10426', '110', '1559615946', '1559615979'); + +INSERT INTO `yoshop_store_access` VALUES ('10437', '复制主商城商品', 'apps.sharing.goods/copy_master', '10306', '112', '1559615946', '1559615979'); + + + + + +UPDATE `yoshop_store_access` SET `sort`='105' WHERE (`access_id`='10021'); +UPDATE `yoshop_store_access` SET `sort`='110' WHERE (`access_id`='10022'); +UPDATE `yoshop_store_access` SET `sort`='115' WHERE (`access_id`='10023'); +UPDATE `yoshop_store_access` SET `sort`='120' WHERE (`access_id`='10024'); +UPDATE `yoshop_store_access` SET `sort`='125' WHERE (`access_id`='10025'); + +UPDATE `yoshop_store_access` SET `sort`='105' WHERE (`access_id`='10307'); +UPDATE `yoshop_store_access` SET `sort`='110' WHERE (`access_id`='10308'); +UPDATE `yoshop_store_access` SET `sort`='115' WHERE (`access_id`='10309'); +UPDATE `yoshop_store_access` SET `sort`='120' WHERE (`access_id`='10310'); +UPDATE `yoshop_store_access` SET `sort`='125' WHERE (`access_id`='10311'); + + + + +COMMIT; \ No newline at end of file diff --git a/doc/database/upgrade/v1.1.29.sql b/doc/database/upgrade/v1.1.29.sql new file mode 100644 index 0000000..ac71d37 --- /dev/null +++ b/doc/database/upgrade/v1.1.29.sql @@ -0,0 +1,83 @@ + +ALTER TABLE `yoshop_user` +ADD COLUMN `points` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户可用积分' AFTER `balance`; + + + +CREATE TABLE `yoshop_user_points_log` ( + `log_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `value` int(11) NOT NULL DEFAULT '0.00' COMMENT '变动数量', + `describe` varchar(500) NOT NULL DEFAULT '' COMMENT '描述/说明', + `remark` varchar(500) NOT NULL DEFAULT '' COMMENT '管理员备注', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序商城id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='用户积分变动明细表'; + + + +ALTER TABLE `yoshop_order` +ADD COLUMN `points_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '积分抵扣金额' AFTER `coupon_money`; + +ALTER TABLE `yoshop_order` +ADD COLUMN `points_num` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '积分抵扣数量' AFTER `points_money`; + +ALTER TABLE `yoshop_order` +ADD COLUMN `points_bonus` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '赠送的积分数量' AFTER `order_status`; + +ALTER TABLE `yoshop_order` +CHANGE COLUMN `is_user_expend` `is_settled` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '订单是否已结算(0未结算 1已结算)' AFTER `points_bonus`; + + + +ALTER TABLE `yoshop_sharing_order` +ADD COLUMN `points_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '积分抵扣金额' AFTER `coupon_money`; + +ALTER TABLE `yoshop_sharing_order` +ADD COLUMN `points_num` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '积分抵扣数量' AFTER `points_money`; + +ALTER TABLE `yoshop_sharing_order` +ADD COLUMN `points_bonus` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '赠送的积分数量' AFTER `order_status`; + +ALTER TABLE `yoshop_sharing_order` +CHANGE COLUMN `is_user_expend` `is_settled` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '订单是否已结算(0未结算 1已结算)' AFTER `points_bonus`; + + + +ALTER TABLE `yoshop_goods` +ADD COLUMN `is_points_gift` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否开启积分赠送(1开启 0关闭)' AFTER `delivery_id`; + +ALTER TABLE `yoshop_goods` +ADD COLUMN `is_points_discount` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否允许使用积分抵扣(1允许 0不允许)' AFTER `is_points_gift`; + +ALTER TABLE `yoshop_order_goods` +ADD COLUMN `points_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '积分金额' AFTER `coupon_money`, +ADD COLUMN `points_num` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '积分抵扣数量' AFTER `points_money`, +ADD COLUMN `points_bonus` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '赠送的积分数量' AFTER `points_num`; + + + +ALTER TABLE `yoshop_sharing_goods` +ADD COLUMN `is_points_gift` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否开启积分赠送(1开启 0关闭)' AFTER `delivery_id`; + +ALTER TABLE `yoshop_sharing_goods` +ADD COLUMN `is_points_discount` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否允许使用积分抵扣(1允许 0不允许)' AFTER `is_points_gift`; + +ALTER TABLE `yoshop_sharing_order_goods` +ADD COLUMN `points_money` decimal(10,2) UNSIGNED NOT NULL DEFAULT 0.00 COMMENT '积分金额' AFTER `coupon_money`, +ADD COLUMN `points_num` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '积分抵扣数量' AFTER `points_money`, +ADD COLUMN `points_bonus` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '赠送的积分数量' AFTER `points_num`; + + +UPDATE `yoshop_store_access` SET `sort`='125' WHERE (`access_id`='10370'); + + +INSERT INTO `yoshop_store_access` VALUES ('10443', '活跃用户', 'market.push/user', '10441', '105', '1561080384', '1561080384'); +INSERT INTO `yoshop_store_access` VALUES ('10442', '发送消息', 'market.push/send', '10441', '100', '1561080384', '1561080384'); +INSERT INTO `yoshop_store_access` VALUES ('10441', '消息推送', 'market.push', '10052', '120', '1561080292', '1561080292'); +INSERT INTO `yoshop_store_access` VALUES ('10440', '积分明细', 'market.points/log', '10438', '105', '1561080384', '1561080384'); +INSERT INTO `yoshop_store_access` VALUES ('10439', '积分设置', 'market.points/setting', '10438', '100', '1561080384', '1561080384'); +INSERT INTO `yoshop_store_access` VALUES ('10438', '积分管理', 'market.points', '10052', '115', '1561080292', '1561080292'); + + diff --git a/doc/database/upgrade/v1.1.3.sql b/doc/database/upgrade/v1.1.3.sql new file mode 100644 index 0000000..3950159 --- /dev/null +++ b/doc/database/upgrade/v1.1.3.sql @@ -0,0 +1,148 @@ + +# 用户记录表:新增用户总消费金额 +ALTER TABLE `yoshop_user` +ADD COLUMN `money` decimal(10,2) unsigned NOT NULL DEFAULT 0 COMMENT '用户总消费金额' AFTER `address_id`; + + +# 订单商品记录表:新增实际付款价 +ALTER TABLE `yoshop_order_goods` +MODIFY COLUMN `total_price` decimal(10,2) unsigned NOT NULL DEFAULT 0.00 COMMENT '商品总价(数量×单价)' AFTER `total_num`, +ADD COLUMN `total_pay_price` decimal(10,2) unsigned NOT NULL DEFAULT 0.00 COMMENT '实际付款价(折扣和优惠后)' AFTER `total_price`; + + +# 分销商申请记录表 +CREATE TABLE `yoshop_dealer_apply` ( + `apply_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `real_name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名', + `mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号', + `referee_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '推荐人用户id', + `apply_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '申请方式(10需后台审核 20无需审核)', + `apply_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '申请时间', + `apply_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '审核状态 (10待审核 20审核通过 30驳回)', + `audit_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '审核时间', + `reject_reason` varchar(500) NOT NULL DEFAULT '' COMMENT '驳回原因', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`apply_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商申请记录表'; + + +# 分销商资金明细表 +CREATE TABLE `yoshop_dealer_capital` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id', + `flow_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '资金流动类型 (10佣金收入 20提现支出)', + `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '金额', + `describe` varchar(500) NOT NULL DEFAULT '' COMMENT '描述', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商资金明细表'; + + +# 销商订单记录表 +CREATE TABLE `yoshop_dealer_order` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id (买家)', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号(废弃,勿用)', + `order_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '订单总金额(不含运费)', + `first_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id(一级)', + `second_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id(二级)', + `third_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id(三级)', + `first_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(一级)', + `second_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(二级)', + `third_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '分销佣金(三级)', + `is_settled` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否已结算佣金 (0未结算 1已结算)', + `settle_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '结算时间', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `order_id` (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商订单记录表'; + + +# 分销商推荐关系表 +CREATE TABLE `yoshop_dealer_referee` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `dealer_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id(被推荐人)', + `level` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '推荐关系层级(1,2,3)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `dealer_id` (`dealer_id`), + KEY `user_id` (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商推荐关系表'; + + +# 分销商设置表 +CREATE TABLE `yoshop_dealer_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分销商设置表'; + + +# 分销商用户记录表 +CREATE TABLE `yoshop_dealer_user` ( + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id', + `real_name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名', + `mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号', + `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '当前可提现佣金', + `freeze_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '已冻结佣金', + `total_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '累积提现佣金', + `referee_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '推荐人用户id', + `first_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成员数量(一级)', + `second_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成员数量(二级)', + `third_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成员数量(三级)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分销商用户记录表'; + + +# 分销商提现明细表 +CREATE TABLE `yoshop_dealer_withdraw` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分销商用户id', + `money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '提现金额', + `pay_type` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '打款方式 (10微信 20支付宝 30银行卡)', + `alipay_name` varchar(30) NOT NULL DEFAULT '' COMMENT '支付宝姓名', + `alipay_account` varchar(30) NOT NULL DEFAULT '' COMMENT '支付宝账号', + `bank_name` varchar(30) NOT NULL DEFAULT '' COMMENT '开户行名称', + `bank_account` varchar(30) NOT NULL DEFAULT '' COMMENT '银行开户名', + `bank_card` varchar(30) NOT NULL DEFAULT '' COMMENT '银行卡号', + `apply_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '申请状态 (10待审核 20审核通过 30驳回 40已打款)', + `audit_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '审核时间', + `reject_reason` varchar(500) NOT NULL DEFAULT '' COMMENT '驳回原因', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='分销商提现明细表'; + + +# 小程序form_id记录表 +CREATE TABLE `yoshop_wxapp_formid` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `form_id` varchar(50) NOT NULL DEFAULT '' COMMENT '小程序form_id', + `expiry_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '过期时间', + `is_used` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否已使用', + `used_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '使用时间', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='小程序form_id记录表'; \ No newline at end of file diff --git a/doc/database/upgrade/v1.1.32.sql b/doc/database/upgrade/v1.1.32.sql new file mode 100644 index 0000000..d79a9ce --- /dev/null +++ b/doc/database/upgrade/v1.1.32.sql @@ -0,0 +1,119 @@ + + + +ALTER TABLE `yoshop_order_goods` +ADD COLUMN `goods_source_id` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '来源记录id' AFTER `user_id`; + + +ALTER TABLE `yoshop_order` +MODIFY COLUMN `order_source` tinyint(3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '订单来源(10普通订单 20砍价订单 30秒杀订单)' AFTER `is_comment`; + + +UPDATE `yoshop_store_access` SET `sort`='105' WHERE (`access_id`='10300'); +UPDATE `yoshop_store_access` SET `sort`='110' WHERE (`access_id`='10426'); +UPDATE `yoshop_store_access` SET `sort`='120' WHERE (`access_id`='10400'); + + + +INSERT INTO `yoshop_store_access` VALUES ('10444', '整点秒杀', 'apps.sharp', '10074', '115', '1564449650', '1564449650'); + +INSERT INTO `yoshop_store_access` VALUES ('10445', '秒杀商品', 'apps.sharp.goods', '10444', '100', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10446', '商品列表', 'apps.sharp.goods/index', '10445', '100', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10447', '新增商品', 'apps.sharp.goods/add', '10445', '105', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10448', '编辑商品', 'apps.sharp.goods/edit', '10445', '110', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10449', '删除商品', 'apps.sharp.goods/delete', '10445', '115', '1564449650', '1564449650'); + +INSERT INTO `yoshop_store_access` VALUES ('10450', '活动会场', 'apps.sharp.active', '10444', '105', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10451', '会场列表', 'apps.sharp.active/index', '10450', '100', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10452', '新增会场', 'apps.sharp.active/add', '10450', '105', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10453', '修改活动状态', 'apps.sharp.active/state', '10450', '110', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10454', '删除会场', 'apps.sharp.active/delete', '10450', '115', '1564449650', '1564449650'); + +INSERT INTO `yoshop_store_access` VALUES ('10455', '场次管理', 'apps.sharp.active_time', '10450', '120', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10456', '场次列表', 'apps.sharp.active_time/index', '10455', '100', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10457', '新增场次', 'apps.sharp.active_time/add', '10455', '105', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10458', '编辑场次', 'apps.sharp.active_time/edit', '10455', '110', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10459', '修改活动状态', 'apps.sharp.active_time/state', '10455', '115', '1564449650', '1564449650'); +INSERT INTO `yoshop_store_access` VALUES ('10460', '删除场次', 'apps.sharp.active_time/delete', '10455', '120', '1564449650', '1564449650'); + +INSERT INTO `yoshop_store_access` VALUES ('10461', '基础设置', 'apps.sharp.setting/index', '10444', '125', '1564449650', '1564449650'); + + + +CREATE TABLE `yoshop_sharp_active` ( + `active_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '活动会场ID', + `active_date` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动日期', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '活动状态(0禁用 1启用)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`active_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-活动会场表'; + + +CREATE TABLE `yoshop_sharp_active_goods` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动会场ID', + `active_time_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动场次ID', + `sharp_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '秒杀商品ID', + `sales_actual` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '实际销量', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-活动会场与商品关联表'; + + +CREATE TABLE `yoshop_sharp_active_time` ( + `active_time_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '场次ID', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '活动会场ID', + `active_time` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '场次时间(0点-23点)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '活动状态(0禁用 1启用)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`active_time_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-活动会场场次表'; + + +CREATE TABLE `yoshop_sharp_goods` ( + `sharp_goods_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '秒杀商品ID', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品ID', + `deduct_stock_type` tinyint(3) unsigned DEFAULT '10' COMMENT '库存计算方式(10下单减库存 20付款减库存)', + `limit_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '限购数量', + `seckill_stock` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品库存总量', + `total_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '累积销量', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品排序(数字越小越靠前)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '商品状态(0下架 1上架)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`sharp_goods_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-商品表'; + +CREATE TABLE `yoshop_sharp_goods_sku` ( + `goods_sku_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品规格id', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '0' COMMENT '商品sku记录索引 (由规格id组成)', + `sharp_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '秒杀商品id', + `seckill_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品价格', + `seckill_stock` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '秒杀库存数量', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`goods_sku_id`), + UNIQUE KEY `sku_idx` (`sharp_goods_id`,`spec_sku_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='整点秒杀-秒杀商品sku信息表'; + +CREATE TABLE `yoshop_sharp_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='整点秒杀设置表'; + + + diff --git a/doc/database/upgrade/v1.1.35.sql b/doc/database/upgrade/v1.1.35.sql new file mode 100644 index 0000000..817a5df --- /dev/null +++ b/doc/database/upgrade/v1.1.35.sql @@ -0,0 +1,66 @@ + + +UPDATE `yoshop_region` SET `name`='省直辖县级行政区划',`merger_name`='中国,湖北省,省直辖县级行政区划' WHERE (`id`='1822'); +UPDATE `yoshop_region` SET `merger_name`='中国,湖北省,省直辖县级行政区划,仙桃市' WHERE (`id`='1823'); +UPDATE `yoshop_region` SET `merger_name`='中国,湖北省,省直辖县级行政区划,潜江市' WHERE (`id`='1824'); +UPDATE `yoshop_region` SET `merger_name`='中国,湖北省,省直辖县级行政区划,天门市' WHERE (`id`='1825'); +UPDATE `yoshop_region` SET `merger_name`='中国,湖北省,省直辖县级行政区划,神农架林区' WHERE (`id`='1826'); + + + + +UPDATE `yoshop_region` SET `shortname`='县', `name`='县', `merger_name`='中国,重庆,县', `pinyin`='xian' WHERE (`id`='2363'); +UPDATE `yoshop_region` SET `id`='2325', `pid`='2324', `shortname`='万州', `name`='万州区', `merger_name`='中国,重庆,重庆市,万州区', `level`='3', `pinyin`='wanzhou', `code`='023', `zip_code`='404000', `first`='W', `lng`='108.40869', `lat`='30.80788' WHERE (`id`='2325'); +UPDATE `yoshop_region` SET `id`='2326', `pid`='2324', `shortname`='涪陵', `name`='涪陵区', `merger_name`='中国,重庆,重庆市,涪陵区', `level`='3', `pinyin`='fuling', `code`='023', `zip_code`='408000', `first`='F', `lng`='107.39007', `lat`='29.70292' WHERE (`id`='2326'); +UPDATE `yoshop_region` SET `id`='2327', `pid`='2324', `shortname`='渝中', `name`='渝中区', `merger_name`='中国,重庆,重庆市,渝中区', `level`='3', `pinyin`='yuzhong', `code`='023', `zip_code`='400010', `first`='Y', `lng`='106.56901', `lat`='29.55279' WHERE (`id`='2327'); +UPDATE `yoshop_region` SET `id`='2328', `pid`='2324', `shortname`='大渡口', `name`='大渡口区', `merger_name`='中国,重庆,重庆市,大渡口区', `level`='3', `pinyin`='dadukou', `code`='023', `zip_code`='400080', `first`='D', `lng`='106.48262', `lat`='29.48447' WHERE (`id`='2328'); +UPDATE `yoshop_region` SET `id`='2329', `pid`='2324', `shortname`='江北', `name`='江北区', `merger_name`='中国,重庆,重庆市,江北区', `level`='3', `pinyin`='jiangbei', `code`='023', `zip_code`='400020', `first`='J', `lng`='106.57434', `lat`='29.60658' WHERE (`id`='2329'); +UPDATE `yoshop_region` SET `id`='2330', `pid`='2324', `shortname`='沙坪坝', `name`='沙坪坝区', `merger_name`='中国,重庆,重庆市,沙坪坝区', `level`='3', `pinyin`='shapingba', `code`='023', `zip_code`='400030', `first`='S', `lng`='106.45752', `lat`='29.54113' WHERE (`id`='2330'); +UPDATE `yoshop_region` SET `id`='2331', `pid`='2324', `shortname`='九龙坡', `name`='九龙坡区', `merger_name`='中国,重庆,重庆市,九龙坡区', `level`='3', `pinyin`='jiulongpo', `code`='023', `zip_code`='400050', `first`='J', `lng`='106.51107', `lat`='29.50197' WHERE (`id`='2331'); +UPDATE `yoshop_region` SET `id`='2332', `pid`='2324', `shortname`='南岸', `name`='南岸区', `merger_name`='中国,重庆,重庆市,南岸区', `level`='3', `pinyin`='nan\'an', `code`='023', `zip_code`='400064', `first`='N', `lng`='106.56347', `lat`='29.52311' WHERE (`id`='2332'); +UPDATE `yoshop_region` SET `id`='2333', `pid`='2324', `shortname`='北碚', `name`='北碚区', `merger_name`='中国,重庆,重庆市,北碚区', `level`='3', `pinyin`='beibei', `code`='023', `zip_code`='400700', `first`='B', `lng`='106.39614', `lat`='29.80574' WHERE (`id`='2333'); +UPDATE `yoshop_region` SET `id`='2334', `pid`='2324', `shortname`='綦江', `name`='綦江区', `merger_name`='中国,重庆,重庆市,綦江区', `level`='3', `pinyin`='qijiang', `code`='023', `zip_code`='400800', `first`=NULL, `lng`='106.926779', `lat`='28.960656' WHERE (`id`='2334'); +UPDATE `yoshop_region` SET `id`='2335', `pid`='2324', `shortname`='大足', `name`='大足区', `merger_name`='中国,重庆,重庆市,大足区', `level`='3', `pinyin`='dazu', `code`='023', `zip_code`='400900', `first`='D', `lng`='105.768121', `lat`='29.484025' WHERE (`id`='2335'); +UPDATE `yoshop_region` SET `id`='2336', `pid`='2324', `shortname`='渝北', `name`='渝北区', `merger_name`='中国,重庆,重庆市,渝北区', `level`='3', `pinyin`='yubei', `code`='023', `zip_code`='401120', `first`='Y', `lng`='106.6307', `lat`='29.7182' WHERE (`id`='2336'); +UPDATE `yoshop_region` SET `id`='2337', `pid`='2324', `shortname`='巴南', `name`='巴南区', `merger_name`='中国,重庆,重庆市,巴南区', `level`='3', `pinyin`='banan', `code`='023', `zip_code`='401320', `first`='B', `lng`='106.52365', `lat`='29.38311' WHERE (`id`='2337'); +UPDATE `yoshop_region` SET `id`='2338', `pid`='2324', `shortname`='黔江', `name`='黔江区', `merger_name`='中国,重庆,重庆市,黔江区', `level`='3', `pinyin`='qianjiang', `code`='023', `zip_code`='409700', `first`='Q', `lng`='108.7709', `lat`='29.5332' WHERE (`id`='2338'); +UPDATE `yoshop_region` SET `id`='2339', `pid`='2324', `shortname`='长寿', `name`='长寿区', `merger_name`='中国,重庆,重庆市,长寿区', `level`='3', `pinyin`='changshou', `code`='023', `zip_code`='401220', `first`='C', `lng`='107.08166', `lat`='29.85359' WHERE (`id`='2339'); +UPDATE `yoshop_region` SET `id`='2340', `pid`='2324', `shortname`='江津', `name`='江津区', `merger_name`='中国,重庆,重庆市,江津区', `level`='3', `pinyin`='jiangjin', `code`='023', `zip_code`='402260', `first`='J', `lng`='106.25912', `lat`='29.29008' WHERE (`id`='2340'); +UPDATE `yoshop_region` SET `id`='2341', `pid`='2324', `shortname`='合川', `name`='合川区', `merger_name`='中国,重庆,重庆市,合川区', `level`='3', `pinyin`='hechuan', `code`='023', `zip_code`='401520', `first`='H', `lng`='106.27633', `lat`='29.97227' WHERE (`id`='2341'); +UPDATE `yoshop_region` SET `id`='2342', `pid`='2324', `shortname`='永川', `name`='永川区', `merger_name`='中国,重庆,重庆市,永川区', `level`='3', `pinyin`='yongchuan', `code`='023', `zip_code`='402160', `first`='Y', `lng`='105.927', `lat`='29.35593' WHERE (`id`='2342'); +UPDATE `yoshop_region` SET `id`='2343', `pid`='2324', `shortname`='南川', `name`='南川区', `merger_name`='中国,重庆,重庆市,南川区', `level`='3', `pinyin`='nanchuan', `code`='023', `zip_code`='408400', `first`='N', `lng`='107.09936', `lat`='29.15751' WHERE (`id`='2343'); +UPDATE `yoshop_region` SET `id`='2344', `pid`='2324', `shortname`='璧山', `name`='璧山区', `merger_name`='中国,重庆,重庆市,璧山区', `level`='3', `pinyin`='bishan', `code`='023', `zip_code`='402760', `first`=NULL, `lng`='106.231126', `lat`='29.593581' WHERE (`id`='2344'); +UPDATE `yoshop_region` SET `id`='2345', `pid`='2324', `shortname`='铜梁', `name`='铜梁区', `merger_name`='中国,重庆,重庆市,铜梁区', `level`='3', `pinyin`='tongliang', `code`='023', `zip_code`='402560', `first`='T', `lng`='106.054948', `lat`='29.839944' WHERE (`id`='2345'); +UPDATE `yoshop_region` SET `id`='2346', `pid`='2324', `shortname`='潼南', `name`='潼南区', `merger_name`='中国,重庆,重庆市,潼南区', `level`='3', `pinyin`='tongnan', `code`='023', `zip_code`='402660', `first`=NULL, `lng`='105.84005', `lat`='30.1912' WHERE (`id`='2346'); +UPDATE `yoshop_region` SET `id`='2347', `pid`='2324', `shortname`='荣昌', `name`='荣昌区', `merger_name`='中国,重庆,重庆市,荣昌区', `level`='3', `pinyin`='rongchang', `code`='023', `zip_code`='402460', `first`='R', `lng`='105.59442', `lat`='29.40488' WHERE (`id`='2347'); +UPDATE `yoshop_region` SET `id`='2348', `pid`='2324', `shortname`='梁平', `name`='梁平区', `merger_name`='中国,重庆,重庆市,梁平区', `level`='3', `pinyin`='liangping', `code`='023', `zip_code`='405200', `first`='L', `lng`='107.79998', `lat`='30.67545' WHERE (`id`='2348'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='城口', `name`='城口县', `merger_name`='中国,重庆,重庆市,城口县', `level`='3', `pinyin`='chengkou', `code`='023', `zip_code`='405900', `first`='C', `lng`='108.66513', `lat`='31.94801' WHERE (`id`='2349'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='丰都', `name`='丰都县', `merger_name`='中国,重庆,重庆市,丰都县', `level`='3', `pinyin`='fengdu', `code`='023', `zip_code`='408200', `first`='F', `lng`='107.73098', `lat`='29.86348' WHERE (`id`='2350'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='垫江', `name`='垫江县', `merger_name`='中国,重庆,重庆市,垫江县', `level`='3', `pinyin`='dianjiang', `code`='023', `zip_code`='408300', `first`='D', `lng`='107.35446', `lat`='30.33359' WHERE (`id`='2351'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='武隆', `name`='武隆县', `merger_name`='中国,重庆,重庆市,武隆县', `level`='3', `pinyin`='wulong', `code`='023', `zip_code`='408500', `first`='W', `lng`='107.7601', `lat`='29.32548' WHERE (`id`='2352'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='忠县', `name`='忠县', `merger_name`='中国,重庆,重庆市,忠县', `level`='3', `pinyin`='zhongxian', `code`='023', `zip_code`='404300', `first`='Z', `lng`='108.03689', `lat`='30.28898' WHERE (`id`='2353'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='开县', `name`='开县', `merger_name`='中国,重庆,重庆市,开县', `level`='3', `pinyin`='kaixian', `code`='023', `zip_code`='405400', `first`='K', `lng`='108.39306', `lat`='31.16095' WHERE (`id`='2354'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='云阳', `name`='云阳县', `merger_name`='中国,重庆,重庆市,云阳县', `level`='3', `pinyin`='yunyang', `code`='023', `zip_code`='404500', `first`='Y', `lng`='108.69726', `lat`='30.93062' WHERE (`id`='2355'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='奉节', `name`='奉节县', `merger_name`='中国,重庆,重庆市,奉节县', `level`='3', `pinyin`='fengjie', `code`='023', `zip_code`='404600', `first`='F', `lng`='109.46478', `lat`='31.01825' WHERE (`id`='2356'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='巫山', `name`='巫山县', `merger_name`='中国,重庆,重庆市,巫山县', `level`='3', `pinyin`='wushan', `code`='023', `zip_code`='404700', `first`='W', `lng`='109.87814', `lat`='31.07458' WHERE (`id`='2357'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='巫溪', `name`='巫溪县', `merger_name`='中国,重庆,重庆市,巫溪县', `level`='3', `pinyin`='wuxi', `code`='023', `zip_code`='405800', `first`='W', `lng`='109.63128', `lat`='31.39756' WHERE (`id`='2358'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='石柱', `name`='石柱土家族自治县', `merger_name`='中国,重庆,重庆市,石柱土家族自治县', `level`='3', `pinyin`='shizhu', `code`='023', `zip_code`='409100', `first`='S', `lng`='108.11389', `lat`='30.00054' WHERE (`id`='2359'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='秀山', `name`='秀山土家族苗族自治县', `merger_name`='中国,重庆,重庆市,秀山土家族苗族自治县', `level`='3', `pinyin`='xiushan', `code`='023', `zip_code`='409900', `first`='X', `lng`='108.98861', `lat`='28.45062' WHERE (`id`='2360'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='酉阳', `name`='酉阳土家族苗族自治县', `merger_name`='中国,重庆,重庆市,酉阳土家族苗族自治县', `level`='3', `pinyin`='youyang', `code`='023', `zip_code`='409800', `first`='Y', `lng`='108.77212', `lat`='28.8446' WHERE (`id`='2361'); +UPDATE `yoshop_region` SET `pid`='2363', `shortname`='彭水', `name`='彭水苗族土家族自治县', `merger_name`='中国,重庆,重庆市,彭水苗族土家族自治县', `level`='3', `pinyin`='pengshui', `code`='023', `zip_code`='409600', `first`='P', `lng`='108.16638', `lat`='29.29516' WHERE (`id`='2362'); +DELETE FROM `yoshop_region` WHERE (`id`='2364'); +DELETE FROM `yoshop_region` WHERE (`id`='2365'); +DELETE FROM `yoshop_region` WHERE (`id`='2366'); + + + + +ALTER TABLE `yoshop_goods_sku` +MODIFY COLUMN `goods_sales` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '商品销量(废弃)' AFTER `stock_num`; + +ALTER TABLE `yoshop_sharing_goods_sku` +MODIFY COLUMN `goods_sales` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '商品销量(废弃)' AFTER `stock_num`; + + + + diff --git a/doc/database/upgrade/v1.1.36.sql b/doc/database/upgrade/v1.1.36.sql new file mode 100644 index 0000000..3947674 --- /dev/null +++ b/doc/database/upgrade/v1.1.36.sql @@ -0,0 +1,15 @@ + + + +UPDATE `yoshop_region` SET `name`='直辖县级',`merger_name`='中国,湖北省,直辖县级' WHERE (`id`='1822'); +UPDATE `yoshop_region` SET `merger_name`='中国,湖北省,直辖县级,仙桃市' WHERE (`id`='1823'); +UPDATE `yoshop_region` SET `merger_name`='中国,湖北省,直辖县级,潜江市' WHERE (`id`='1824'); +UPDATE `yoshop_region` SET `merger_name`='中国,湖北省,直辖县级,天门市' WHERE (`id`='1825'); +UPDATE `yoshop_region` SET `merger_name`='中国,湖北省,直辖县级,神农架林区' WHERE (`id`='1826'); + + +UPDATE `yoshop_region` SET `name`='吐鲁番市', `merger_name`='中国,新疆维吾尔自治区,吐鲁番市' WHERE (`id`='3221'); +UPDATE `yoshop_region` SET `name`='哈密市', `merger_name`='中国,新疆维吾尔自治区,哈密市' WHERE (`id`='3225'); + + + diff --git a/doc/database/upgrade/v1.1.37.sql b/doc/database/upgrade/v1.1.37.sql new file mode 100644 index 0000000..231a108 --- /dev/null +++ b/doc/database/upgrade/v1.1.37.sql @@ -0,0 +1,4 @@ + + + +INSERT INTO `yoshop_store_access` VALUES ('11003', '数据统计', 'statistics.data/index', '0', '138', '1572507520', '1572507520'); diff --git a/doc/database/upgrade/v1.1.4.sql b/doc/database/upgrade/v1.1.4.sql new file mode 100644 index 0000000..03291f7 --- /dev/null +++ b/doc/database/upgrade/v1.1.4.sql @@ -0,0 +1,5 @@ + +# 用户记录表:新增伪删除字段 +ALTER TABLE `yoshop_user` +ADD COLUMN `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除' AFTER `money`; + diff --git a/doc/database/upgrade/v1.1.6.sql b/doc/database/upgrade/v1.1.6.sql new file mode 100644 index 0000000..c66bbca --- /dev/null +++ b/doc/database/upgrade/v1.1.6.sql @@ -0,0 +1,219 @@ + +# 微信小程序记录表:删除冗余字段 +ALTER TABLE `yoshop_wxapp` +DROP COLUMN `app_name`, +DROP COLUMN `is_service`, +DROP COLUMN `service_image_id`, +DROP COLUMN `is_phone`, +DROP COLUMN `phone_no`, +DROP COLUMN `phone_image_id`; + + +# 微信小程序记录表:新增是否回收字段 +ALTER TABLE `yoshop_wxapp` +ADD COLUMN `is_recycle` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否回收' AFTER `apikey`; + + +# 微信小程序记录表:新增伪删除字段 +ALTER TABLE `yoshop_wxapp` +ADD COLUMN `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除' AFTER `is_recycle`; + + +# 超管用户记录表 +DROP TABLE IF EXISTS `yoshop_admin_user`; +CREATE TABLE `yoshop_admin_user` ( + `admin_user_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名', + `password` varchar(255) NOT NULL DEFAULT '' COMMENT '登录密码', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`admin_user_id`), + KEY `user_name` (`user_name`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='超管用户记录表'; + +INSERT INTO `yoshop_admin_user` (`user_name`, `password`, `create_time`, `update_time`) VALUES ('admin', '9ae7b2e6f25c907a1fc81b503b16e25f', '1529926348', '1540194026'); + + +# 商家用户记录表:新增是否为超级管理员字段 +ALTER TABLE `yoshop_store_user` +ADD COLUMN `is_super` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否为超级管理员' AFTER `password`; + + +# 商家用户记录表:新增伪删除字段 +ALTER TABLE `yoshop_store_user` +ADD COLUMN `is_delete` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否删除' AFTER `is_super`; + + +# 商家用户记录表:新增姓名字段 +ALTER TABLE `yoshop_store_user` +ADD COLUMN `real_name` varchar(255) NOT NULL DEFAULT '' COMMENT '姓名' AFTER `password`; + + +# 商家用户记录表:设置默认姓名 +UPDATE `yoshop_store_user` SET `real_name` = '管理员' WHERE 1; + + +# 商家用户记录表:删除用户名唯一索引 +ALTER TABLE `yoshop_store_user` +DROP INDEX `user_name` , +ADD INDEX `user_name` (`user_name`); + + +# 商家用户权限表 +CREATE TABLE `yoshop_store_access` ( + `access_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT '权限名称', + `url` varchar(255) NOT NULL DEFAULT '' COMMENT '权限url', + `parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父级id', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '100' COMMENT '排序(数字越小越靠前)', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`access_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10108 DEFAULT CHARSET=utf8 COMMENT='商家用户权限表'; + + +INSERT INTO `yoshop_store_access` VALUES ('10001', '首页', 'index/index', '0', '100', '1540628721', '1540781975'); +INSERT INTO `yoshop_store_access` VALUES ('10002', '管理员', 'store', '0', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10003', '管理员管理', 'store.user', '10002', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10004', '管理员列表', 'store.user/index', '10003', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10005', '添加管理员', 'store.user/add', '10003', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10006', '编辑管理员', 'store.user/edit', '10003', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10007', '删除管理员', 'store.user/delete', '10003', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10008', '角色管理', 'store.role', '10002', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10009', '角色列表', 'store.role/index', '10008', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10010', '添加角色', 'store.role/add', '10008', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10011', '编辑角色', 'store.role/edit', '10008', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10012', '删除角色', 'store.role/delete', '10008', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10013', '权限管理', 'store.access', '10002', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10014', '权限列表', 'store.access/index', '10013', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10015', '添加权限', 'store.access/add', '10013', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10016', '编辑权限', 'store.access/edit', '10013', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10017', '删除权限', 'store.access/delete', '10013', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10018', '商品管理', 'goods', '0', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10019', '商品管理', 'goods', '10018', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10020', '商品列表', 'goods/index', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10021', '添加商品', 'goods/add', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10022', '编辑商品', 'goods/edit', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10023', '复制商品', 'goods/copy', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10024', '删除商品', 'goods/delete', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10025', '商品上下架', 'goods/state', '10019', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10026', '商品分类', 'goods.category', '10018', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10027', '分类列表', 'goods.category/index', '10026', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10028', '添加分类', 'goods.category/add', '10026', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10029', '编辑分类', 'goods.category/edit', '10026', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10030', '删除分类', 'goods.category/delete', '10026', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10031', '商品评价', 'goods.comment', '10018', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10032', '评价列表', 'goods.comment/index', '10031', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10033', '评价详情', 'goods.comment/detail', '10031', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10034', '删除评价', 'goods.comment/delete', '10031', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10035', '订单管理', 'order', '0', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10036', '订单列表', '', '10035', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10037', '待发货', 'order/delivery_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10038', '待收货', 'order/receipt_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10039', '待付款', 'order/pay_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10040', '已完成', 'order/complete_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10041', '已取消', 'order/cancel_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10042', '全部订单', 'order/all_list', '10036', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10043', '订单详情', '', '10035', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10044', '详情信息', 'order/detail', '10043', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10045', '确认发货', 'order/delivery', '10043', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10046', '修改订单价格', 'order/updateprice', '10043', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10047', '订单导出', 'order.operate/export', '10035', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10048', '批量发货', 'order.operate/batchdelivery', '10035', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10049', '用户管理', 'user', '0', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10050', '用户列表', 'user/index', '10049', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10051', '删除用户', 'user/delete', '10049', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10052', '营销设置', 'market', '0', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10053', '优惠券', 'coupon', '10052', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10054', '优惠券列表', 'market.coupon/index', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10055', '新增优惠券', 'market.coupon/add', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10056', '编辑优惠券', 'market.coupon/edit', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10057', '删除优惠券', 'market.coupon/delete', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10058', '领取记录', 'market.coupon/receive', '10053', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10059', '小程序', 'wxapp', '0', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10060', '小程序设置', 'wxapp/setting', '10059', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10061', '页面管理', 'wxapp.page', '10059', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10062', '页面设计', '', '10061', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10063', '页面列表', 'wxapp.page/index', '10062', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10064', '新增页面', 'wxapp.page/add', '10062', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10065', '编辑页面', 'wxapp.page/edit', '10062', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10066', '设为首页', 'wxapp.page/sethome', '10062', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10067', '分类页模板', 'wxapp.page/category', '10061', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10068', '页面链接', 'wxapp.page/links', '10061', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10069', '帮助中心', 'wxapp.help', '10059', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10070', '帮助列表', 'wxapp.help/index', '10069', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10071', '新增帮助', 'wxapp.help/add', '10069', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10072', '编辑帮助', 'wxapp.help/edit', '10069', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10073', '删除帮助', 'wxapp.help/delete', '10069', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10074', '应用中心', 'apps', '0', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10075', '分销中心', 'apps.dealer', '10074', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10076', '入驻申请', 'apps.dealer.apply', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10077', '申请列表', 'apps.dealer.apply/index', '10076', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10078', '分销商审核', 'apps.dealer.apply/submit', '10076', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10079', '分销商用户', 'apps.dealer.user', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10080', '分销商列表', 'apps.dealer.user/index', '10079', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10081', '删除分销商', 'apps.dealer.user/delete', '10079', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10082', '分销商二维码', 'apps.dealer.user/qrcode', '10079', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10083', '分销订单', 'apps.dealer.order/index', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10084', '提现申请', 'apps.dealer.withdraw', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10085', '申请列表', 'apps.dealer.withdraw/index', '10084', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10086', '提现审核', 'apps.dealer.withdraw/submit', '10084', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10087', '确认打款', 'apps.dealer.withdraw/money', '10084', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10088', '分销设置', 'apps.dealer.setting/index', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10089', '分销海报', 'apps.dealer.setting/qrcode', '10075', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10090', '设置', 'setting', '0', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10091', '商城设置', 'setting/store', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10092', '交易设置', 'setting/trade', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10093', '配送设置', 'setting.delivery', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10094', '运费模板列表', 'setting.delivery/index', '10093', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10095', '新增运费模板', 'setting.delivery/add', '10093', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10096', '编辑运费模板', 'setting.delivery/edit', '10093', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10097', '删除运费模板', 'setting.delivery/delete', '10093', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10098', '物流公司', 'setting.express', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10099', '物流公司列表', 'setting.express/index', '10098', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10100', '新增物流公司', 'setting.express/add', '10098', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10101', '编辑物流公司', 'setting.express/edit', '10098', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10102', '删除物流公司', 'setting.express/delete', '10098', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10103', '短信通知', 'setting/sms', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10104', '模板消息', 'setting/tplmsg', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10105', '上传设置', 'setting/storage', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10106', '其他', '', '10090', '100', '1540628721', '1540628721'); +INSERT INTO `yoshop_store_access` VALUES ('10107', '清理缓存', 'setting.cache/clear', '10106', '100', '1540628721', '1540628721'); + + +# 商家用户角色表 +CREATE TABLE `yoshop_store_role` ( + `role_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '角色id', + `role_name` varchar(50) NOT NULL DEFAULT '' COMMENT '角色名称', + `parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父级角色id', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '100' COMMENT '排序(数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`role_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家用户角色表'; + + +# 商家用户角色权限关系表 +CREATE TABLE `yoshop_store_role_access` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `role_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '角色id', + `access_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '权限id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `role_id` (`role_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家用户角色权限关系表'; + + +# 商家用户角色记录表 +CREATE TABLE `yoshop_store_user_role` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `store_user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '超管用户id', + `role_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '角色id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `admin_user_id` (`store_user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商家用户角色记录表'; + diff --git a/doc/database/upgrade/v1.1.7.sql b/doc/database/upgrade/v1.1.7.sql new file mode 100644 index 0000000..ce2d626 --- /dev/null +++ b/doc/database/upgrade/v1.1.7.sql @@ -0,0 +1,83 @@ + + +# 微信小程序记录表:新增证书文件字段 +ALTER TABLE `yoshop_wxapp` +ADD COLUMN `cert_pem` longtext COMMENT '证书文件cert' AFTER `apikey`, +ADD COLUMN `key_pem` longtext COMMENT '证书文件key' AFTER `cert_pem`; + + +# 订单记录表:订单状态 新增21待取消状态 +ALTER TABLE `yoshop_order` +MODIFY COLUMN `order_status` tinyint(3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '订单状态(10进行中 20取消 21待取消 30已完成)' AFTER `receipt_time`; + + +# 退货地址记录表 +CREATE TABLE `yoshop_return_address` ( + `address_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '退货地址id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序 (数字越小越靠前)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`address_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='退货地址记录表'; + + +# 售后单记录表 +CREATE TABLE `yoshop_order_refund` ( + `order_refund_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '售后单id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单id', + `order_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单商品id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '售后类型(10退货退款 20换货)', + `apply_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '用户申请原因(说明)', + `is_agree` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商家审核状态(0待审核 10已同意 20已拒绝)', + `refuse_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '商家拒绝原因(说明)', + `refund_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际退款金额', + `is_user_send` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '用户是否发货(0未发货 1已发货)', + `send_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户发货时间', + `express_id` varchar(32) NOT NULL DEFAULT '' COMMENT '用户发货物流公司id', + `express_no` varchar(32) NOT NULL DEFAULT '' COMMENT '用户发货物流单号', + `is_receipt` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商家收货状态(0未收货 1已收货)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '售后单状态(0进行中 10已拒绝 20已完成 30已取消)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_refund_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='售后单记录表'; + + +# 售后单退货地址记录表 +CREATE TABLE `yoshop_order_refund_address` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_refund_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '售后单id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='售后单退货地址记录表'; + + +# 售后单图片记录表 +CREATE TABLE `yoshop_order_refund_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_refund_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '售后单id', + `image_id` int(11) NOT NULL DEFAULT '0' COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='售后单图片记录表'; + + +# 新增权限记录 +INSERT INTO `yoshop_store_access` (`name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('审核用户取消申请', 'order.operate/confirmcancel', '10043', '100', '1542163260', '1542163260'); +INSERT INTO `yoshop_store_access` (`name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('售后管理', 'order.refund', '10035', '100', '1542161684', '1542161684'); +INSERT INTO `yoshop_store_access` (`name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('售后列表', 'order.refund/index', '10108', '100', '1542161714', '1542161714'); +INSERT INTO `yoshop_store_access` (`name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('售后详情', 'order.refund/detail', '10108', '100', '1542161736', '1542161736'); +INSERT INTO `yoshop_store_access` (`name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('审核售后单', 'order.refund/audit', '10108', '100', '1542162196', '1542162196'); +INSERT INTO `yoshop_store_access` (`name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('确认收货并退款', 'order.refund/receipt', '10108', '100', '1542162231', '1542162231'); diff --git a/doc/database/upgrade/v1.1.8.sql b/doc/database/upgrade/v1.1.8.sql new file mode 100644 index 0000000..898aa65 --- /dev/null +++ b/doc/database/upgrade/v1.1.8.sql @@ -0,0 +1,11 @@ + +# 商品规格表:规格图片 +ALTER TABLE `yoshop_goods_sku` +ADD COLUMN `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格图片id' AFTER `spec_sku_id`; + + +# 用户收货地址表:新市辖区 +ALTER TABLE `yoshop_user_address` +ADD COLUMN `district` varchar(255) NULL DEFAULT '' COMMENT '新市辖区(该字段用于记录region表中没有的市辖区)' AFTER `region_id`; + + diff --git a/doc/database/upgrade/v1.1.9.sql b/doc/database/upgrade/v1.1.9.sql new file mode 100644 index 0000000..ad4bbe4 --- /dev/null +++ b/doc/database/upgrade/v1.1.9.sql @@ -0,0 +1,370 @@ + +#商品记录表:新增商品卖点字段 +ALTER TABLE `yoshop_goods` +ADD COLUMN `selling_point` varchar(500) NOT NULL DEFAULT '' COMMENT '商品卖点' AFTER `category_id`; + + +# 小程序prepay_id记录表:新增订单类型 +ALTER TABLE `yoshop_wxapp_prepay_id` +ADD COLUMN `order_type` tinyint(3) UNSIGNED NOT NULL DEFAULT 10 COMMENT '订单类型(10商城订单 20拼团订单)' AFTER `order_id`; + + +# 分销商订单记录表:新增订单类型 +ALTER TABLE `yoshop_dealer_order` +ADD COLUMN `order_type` TINYINT (3) UNSIGNED NOT NULL DEFAULT '10' COMMENT '订单类型(10商城订单 20拼团订单)' AFTER `order_id`; + + +# 新增:拼团拼单记录表 +CREATE TABLE `yoshop_sharing_active` ( + `active_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '拼单id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团商品id', + `people` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成团人数', + `actual_people` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '当前已拼人数', + `creator_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '团长用户id', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼单结束时间', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '拼单状态(0未拼单 10拼单中 20拼单成功 30拼单失败)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`active_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团拼单记录表'; + + +# 新增:拼团拼单成员记录表 +CREATE TABLE `yoshop_sharing_active_users` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼单id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `is_creator` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否为创建者', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团拼单成员记录表'; + + +# 新增:拼团商品分类表 +CREATE TABLE `yoshop_sharing_category` ( + `category_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品分类id', + `name` varchar(50) NOT NULL DEFAULT '' COMMENT '分类名称', + `parent_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '上级分类id', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分类图片id', + `sort` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '排序方式(数字越小越靠前)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品分类表'; + + +# 新增:拼团商品评价表 +CREATE TABLE `yoshop_sharing_comment` ( + `comment_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '评价id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团商品id', + `order_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单商品id', + `score` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '评分(10好评 20中评 30差评)', + `content` text NOT NULL COMMENT '评价内容', + `is_picture` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否为图片评价', + `sort` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '评价排序', + `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0隐藏 1显示)', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `is_delete` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '软删除', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`comment_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品评价表'; + + +# 新增:拼团评价图片记录表 +CREATE TABLE `yoshop_sharing_comment_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `comment_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '评价id', + `image_id` int(11) NOT NULL DEFAULT '0' COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团评价图片记录表'; + + +# 新增:拼团商品记录表 +CREATE TABLE `yoshop_sharing_goods` ( + `goods_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '拼团商品id', + `goods_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称', + `category_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品分类id', + `selling_point` varchar(500) NOT NULL DEFAULT '' COMMENT '商品卖点', + `people` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '成团人数', + `group_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成团有效时间(单位:小时)', + `is_alone` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否允许单买(0不允许 1允许)', + `spec_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商品规格(10单规格 20多规格)', + `deduct_stock_type` tinyint(3) unsigned NOT NULL DEFAULT '20' COMMENT '库存计算方式(10下单减库存 20付款减库存)', + `content` longtext NOT NULL COMMENT '商品详情', + `sales_initial` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '初始销量', + `sales_actual` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '实际销量', + `goods_sort` int(11) unsigned NOT NULL DEFAULT '100' COMMENT '商品排序(数字越小越靠前)', + `delivery_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '配送模板id', + `goods_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '商品状态(10上架 20下架)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`goods_id`), + KEY `category_id` (`category_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品记录表'; + + +# 新增:商品图片记录表 +CREATE TABLE `yoshop_sharing_goods_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `image_id` int(11) NOT NULL COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='商品图片记录表'; + + +# 新增:拼团商品规格表 +CREATE TABLE `yoshop_sharing_goods_sku` ( + `goods_sku_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品规格id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '0' COMMENT '商品sku记录索引(由规格id组成)', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格图片id', + `goods_no` varchar(100) NOT NULL DEFAULT '' COMMENT '商品编码', + `sharing_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '拼团价格', + `goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品价格(单买价)', + `line_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品划线价', + `stock_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '当前库存数量', + `goods_sales` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品销量', + `goods_weight` double unsigned NOT NULL DEFAULT '0' COMMENT '商品重量(Kg)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`goods_sku_id`), + UNIQUE KEY `sku_idx` (`goods_id`,`spec_sku_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品规格表'; + + +# 新增:拼团商品与规格值关系记录表 +CREATE TABLE `yoshop_sharing_goods_spec_rel` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品id', + `spec_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格组id', + `spec_value_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '规格值id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团商品与规格值关系记录表'; + + +# 新增:拼团订单记录表 +CREATE TABLE `yoshop_sharing_order` ( + `order_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单id', + `order_no` varchar(20) NOT NULL DEFAULT '' COMMENT '订单号', + `order_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '订单类型(10单独购买 20拼团)', + `active_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼单id', + `total_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '订单金额(不含运费)', + `coupon_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '优惠券id', + `coupon_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '优惠券抵扣金额', + `pay_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际付款金额(包含运费、优惠)', + `update_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '后台修改的订单金额(差价)', + `buyer_remark` varchar(255) NOT NULL DEFAULT '' COMMENT '买家留言', + `pay_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '付款状态(10未付款 20已付款)', + `pay_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '付款时间', + `express_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '运费金额', + `express_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '物流公司id', + `express_company` varchar(50) NOT NULL DEFAULT '' COMMENT '物流公司', + `express_no` varchar(50) NOT NULL DEFAULT '' COMMENT '物流单号', + `delivery_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '发货状态(10未发货 20已发货)', + `delivery_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '发货时间', + `receipt_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '收货状态(10未收货 20已收货)', + `receipt_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '收货时间', + `order_status` tinyint(3) unsigned NOT NULL DEFAULT '10' COMMENT '订单状态(10进行中 20已取消 21待取消 30已完成)', + `is_refund` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '拼团未成功退款(0未退款 1已退款)', + `transaction_id` varchar(30) NOT NULL DEFAULT '' COMMENT '微信支付交易号', + `is_comment` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否已评价(0否 1是)', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_id`), + UNIQUE KEY `order_no` (`order_no`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团订单记录表'; + + +# 新增:拼团订单收货地址记录表 +CREATE TABLE `yoshop_sharing_order_address` ( + `order_address_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '地址id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `province_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在省份id', + `city_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在城市id', + `region_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所在区id', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`order_address_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团订单收货地址记录表'; + + +# 新增:拼团订单商品记录表 +CREATE TABLE `yoshop_sharing_order_goods` ( + `order_goods_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团商品id', + `goods_name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名称', + `image_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品封面图id', + `selling_point` varchar(500) NOT NULL DEFAULT '' COMMENT '商品卖点', + `people` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '成团人数', + `group_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '成团有效时间(单位:小时)', + `is_alone` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否允许单买(0不允许 1允许)', + `deduct_stock_type` tinyint(3) unsigned NOT NULL DEFAULT '20' COMMENT '库存计算方式(10下单减库存 20付款减库存)', + `spec_type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '规格类型(10单规格 20多规格)', + `spec_sku_id` varchar(255) NOT NULL DEFAULT '' COMMENT '商品sku标识', + `goods_sku_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商品规格id', + `goods_attr` varchar(500) NOT NULL DEFAULT '' COMMENT '商品规格信息', + `content` longtext NOT NULL COMMENT '商品详情', + `goods_no` varchar(100) NOT NULL DEFAULT '' COMMENT '商品编码', + `goods_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品价格', + `line_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品划线价', + `goods_weight` double unsigned NOT NULL DEFAULT '0' COMMENT '商品重量(Kg)', + `total_num` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '购买数量', + `total_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '商品总价(数量×单价)', + `total_pay_price` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际付款价(包含优惠、折扣)', + `is_comment` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '是否已评价(0否 1是)', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`order_goods_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团订单商品记录表'; + + +# 新增:拼团售后单记录表 +CREATE TABLE `yoshop_sharing_order_refund` ( + `order_refund_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '售后单id', + `order_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '拼团订单id', + `order_goods_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '订单商品id', + `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', + `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '售后类型(10退货退款 20换货)', + `apply_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '用户申请原因(说明)', + `is_agree` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商家审核状态(0待审核 10已同意 20已拒绝)', + `refuse_desc` varchar(1000) NOT NULL DEFAULT '' COMMENT '商家拒绝原因(说明)', + `refund_money` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '实际退款金额', + `is_user_send` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '用户是否发货(0未发货 1已发货)', + `send_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户发货时间', + `express_id` varchar(32) NOT NULL DEFAULT '' COMMENT '用户发货物流公司id', + `express_no` varchar(32) NOT NULL DEFAULT '' COMMENT '用户发货物流单号', + `is_receipt` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '商家收货状态(0未收货 1已收货)', + `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '售后单状态(0进行中 10已拒绝 20已完成 30已取消)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`order_refund_id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团售后单记录表'; + + +# 新增:拼团售后单退货地址记录表 +CREATE TABLE `yoshop_sharing_order_refund_address` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_refund_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '售后单id', + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '收货人姓名', + `phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话', + `detail` varchar(255) NOT NULL DEFAULT '' COMMENT '详细地址', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团售后单退货地址记录表'; + + +# 新增:拼团售后单图片记录表 +CREATE TABLE `yoshop_sharing_order_refund_image` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', + `order_refund_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '售后单id', + `image_id` int(11) NOT NULL DEFAULT '0' COMMENT '图片id(关联文件记录表)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='拼团售后单图片记录表'; + + +# 新增:拼团设置表 +CREATE TABLE `yoshop_sharing_setting` ( + `key` varchar(30) NOT NULL DEFAULT '' COMMENT '设置项标示', + `describe` varchar(255) NOT NULL DEFAULT '' COMMENT '设置项描述', + `values` mediumtext NOT NULL COMMENT '设置内容(json格式)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `unique_key` (`key`,`wxapp_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='拼团设置表'; + + + +# 拼团管理 +INSERT INTO `yoshop_store_access` VALUES ('10300', '拼团管理', 'apps.sharing', '10074', '100', '1544601161', '1544601161'); + + +# 商品分类 +INSERT INTO `yoshop_store_access` VALUES ('10301', '商品分类', 'apps.sharing.category', '10300', '100', '1544601378', '1544601378'); + +INSERT INTO `yoshop_store_access` VALUES ('10302', '分类列表', 'apps.sharing.category/index', '10301', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10303', '添加分类', 'apps.sharing.category/add', '10301', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10304', '编辑分类', 'apps.sharing.category/edit', '10301', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10305', '删除分类', 'apps.sharing.category/delete', '10301', '100', '1544601378', '1544601378'); + + +# 拼团商品管理 +INSERT INTO `yoshop_store_access` VALUES ('10306', '商品管理', 'apps.sharing.goods', '10300', '100', '1544601378', '1544601378'); + +INSERT INTO `yoshop_store_access` VALUES ('10307', '商品列表', 'apps.sharing.goods/index', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10308', '添加商品', 'apps.sharing.goods/add', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10309', '编辑商品', 'apps.sharing.goods/edit', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10310', '复制商品', 'apps.sharing.goods/copy', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10311', '删除商品', 'apps.sharing.goods/delete', '10306', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10312', '商品上下架', 'apps.sharing.goods/state', '10306', '100', '1544601378', '1544601378'); + + +# 拼单管理 +INSERT INTO `yoshop_store_access` VALUES ('10313', '拼单管理', 'apps.sharing.active', '10300', '100', '1544601378', '1544601378'); + +INSERT INTO `yoshop_store_access` VALUES ('10314', '拼单列表', 'apps.sharing.active/index', '10313', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10315', '拼单成员列表', 'apps.sharing.active/users', '10313', '100', '1544601378', '1544601378'); + + +# 拼团订单 +INSERT INTO `yoshop_store_access` VALUES ('10316', '订单管理', 'apps.sharing.order', '10300', '100', '1544601378', '1544601378'); + +INSERT INTO `yoshop_store_access` VALUES ('10317', '订单列表', 'apps.sharing.order/index', '10316', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10318', '订单详情', '', '10316', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10319', '详情信息', 'apps.sharing.order/detail', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10320', '确认发货', 'apps.sharing.order/delivery', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10321', '修改订单价格', 'apps.sharing.order/updateprice', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10322', '审核用户取消订单', 'apps.sharing.order/confirmcancel', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10323', '拼团失败手动退款', 'apps.sharing.order/refund', '10318', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10324', '订单导出', 'apps.sharing.order.operate/export', '10316', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10325', '批量发货', 'apps.sharing.order.operate/batchdelivery', '10316', '100', '1544601378', '1544601378'); + + +# 售后管理 +INSERT INTO `yoshop_store_access` VALUES ('10326', '售后管理', 'apps.sharing.order.refund', '10300', '100', '1544601378', '1544601378'); + +INSERT INTO `yoshop_store_access` VALUES ('10327', '售后列表', 'apps.sharing.order.refund/index', '10326', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10328', '售后详情', 'apps.sharing.order.refund/detail', '10326', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10329', '审核售后单', 'apps.sharing.order.refund/audit', '10326', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10330', '确认收货并退款', 'apps.sharing.order.refund/receipt', '10326', '100', '1544601378', '1544601378'); + + +# 商品评价 +INSERT INTO `yoshop_store_access` VALUES ('10331', '商品评价', 'apps.sharing.comment', '10300', '100', '1544601378', '1544601378'); + +INSERT INTO `yoshop_store_access` VALUES ('10332', '评价列表', 'apps.sharing.comment/index', '10331', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10333', '评价详情', 'apps.sharing.comment/detail', '10331', '100', '1544601378', '1544601378'); +INSERT INTO `yoshop_store_access` VALUES ('10334', '删除评价', 'apps.sharing.comment/delete', '10331', '100', '1544601378', '1544601378'); + + +# 拼团设置 +INSERT INTO `yoshop_store_access` VALUES ('10335', '拼团设置', 'apps.sharing.setting/index', '10300', '100', '1544601378', '1544601378'); + + diff --git a/doc/更新日志.txt b/doc/更新日志.txt new file mode 100644 index 0000000..6baa1aa --- /dev/null +++ b/doc/更新日志.txt @@ -0,0 +1,598 @@ + +### v1.1.38 更新日志 ### + +修复:订单确认页收货地址提示 +修复:后台拼团商品列表状态筛选 +修复:秒杀订单并发库存问题 +修复:后台编辑门店后坐标不准确 +新增:后台分销中心编辑分销商用户 +优化:删除分销商清空推荐关系 +优化:删除用户时删除上级推荐关系 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.37 更新日志 ### + +优化:下单运费模板计算方式 +优化:已付款拼团订单不允许取消 +优化:后台砍价活动时间选择 +优化:权限管理新增数据统计项 +新增:首页DIY公众号关注组件 +修复:用户充值自动匹配套餐 +修复:后台新增管理员提示已存在 +修复:图片库列表不显示回收站的文件 +修复:订单详情页不显示详细地址 +修复:秒杀会场页进入商品报错 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.36 更新日志 ### + +优化:订单发货后即可申请售后 +优化:图片库删除图片进入回收站 +新增:后台数据统计模块 +修复:删除会员等级提示存在会员 +修复:小程序端 下单提示收货地址不存在 +修复:小程序端 秒杀商品详情页价格联动 +修复:小程序端 订单确认页has_error问题 +修复:小程序端 拼团拼单页暂无现货按钮样式 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.35 更新日志 ### + +优化:调整新授权登录方式 +修复:后台门店坐标无法选择 +修复:砍价会场页面报错(个别环境) +修复:不存在的收货城市不允许下单 +修复:验证运费模板是否被商品使用 +修复:复制参与活动的商品规格问题 +修复:小程序端 文章详情页空格不解析 +修复:小程序端 diy秒杀组件报错 +修复:小程序端 秒杀商品页倒计时错误 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.34 更新日志 ### + +优化:后台订单导出显示自提联系人 +修复:后台门店坐标地图加载无效 +修复:拼团订单结算错误 +修复:秒杀进度数字精确度问题 +修复:砍价商品正在砍价人数不正确 +修复:砍价任务并发问题 +修复:小程序端图片橱窗组样式错误 +修复:小程序端富文本空格不生效 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.33 更新日志 ### + +修复:砍价主商品下架详情页错误 +修复:php7环境秒杀首页报错 +修复:秒杀商品商品海报错误 +优化:余额支付订单支持小程序模板消息 +修复:购买产品成为分销商报错 +新增:diy组件-秒杀商品组 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.32 更新日志 ### + +优化:实际消费金额规则判断存在售后 +优化:订单存在退款的商品不赠送积分 +新增:新增营销模块整点秒杀 +修复:超管后台商城列表不显示分页 +修复:后台会员管理操作权限不正确 +修复:收货地址不在配送区报错 +修复:取消订单恢复sku库存的bug +修复:后台更新管理员时检测用户名重复 +修复:小程序端 更新收货地址时提示失败 +修复:小程序端 砍价商品详情页商品评价数量不正确 +修复:小程序端 门店详情页转发链接错误 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.31 更新日志 ### + +修复:用户可用积分不足无法抵扣问题 +修复:后台编辑商品compact方法报错 +修复:拼团订单新增分销记录类型错误 +新增:订单导出显示优惠抵扣后台改价等信息 +新增:小票打印显示上门自提点及联系信息 +优化:小程序端 砍价按钮重复点击问题 +优化:小程序端 商品详情页规格过多时支持滚动 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.30 更新日志 ### + +新增:小程序端积分明细页 +新增:页面设计砍价商品组件 +修复:购物车同商品多sku数据错误 +修复:默认购买商品积分赠送数量 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.29 更新日志 ### + +优化:小程序端 商品列表api +优化:小程序端 记忆购物车选择的商品 +新增:用户积分功能(支持积分赠送、积分抵扣) +新增:后台设置是否允许用户充值(隐藏充值入口) +修复:砍价商品海报二维码链接 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.28 更新日志 ### + +优化:监听砍价任务结束 +修复:订单结算优惠券价格计算问题 +修复:后台砍价活动状态设置 +修复:砍价页面分享链接不正确 +修复:订单确认页delivery报错 +修复:后台编辑运费模版报错 +新增:小程序端个人中心我的砍价 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.27 更新日志 ### + +优化:后台优惠券管理折扣率精确度 +优化:会员折扣比例精确小数点2位数 +优化:小程序端验证输入的商品购买数量 +优化:用户首次进入充值中心提示授权 +新增:新增营销模块砍价活动 +修复:后台拼团复制主商城商品提示无权限 +修复:后台导出订单收货地址地级市错误 +修复:未付款订单商品SKU不存在时提示 +修复:小程序端在线客服图标显示 +修复:记忆用户自提订单联系方式 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.26 更新日志 ### + +修复:后台修改会员等级bug +修复:拼团商品生成海报图片错误 +修复:订单自动关闭时回退商品库存、用户优惠券 +修复:拼团商品下单减库存无效 +优化:记忆用户自提订单联系方式 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.25 更新日志 ### + +优化:用户取消订单时回退优惠券 +优化:小程序端 订单详情页快捷导航组件 +优化:后台选择用户添加筛选条件 +优化:后台选择商品添加筛选条件 +新增:会员等级功能 +修复:后台编辑一级分类bug +修复:拼团商品无法使用优惠券 +修复:运费金额过大时计算错误 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.24 更新日志 ### + +优化:小程序端授权登录错误提示 +优化:收货地址的电话支持17x手机号 +新增:微信好物圈同步功能 +修复:后台会员管理充值权限记录 +修复:小程序端余额充值状态提示 +修复:小程序端商品列表单行隐藏问题 +修复:后台文件库列表不显示所属分组 +修复:下单收货地址选择香港出错 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.23 更新日志 ### + +修复:订单管理查询条件情况下导出报错 +新增:diy商品组件支持列表显示 +新增:后台营销管理活跃用户列表 +新增:后台营销管理发送推送消息 +优化:超管后台环境监测临时文件目录 +优化:订单表增加is_delete条件 +优化:小程序端 商品列表显示卖点和销量 +优化:小程序端 商品列表页记忆显示方式 +优化:小程序端 商品购买数量支持自定义输入 +优化:小程序端 拼单详情页面添加快捷导航组件 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.22 更新日志 ### + +修复:拼团订单退款到用户余额 +修复:充值自定义金额时报错 +修复:拼团订单余额支付失败 +新增:后台订单导出显示支付方式 +新增:后台商城设置支持的配送方式 +新增:自提订单需填写联系信息 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.21 更新日志 ### + +修复:后台拼团订单管理搜索查询失效 +修复:后台订单管理按时间筛选报错 +修复:后台设置角色权限不显示菜单问题 +新增:用户余额管理(充值套餐、余额支付) +新增:后台查看指定用户的订单记录 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.20 更新日志 ### + +优化:阿里云OSS支持https域名 +修复:取消订单时回退商品库存 +修复:分销商下级团队列表已删除的用户 +修复:图片上传到本地服务器时报错 +新增:订单管理筛选条件配送方式、自提门店、用户昵称 +新增:订单导出显示商品规格、配送方式、自提门店 +优化:小程序端选择门店时无自提门店提示 +优化:小程序端收货地址手机号验证规则 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.19 更新日志 ### + +修复:小程序端 购物车添加不同规格的商品 +修复:后台拼团订单导出时报错 +新增:后台文件库管理 +修复:后台首页统计已取消订单 +优化:图片库弹窗显示完整的缩略图 +优化:页面设计商品组件支持单列显示 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.18 更新日志 ### + +优化:门店列表支持排序设置 +优化:分销订单自动结算标记是否失效 +优化:分销商订单列表数据获取方式 +优化:token令牌的生成规则 +优化:小程序端导航条加载动画 +优化:订单售后页面显示实付款金额 +优化:查看门店位置显示名称和地址 +新增:DIY页面线下门店列表组件 +修复:小程序端购物车报错(计算运费) +修复:保存小程序码文件报错 +修复:后台文章列表分页 +修复:小程序端商品详情购物车数量 +修复:后台门店店员列表报错 +修复:拼团商品无法选择收货地址 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.17 更新日志 ### + +修复:服务端php7.3环境下报错问题 +修复:新增商城时默认diy页面数据 +修复:后台运费模版页选择区域bug +新增:订单满额包邮功能 +新增:门店自提核销功能 +修复:小程序端 分享自定义专题页打开后是首页 +优化:小程序端 分销商协议过长滚动 +-------------------------------- +注:本次更新须重新发布小程序 + + +### v1.1.16 更新日志 ### + +修复:后台页面设计无法删除默认数据 +修复:页面设计图片橱窗组件报错 +修复:拼团订单发放分销佣金出错 +修复:后台编辑分类无法删除图片 +修复:拼团商品列表价格排序问题 +修复:后台配送模板设置错误 +新增:分销商提现支持微信支付企业付款 +新增:小票打印机管理 +优化:小程序端文章详情页样式 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.15 更新日志 ### + +修复:后台页面设计旧数据兼容性问题 +修复:小程序端文章页分享错误 +修复:小程序端文章列表标题超出未隐藏 +修复:小程序端商品标题超出隐藏兼容性 +新增:页面设计头条快报组件 +优化:升级thinkphp版本5.0.24 +优化:轮播图组件支持设置切换时间 +优化:商品组件支持显示划线价格 +优化:富文本文字默认边距 +优化:小程序端富文本图片加载时尺寸 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.14 更新日志 ### + +修复:后台编辑小程序页面报错 +修复:文章板块设置虚拟阅读量无效 +修复:自定义页面api数据格式不正确 +新增:页面设计在线客服组件 +修复:小程序端 个人中心菜单formid收集 +优化:小程序端 快捷导航组件样式 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.13 更新日志 ### + +优化:小程序DIY视频组件支持自动播放 +优化:小程序端购物车商品总数量显示 +优化:重构后台小程序页面设计 +新增:后台富文本编辑器支持文件库图片 +新增:后台富文本编辑器支持添加视频 +新增:文章资讯板块 +修复:多规格商品默认价格不正确问题 +修复:拼团商品与商城商品id一致生成海报错误 +修复:获取拼团失败的订单过滤未付款订单 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.12 更新日志 ### + +修复:小程序端购物车is_delete报错问题 +修复:拼团DIY组件商品分类数据不正确 +修复:分类页模板分享标题长度 +修复:购物车结算商品数量问题 +修复:超管后台删除权限url报错 +修复:小程序端客服图标不能点击问题 +修复:小程序端拼单详情更多拼团跳转 +优化:小程序端商品详情增加快捷导航 +优化:小程序端个人中心页面UI改版 +优化:后台登录页面兼容适配 +优化:待付款订单中不显示已取消订单 +新增:商品管理支持单独分销设置 +新增:支持分销商内购返一级佣金 +新增:后台分销商列表查看下级用户 +新增:支持购买指定商品成为分销商 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.11 更新日志 ### + +修复:DIY商品组件手动选择排序问题 +修复:待付款订单不显示订单状态 +修复:订单退款失败问题 +修复:小程序端 更多拼团商品价格不正确 +新增:拼团设置支持开启分销 +新增:拼团拼单状态模板消息通知 +优化:小程序端 订单列表分页加载 +优化:小程序端 拼团首页分页加载 +优化:小程序端 拼单详情页支持跳转更多拼团 +优化:小程序端 拼团商品页面收集formId + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.10 更新日志 ### + +优化:小程序端立即购买时关闭确认弹框 +优化:小程序端拼团首页支持转发 +优化:小程序端拼单详情页标签样式 +修复:编辑微信支付证书文件缓存问题 +修复:小程序端拼团商品分享链接错误 +新增:后台小程序页面链接拼团页面 +新增:小程序端拼团商品页显示进行中的拼团 +新增:后台拼团管理支持复制主商城商品 +修复:拼团订单列表发起支付错误 +修复:小程序端领劵中心文字样式 +修复:IOS手机倒计时兼容性问题 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.9 更新日志 ### + +修复:thinkphp路由getshell漏洞(重要) +修复:后台首页统计中包含已删除的会员 +修复:用户申请退货时未填写运单信息可提交 +修复:小程序端生成二维码海报图缓存问题 +修复:已领取的优惠券不自动过期问题 +新增:商品管理商品卖点简述 +新增:后台商品评价管理查看订单详情 +新增:应用中心拼团管理模块 +优化:后台列表页面兼容性问题 +优化:小程序端显示用户已取消的订单 +优化:环境检测中新增微信支付证书目录 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.8 更新日志 ### + +修复:取消已付款订单时更改订单状态 +修复:申请售后不上传图片时凭证报错 +修复:后台售后单列表筛选错误 +优化:发放分销订单佣金时重新计算佣金 +优化:订单商品存在售后退款则不计算分销佣金 +优化:删除umeditor清空文档按钮 +优化:收货地址兼容最新的市辖区 +优化:小程序端售后详情显示已退款金额 +新增:后台分销设置佣金结算天数 +新增:用户售后单状态通知模板消息 +新增:多规格商品支持单独封面图片 +新增:后台设置已完成订单n天内允许申请售后 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.7 更新日志 ### + +新增:商户后台退货地址管理 +新增:后台设置微信支付双向认证文件 +新增:已付款订单用户申请取消并原路退回 +新增:售后订单管理模块(退货退款、换货) +优化:兼容非mysqlnd环境全等改为等于 +优化:小程序端 改版商品详情页面SKU选择 +优化:小程序端 操作按钮样式 +优化:小程序端 删除保存图片分享朋友圈提示语 +修复:多商家后台图片库文件未分离 +修复:新增商城创建默认分类页模板记录 +修复:富文本编辑器p标签间距 +修复:订单批量发货报错问题 +修复:商户后台添加管理员用户名重复问题 +修复:小程序端 富文本域名中带code解析异常 +修复:小程序端 订单列表不显示评论按钮(个别环境) + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.6 更新日志 ### + +新增:超管后台模块 (支持多开小程序商城) +新增:商家后台管理员模板 (支持角色管理和权限管理) +优化:商家后台订单列表添加时间筛选 +优化:商家后台小程序设置敏感项星号隐藏 +优化:自动删除购物车中失效的商品 +修复:小程序端商品列表价格排序箭头顺序 + +注:本次更新无须重新发布小程序 +-------------------------------- + + +### v1.1.5 更新日志 ### + +优化:小程序端富文本内容兼容性 +优化:小程序端商品海报图默认高度 +优化:保存商品海报图显示loading +修复:小程序端扫码进入获取不到场景值 +修复:小程序端DIY富文本组件样式不生效问题 +修复:小程序端loading窗口不关闭的问题 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.4 更新日志 ### + +新增:后台小程序分销中心链接 +新增:后台订单导出功能 +新增:后台订单批量发货功能 +新增:小程序页面DIY公告组 +新增:小程序页面DIY富文本组 +新增:后台删除会员功能 +新增:小程序端商品分享(海报二维码+转发) +优化:后台分销海报设置页面兼容性 +优化:小程序端最近搜索不记录重复值 +优化:小程序端商品列表价格筛选箭头 +优化:小程序端商品详情富文本兼容性 +修复:小程序端banner组件图片高度兼容 +修复:小程序端视频组件底部空白 +修复:小程序端操作成功提示框失效问题 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.3 更新日志 ### + +修复:删除分类时提示存在商品 +修复:后台订单管理不显示改价按钮 +修复:后台编辑DIY页面名称不生效 +优化:后台订单改价不能为0元 +优化:后台商品详情编辑器尺寸 +新增:分销商中心功能 +新增:后台商品列表一键上下架 +新增:后台一键复制商品 +新增:后台用户列表显示累积消费金额 +修复:小程序端商品评价页错乱问题 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.2 更新日志 ### + +修复:订单发货模板消息不显示物流公司 +修复:后台首页统计下单用户数不正确 +修复:后台首页商城统计遮挡左侧菜单 +修复:小程序端 二级分类排版错乱问题 +优化:兼容系统中不存在的地区 +优化:发送消息通知时记录日志 +优化:小程序端 编辑收货地址默认地区选择 +优化:小程序端 图片轮播高度自适应 +优化:小程序端 支持链接跳转tabBar页面 +新增:后台会员列表页条件查询 +新增:后台商品列表页条件查询 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.1 更新日志 ### + +修复:优惠券最多优惠到0.01元 +修复:优惠券有效期日期格式 +修复:小程序端图片橱窗DIY组件链接 +新增:订单提交买家留言 +新增:新版后台首页 +新增:小程序主动更新机制 +新增:小程序模板消息通知功能 +新增:小程序端购物车页可多选 +优化:删除商品改为软删除 + +注:本次更新须重新发布小程序 +-------------------------------- + + +### v1.1.0 更新日志 ### + +新增:小程序分类页面模板 +新增:后台小程序页面设计-自定义页 +新增:后台修改订单价格功能 +新增:小程序端一键获取微信地址 +修复:后台操作成功url跳转问题 +修复:优惠券抵扣金额计算问题 +修复:后台商品评论管理隐藏状态 +修复:小程序端DIY商品组件控制显示内容 +修复:小程序端分类页无数据时错误提示 +优化:后台操作提示等待时间 +优化:后台页面设计兼容性问题 +优化:商品分类排序问题 +优化:小程序端二级分类图片大小 +优化:商品详情页支持点击主图预览 +优化:商品详情页支持右上角分享 + +注:本次更新须重新发布小程序 +-------------------------------- diff --git a/icon.jpg b/icon.jpg new file mode 100644 index 0000000..6238359 Binary files /dev/null and b/icon.jpg differ diff --git a/source/application/admin/config.php b/source/application/admin/config.php new file mode 100644 index 0000000..42bafc0 --- /dev/null +++ b/source/application/admin/config.php @@ -0,0 +1,28 @@ + 'html', + + 'template' => [ + // layout布局 + 'layout_on' => true, + 'layout_name' => 'layouts/layout', + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'think', + // 模板路径 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DS, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}}', + // 标签库标签开始标记 + 'taglib_begin' => '{{', + // 标签库标签结束标记 + 'taglib_end' => '}}', + ], +]; diff --git a/source/application/admin/controller/Controller.php b/source/application/admin/controller/Controller.php new file mode 100644 index 0000000..5e9a4d9 --- /dev/null +++ b/source/application/admin/controller/Controller.php @@ -0,0 +1,187 @@ +admin = Session::get('yoshop_admin'); + // 当前路由信息 + $this->getRouteinfo(); + // 验证登录 + $this->checkLogin(); + // 全局layout + $this->layout(); + } + + /** + * 全局layout模板输出 + * @throws \Exception + */ + private function layout() + { + // 验证当前请求是否在白名单 + if (!in_array($this->routeUri, $this->notLayoutAction)) { + // 输出到view + $this->assign([ + 'base_url' => base_url(), // 当前域名 + 'admin_url' => url('/admin'), // 后台模块url + 'group' => $this->group, + 'menus' => $this->menus(), // 后台菜单 + 'admin' => $this->admin, // 商家登录信息 + 'version' => get_version(), // 当前系统版本号 + 'request' => Request::instance() // Request对象 + ]); + } + } + + /** + * 解析当前路由参数 (分组名称、控制器名称、方法名) + */ + protected function getRouteinfo() + { + // 控制器名称 + $this->controller = toUnderScore($this->request->controller()); + // 方法名称 + $this->action = $this->request->action(); + // 控制器分组 (用于定义所属模块) + $groupstr = strstr($this->controller, '.', true); + $this->group = $groupstr !== false ? $groupstr : $this->controller; + // 当前uri + $this->routeUri = $this->controller . '/' . $this->action; + } + + /** + * 后台菜单配置 + * @return array + */ + private function menus() + { + foreach ($data = Config::get('menus') as $group => $first) { + $data[$group]['active'] = $group === $this->group; + // 遍历:二级菜单 + if (isset($first['submenu'])) { + foreach ($first['submenu'] as $secondKey => $second) { + // 二级菜单所有uri + $secondUris = isset($second['uris']) ? $second['uris'] : [$second['index']]; + // 二级菜单:active + !isset($data[$group]['submenu'][$secondKey]['active']) + && $data[$group]['submenu'][$secondKey]['active'] = in_array($this->routeUri, $secondUris); + } + } + } + return $data; + } + + /** + * 验证登录状态 + * @return bool + */ + private function checkLogin() + { + // 验证当前请求是否在白名单 + if (in_array($this->routeUri, $this->allowAllAction)) { + return true; + } + // 验证登录状态 + if (empty($this->admin) + || (int)$this->admin['is_login'] !== 1 + ) { + $this->redirect('passport/login'); + return false; + } + return true; + } + + /** + * 返回封装后的 API 数据到客户端 + * @param int $code + * @param string $msg + * @param string $url + * @param array $data + * @return array + */ + protected function renderJson($code = 1, $msg = '', $url = '', $data = []) + { + return compact('code', 'msg', 'url', 'data'); + } + + /** + * 返回操作成功json + * @param string $msg + * @param string $url + * @param array $data + * @return array + */ + protected function renderSuccess($msg = 'success', $url = '', $data = []) + { + return $this->renderJson(1, $msg, $url, $data); + } + + /** + * 返回操作失败json + * @param string $msg + * @param string $url + * @param array $data + * @return array + */ + protected function renderError($msg = 'error', $url = '', $data = []) + { + return $this->renderJson(0, $msg, $url, $data); + } + + /** + * 获取post数据 (数组) + * @param $key + * @return mixed + */ + protected function postData($key) + { + return $this->request->post($key . '/a'); + } + +} diff --git a/source/application/admin/controller/Index.php b/source/application/admin/controller/Index.php new file mode 100644 index 0000000..05f56cc --- /dev/null +++ b/source/application/admin/controller/Index.php @@ -0,0 +1,22 @@ +fetch('index'); + } + +} diff --git a/source/application/admin/controller/Passport.php b/source/application/admin/controller/Passport.php new file mode 100644 index 0000000..411dd8d --- /dev/null +++ b/source/application/admin/controller/Passport.php @@ -0,0 +1,44 @@ +request->isAjax()) { + $model = new UserModel; + if ($model->login($this->postData('User'))) { + return $this->renderSuccess('登录成功', url('index/index')); + } + return $this->renderError($model->getError() ?: '登录失败'); + } + $this->view->engine->layout(false); + return $this->fetch('login'); + } + + /** + * 退出登录 + */ + public function logout() + { + Session::clear('yoshop_admin'); + $this->redirect('passport/login'); + } + +} diff --git a/source/application/admin/controller/Store.php b/source/application/admin/controller/Store.php new file mode 100644 index 0000000..6041b35 --- /dev/null +++ b/source/application/admin/controller/Store.php @@ -0,0 +1,122 @@ +fetch('index', [ + 'list' => $list = $model->getList(), + 'names' => $model->getStoreName($list) + ]); + } + + /** + * 进入商城 + * @param $wxapp_id + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function enter($wxapp_id) + { + $model = new StoreUser; + $model->login($wxapp_id); + $this->redirect('store/index/index'); + } + + /** + * 回收站列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function recycle() + { + $model = new WxappModel; + return $this->fetch('recycle', [ + 'list' => $list = $model->getList(true), + 'names' => $model->getStoreName($list) + ]); + } + + /** + * 添加小程序 + * @return array|mixed + * @throws \think\exception\PDOException + */ + public function add() + { + $model = new WxappModel; + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + if ($model->add($this->postData('store'))) { + return $this->renderSuccess('添加成功', url('store/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 回收小程序 + * @param $wxapp_id + * @return array + * @throws \think\exception\DbException + */ + public function recovery($wxapp_id) + { + // 小程序详情 + $model = WxappModel::detail($wxapp_id); + if (!$model->recycle()) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 移出回收站 + * @param $wxapp_id + * @return array + * @throws \think\exception\DbException + */ + public function move($wxapp_id) + { + // 小程序详情 + $model = WxappModel::detail($wxapp_id); + if (!$model->recycle(false)) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 删除小程序 + * @param $wxapp_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($wxapp_id) + { + // 小程序详情 + $model = WxappModel::detail($wxapp_id); + if (!$model->setDelete()) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + +} \ No newline at end of file diff --git a/source/application/admin/controller/admin/User.php b/source/application/admin/controller/admin/User.php new file mode 100644 index 0000000..59e25cc --- /dev/null +++ b/source/application/admin/controller/admin/User.php @@ -0,0 +1,31 @@ +admin['user']['admin_user_id']); + if ($this->request->isAjax()) { + if ($model->renew($this->postData('user'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + return $this->fetch('renew', compact('model')); + } +} diff --git a/source/application/admin/controller/setting/Cache.php b/source/application/admin/controller/setting/Cache.php new file mode 100644 index 0000000..34548d9 --- /dev/null +++ b/source/application/admin/controller/setting/Cache.php @@ -0,0 +1,84 @@ +request->isAjax()) { + $this->rmCache($this->postData('cache')); + return $this->renderSuccess('操作成功'); + } + return $this->fetch('clear', [ + 'isForce' => !!$isForce ?: config('app_debug'), + ]); + } + + /** + * 删除缓存 + * @param $data + */ + private function rmCache($data) + { + // 数据缓存 + if (in_array('data', $data['item'])) { + // 强制模式 + $isForce = isset($data['isForce']) ? !!$data['isForce'] : false; + // 清除缓存 + CacheDriver::clear($isForce ? null : 'cache'); + } + // 临时文件 + if (in_array('temp', $data['item'])) { + $paths = [ + 'temp' => WEB_PATH . 'temp/', + 'runtime' => RUNTIME_PATH . 'image/' + ]; + foreach ($paths as $path) { + $this->deleteFolder($path); + } + } + } + + /** + * 递归删除指定目录下所有文件 + * @param $path + * @return bool + */ + private function deleteFolder($path) + { + if (!is_dir($path)) + return false; + // 扫描一个文件夹内的所有文件夹和文件 + foreach (scandir($path) as $val) { + // 排除目录中的.和.. + if (!in_array($val, ['.', '..', '.gitignore'])) { + // 如果是目录则递归子目录,继续操作 + if (is_dir($path . $val)) { + // 子目录中操作删除文件夹和文件 + $this->deleteFolder($path . $val . '/'); + // 目录清空后删除空文件夹 + rmdir($path . $val . '/'); + } else { + // 如果是文件直接删除 + unlink($path . $val); + } + } + } + return true; + } + +} diff --git a/source/application/admin/controller/setting/Science.php b/source/application/admin/controller/setting/Science.php new file mode 100644 index 0000000..bd9352d --- /dev/null +++ b/source/application/admin/controller/setting/Science.php @@ -0,0 +1,204 @@ + '', + 'warning' => 'am-active', + 'danger' => 'am-danger' + ]; + + /** + * 环境检测 + */ + public function index() + { + return $this->fetch('index', [ + 'statusClass' => $this->statusClass, + 'phpinfo' => $this->phpinfo(), // PHP环境要求 + 'server' => $this->server(), // 服务器信息 + 'writeable' => $this->writeable(), // 目录权限监测 + ]); + } + + /** + * 服务器信息 + * @return array + */ + private function server() + { + return [ + 'system' => [ + 'name' => '服务器操作系统', + 'value' => PHP_OS, + 'status' => PHP_SHLIB_SUFFIX === 'dll' ? 'warning' : 'normal', + 'remark' => '建议使用 Linux 系统以提升程序性能' + ], + 'webserver' => [ + 'name' => 'Web服务器环境', + 'value' => $this->request->server('SERVER_SOFTWARE'), + 'status' => PHP_SAPI === 'isapi' ? 'warning' : 'normal', + 'remark' => '建议使用 Apache 或 Nginx 以提升程序性能' + ], + 'php' => [ + 'name' => 'PHP版本', + 'value' => PHP_VERSION, + 'status' => version_compare(PHP_VERSION, '5.4.0') === -1 ? 'danger' : 'normal', + 'remark' => 'PHP版本必须为 5.4.0 以上' + ], + 'upload_max' => [ + 'name' => '文件上传限制', + 'value' => @ini_get('file_uploads') ? ini_get('upload_max_filesize') : 'unknow', + 'status' => 'normal', + 'remark' => '' + ], + 'web_path' => [ + 'name' => '程序运行目录', + 'value' => str_replace('\\', '/', WEB_PATH), + 'status' => 'normal', + 'remark' => '' + ], + ]; + } + + /** + * PHP环境要求 + * @return array + */ + private function phpinfo() + { +// pre( get_loaded_extensions() ); + return [ + 'php_version' => [ + 'name' => 'PHP版本', + 'value' => '5.4.0及以上', + 'status' => version_compare(PHP_VERSION, '5.4.0') === -1 ? 'danger' : 'normal', + 'remark' => 'PHP版本必须为 5.4.0及以上' + ], + 'curl' => [ + 'name' => 'CURL', + 'value' => '支持', + 'status' => extension_loaded('curl') && function_exists('curl_init') ? 'normal' : 'danger', + 'remark' => '您的PHP环境不支持CURL, 系统无法正常运行' + ], + 'openssl' => [ + 'name' => 'OpenSSL', + 'value' => '支持', + 'status' => extension_loaded('openssl') ? 'normal' : 'danger', + 'remark' => '没有启用OpenSSL, 将无法访问微信平台的接口' + ], + 'pdo' => [ + 'name' => 'PDO', + 'value' => '支持', + 'status' => extension_loaded('PDO') && extension_loaded('pdo_mysql') ? 'normal' : 'danger', + 'remark' => '您的PHP环境不支持PDO, 系统无法正常运行' + ], + 'gd' => [ + 'name' => 'GD', + 'value' => '支持', + 'status' => extension_loaded('gd') ? 'normal' : 'danger', + 'remark' => '您的PHP环境不支持GD, 系统无法正常生成图片' + ], + 'bcmath' => [ + 'name' => 'BCMath', + 'value' => '支持', + 'status' => extension_loaded('bcmath') ? 'normal' : 'danger', + 'remark' => '您的PHP环境不支持BCMath, 系统无法正常运行' + ], + 'mbstring' => [ + 'name' => 'mbstring', + 'value' => '支持', + 'status' => extension_loaded('mbstring') ? 'normal' : 'danger', + 'remark' => '您的PHP环境不支持mbstring, 系统无法正常运行' + ], + 'SimpleXML' => [ + 'name' => 'SimpleXML', + 'value' => '支持', + 'status' => extension_loaded('SimpleXML') ? 'normal' : 'danger', + 'remark' => '您的PHP环境不支持SimpleXML, 系统无法解析xml 无法使用微信支付' + ], + ]; + + } + + /** + * 目录权限监测 + */ + private function writeable() + { + $paths = [ + 'uploads' => realpath(WEB_PATH) . '/uploads/', + 'temp' => realpath(WEB_PATH) . '/temp/', + 'wxpay_log' => realpath(APP_PATH) . '/common/library/wechat/logs/', + 'wxpay_cert' => realpath(APP_PATH) . '/common/library/wechat/cert/', + 'behavior_log' => realpath(APP_PATH) . '/task/behavior/logs/', + ]; + return [ + 'uploads' => [ + 'name' => '文件上传目录', + 'value' => str_replace('\\', '/', $paths['uploads']), + 'status' => $this->checkWriteable($paths['uploads']) ? 'normal' : 'danger', + 'remark' => '目录不可写,系统将无法正常上传文件' + ], + 'temp' => [ + 'name' => '临时文件目录', + 'value' => str_replace('\\', '/', $paths['temp']), + 'status' => $this->checkWriteable($paths['temp']) ? 'normal' : 'danger', + 'remark' => '目录不可写,系统将无法正常写入文件' + ], + 'wxpay_log' => [ + 'name' => '微信支付日志目录', + 'value' => str_replace('\\', '/', $paths['wxpay_log']), + 'status' => $this->checkWriteable($paths['wxpay_log']) ? 'normal' : 'danger', + 'remark' => '目录不可写,系统将无法正常写入文件' + ], + 'wxpay_cert' => [ + 'name' => '微信支付证书目录', + 'value' => str_replace('\\', '/', $paths['wxpay_cert']), + 'status' => $this->checkWriteable($paths['wxpay_cert']) ? 'normal' : 'danger', + 'remark' => '目录不可写,系统将无法正常写入文件' + ], +// 'behavior_log' => [ +// 'name' => '自动任务日志目录', +// 'value' => str_replace('\\', '/', $paths['behavior_log']), +// 'status' => $this->checkWriteable($paths['behavior_log']) ? 'normal' : 'danger', +// 'remark' => '目录不可写,系统将无法正常上传文件' +// ], + ]; + + } + + /** + * 检查目录是否可写 + * @param $path + * @return bool + */ + private function checkWriteable($path) + { + try { + !is_dir($path) && mkdir($path, 0755); + if (!is_dir($path)) + return false; + $fileName = $path . '/_test_write.txt'; + if ($fp = fopen($fileName, 'w')) { + return fclose($fp) && unlink($fileName); + } + } catch (\Exception $e) { + } + return false; + } + +} diff --git a/source/application/admin/controller/store/Access.php b/source/application/admin/controller/store/Access.php new file mode 100644 index 0000000..5e3fa9c --- /dev/null +++ b/source/application/admin/controller/store/Access.php @@ -0,0 +1,94 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加权限 + * @return array|mixed + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function add() + { + $model = new AccesscModel; + if (!$this->request->isAjax()) { + // 权限列表 + $accessList = $model->getList(); + return $this->fetch('add', compact('accessList')); + } + // 新增记录 + if ($model->add($this->postData('access'))) { + return $this->renderSuccess('添加成功', url('store.access/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 更新权限 + * @param $access_id + * @return array|mixed + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit($access_id) + { + // 权限详情 + $model = AccesscModel::detail($access_id); + if (!$this->request->isAjax()) { + // 权限列表 + $accessList = $model->getList(); + return $this->fetch('edit', compact('model', 'accessList')); + } + // 更新记录 + if ($model->edit($this->postData('access'))) { + return $this->renderSuccess('更新成功', url('store.access/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除权限 + * @param $access_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($access_id) + { + // 权限详情 + $model = AccesscModel::detail($access_id); + if (!$model->remove()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/admin/extra/menus.php b/source/application/admin/extra/menus.php new file mode 100644 index 0000000..067fe2f --- /dev/null +++ b/source/application/admin/extra/menus.php @@ -0,0 +1,47 @@ + [ + * 'name' => '首页', // 菜单名称 + * 'icon' => 'icon-home', // 图标 (class) + * 'index' => 'index/index', // 链接 + * ], + */ +return [ + 'store' => [ + 'name' => '小程序商城', + 'icon' => 'icon-shangcheng', + 'submenu' => [ + [ + 'name' => '商城列表', + 'index' => 'store/index', + 'uris' => [ + 'store/index', + 'store/add', + ] + ], + [ + 'name' => '回收站', + 'index' => 'store/recycle' + ], + [ + 'name' => '权限管理', + 'index' => 'store.access/index' + ] + ], + ], + 'setting' => [ + 'name' => '系统设置', + 'icon' => 'icon-shezhi', + 'submenu' => [ + [ + 'name' => '清理缓存', + 'index' => 'setting.cache/clear' + ], + [ + 'name' => '环境检测', + 'index' => 'setting.science/index' + ], + ], + ], +]; diff --git a/source/application/admin/model/Setting.php b/source/application/admin/model/Setting.php new file mode 100644 index 0000000..582ae8c --- /dev/null +++ b/source/application/admin/model/Setting.php @@ -0,0 +1,31 @@ +defaultData($store_name) as $key => $item) { + $data[] = array_merge($item, ['wxapp_id' => $wxapp_id]); + } + return $this->saveAll($data); + } + +} diff --git a/source/application/admin/model/Wxapp.php b/source/application/admin/model/Wxapp.php new file mode 100644 index 0000000..824964b --- /dev/null +++ b/source/application/admin/model/Wxapp.php @@ -0,0 +1,103 @@ +where('is_recycle', '=', (int)$is_recycle) + ->where('is_delete', '=', 0) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + + /** + * 从缓存中获取商城名称 + * @param $data + * @return array + */ + public function getStoreName($data) + { + $names = []; + foreach ($data as $wxapp) { + $names[$wxapp['wxapp_id']] = Setting::getItem('store', $wxapp['wxapp_id'])['name']; + } + return $names; + } + + /** + * 新增记录 + * @param $data + * @return bool + * @throws \think\exception\PDOException + */ + public function add($data) + { + if ($data['password'] !== $data['password_confirm']) { + $this->error = '确认密码不正确'; + return false; + } + $this->startTrans(); + try { + // 添加小程序记录 + $this->allowField(true)->save($data); + // 商城默认设置 + (new Setting)->insertDefault($this['wxapp_id'], $data['store_name']); + // 新增商家用户信息 + $StoreUser = new StoreUser; + if (!$StoreUser->add($this['wxapp_id'], $data)) { + $this->error = $StoreUser->error; + return false; + } + // 新增小程序默认帮助 + (new WxappHelp)->insertDefault($this['wxapp_id']); + // 新增小程序diy配置 + (new WxappPage)->insertDefault($this['wxapp_id']); + // 新增小程序分类页模板 + (new WxappCategory)->insertDefault($this['wxapp_id']); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 移入移出回收站 + * @param bool $is_recycle + * @return false|int + */ + public function recycle($is_recycle = true) + { + return $this->save(['is_recycle' => (int)$is_recycle]); + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]); + } + +} diff --git a/source/application/admin/model/WxappCategory.php b/source/application/admin/model/WxappCategory.php new file mode 100644 index 0000000..b42f29c --- /dev/null +++ b/source/application/admin/model/WxappCategory.php @@ -0,0 +1,28 @@ +save([ + 'wxapp_id' => $wxapp_id, + 'category_style' => '11', + 'share_title' => '', + ]); + } + +} \ No newline at end of file diff --git a/source/application/admin/model/WxappHelp.php b/source/application/admin/model/WxappHelp.php new file mode 100644 index 0000000..1aaa62c --- /dev/null +++ b/source/application/admin/model/WxappHelp.php @@ -0,0 +1,29 @@ +save([ + 'title' => '关于小程序', + 'content' => '小程序本身无需下载,无需注册,不占用手机内存,可以跨平台使用,响应迅速,体验接近原生APP。', + 'sort' => 100, + 'wxapp_id' => $wxapp_id + ]); + } + +} diff --git a/source/application/admin/model/WxappPage.php b/source/application/admin/model/WxappPage.php new file mode 100644 index 0000000..81f6917 --- /dev/null +++ b/source/application/admin/model/WxappPage.php @@ -0,0 +1,75 @@ +save([ + 'page_type' => 10, + 'page_name' => '小程序首页', + 'page_data' => [ + 'page' => [ + 'type' => 'page', + 'name' => '页面设置', + 'params' => [ + 'name' => '页面标题', + 'title' => '页面标题', + 'share_title' => '分享标题' + ], + 'style' => [ + 'titleTextColor' => 'black', + 'titleBackgroundColor' => '#ffffff', + ] + ], + 'items' => [ + [ + 'type' => 'search', + 'name' => '搜索框', + 'params' => ['placeholder' => '搜索商品'], + 'style' => [ + 'textAlign' => 'center', + 'searchStyle' => 'radius', + ], + ], + [ + 'type' => 'banner', + 'name' => '图片轮播', + 'style' => [ + 'btnColor' => '#ffffff', + 'btnShape' => 'round', + ], + 'params' => [ + 'interval' => '2800' + ], + 'data' => [ + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/banner/01.png', + 'linkUrl' => '', + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/banner/01.png', + 'linkUrl' => '', + ], + ], + ] + ], + ], + 'wxapp_id' => $wxapp_id + ]); + } + +} diff --git a/source/application/admin/model/admin/User.php b/source/application/admin/model/admin/User.php new file mode 100644 index 0000000..bc2b3dc --- /dev/null +++ b/source/application/admin/model/admin/User.php @@ -0,0 +1,81 @@ +where([ + 'user_name' => $data['user_name'], + 'password' => yoshop_hash($data['password']) + ])->find()) { + $this->error = '登录失败, 用户名或密码错误'; + return false; + } + // 保存登录状态 + Session::set('yoshop_admin', [ + 'user' => [ + 'admin_user_id' => $user['admin_user_id'], + 'user_name' => $user['user_name'], + ], + 'is_login' => true, + ]); + return true; + } + + /** + * 超管用户信息 + * @param $admin_user_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($admin_user_id) + { + return self::get($admin_user_id); + } + + /** + * 更新当前管理员信息 + * @param $data + * @return bool + */ + public function renew($data) + { + if ($data['password'] !== $data['password_confirm']) { + $this->error = '确认密码不正确'; + return false; + } + // 更新管理员信息 + if ($this->save([ + 'user_name' => $data['user_name'], + 'password' => yoshop_hash($data['password']), + ]) === false) { + return false; + } + // 更新session + Session::set('yoshop_admin.user', [ + 'admin_user_id' => $this['admin_user_id'], + 'user_name' => $data['user_name'], + ]); + return true; + } + +} \ No newline at end of file diff --git a/source/application/admin/model/store/Access.php b/source/application/admin/model/store/Access.php new file mode 100644 index 0000000..e00c2c6 --- /dev/null +++ b/source/application/admin/model/store/Access.php @@ -0,0 +1,656 @@ +formatTreeData($all); + } + + /** + * 新增记录 + * @param $data + * @return false|int + */ + public function add($data) + { + $data['wxapp_id'] = self::$wxapp_id; + return $this->allowField(true)->save($data); + } + + /** + * 更新记录 + * @param $data + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit($data) + { + // 判断上级角色是否为当前子级 + if ($data['parent_id'] > 0) { + // 获取所有上级id集 + $parentIds = $this->getTopAccessIds($data['parent_id']); + if (in_array($this['access_id'], $parentIds)) { + $this->error = '上级权限不允许设置为当前子权限'; + return false; + } + } + return $this->allowField(true)->save($data) !== false; + } + + /** + * 删除权限 + * @return bool|int + * @throws \think\exception\DbException + */ + public function remove() + { + // 判断是否存在下级权限 + if (self::detail(['parent_id' => $this['access_id']])) { + $this->error = '当前权限下存在子权限,请先删除'; + return false; + } + return $this->delete(); + } + + /** + * 获取所有上级id集 + * @param $access_id + * @param null $all + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getTopAccessIds($access_id, &$all = null) + { + static $ids = []; + is_null($all) && $all = $this->getAll(); + foreach ($all as $item) { + if ($item['access_id'] == $access_id && $item['parent_id'] > 0) { + $ids[] = $item['parent_id']; + $this->getTopAccessIds($item['parent_id'], $all); + } + } + return $ids; + } + + /** + * 获取权限列表 + * @param $all + * @param int $parent_id + * @param int $deep + * @return array + */ + private function formatTreeData(&$all, $parent_id = 0, $deep = 1) + { + static $tempTreeArr = []; + foreach ($all as $key => $val) { + if ($val['parent_id'] == $parent_id) { + // 记录深度 + $val['deep'] = $deep; + // 根据角色深度处理名称前缀 + $val['name_h1'] = $this->htmlPrefix($deep) . $val['name']; + $tempTreeArr[] = $val; + $this->formatTreeData($all, $val['access_id'], $deep + 1); + } + } + return $tempTreeArr; + } + + private function htmlPrefix($deep) + { + // 根据角色深度处理名称前缀 + $prefix = ''; + if ($deep > 1) { + for ($i = 1; $i <= $deep - 1; $i++) { + $prefix .= '   ├ '; + } + $prefix .= ' '; + } + return $prefix; + } + + /** + * 新增默认权限 + */ + public function insertDefault() + { + $defaultData = $this->defaultData(); + $this->buildData($defaultData); + } + + /** + * 生成并写入默认数据 + * @param $defaultData + * @param int $parent_id + */ + private function buildData(&$defaultData, $parent_id = 0) + { + foreach ($defaultData as $key => $item) { + // 保存数据 + $model = new static; + $model->save([ + 'name' => $item['name'], + 'url' => $item['url'], + 'parent_id' => $parent_id, + 'sort' => 100, + ]); + if (isset($item['subset']) && !empty($item['subset'])) { + $this->buildData($item['subset'], $model['access_id']); + } + } + } + + /** + * 默认权限数据 + * @return array + */ + private function defaultData() + { + return [ + [ + 'name' => '首页', + 'url' => 'index/index' + ], + [ + 'name' => '管理员', + 'url' => 'store', + 'subset' => [ + [ + 'name' => '管理员管理', + 'url' => 'store.user', + 'subset' => [ + [ + 'name' => '管理员列表', + 'url' => 'store.user/index' + ], + [ + 'name' => '添加管理员', + 'url' => 'store.user/add' + ], + [ + 'name' => '编辑管理员', + 'url' => 'store.user/edit' + ], + [ + 'name' => '删除管理员', + 'url' => 'store.user/delete' + ], + ] + ], + [ + 'name' => '角色管理', + 'url' => 'store.role', + 'subset' => [ + [ + 'name' => '角色列表', + 'url' => 'store.role/index' + ], + [ + 'name' => '添加角色', + 'url' => 'store.role/add' + ], + [ + 'name' => '编辑角色', + 'url' => 'store.role/edit' + ], + [ + 'name' => '删除角色', + 'url' => 'store.role/delete' + ], + ] + ], + [ + 'name' => '权限管理', + 'url' => 'store.access', + 'subset' => [ + [ + 'name' => '权限列表', + 'url' => 'store.access/index' + ], + [ + 'name' => '添加权限', + 'url' => 'store.access/add' + ], + [ + 'name' => '编辑权限', + 'url' => 'store.access/edit' + ], + [ + 'name' => '删除权限', + 'url' => 'store.access/delete' + ], + ] + ], + ] + ], + [ + 'name' => '商品管理', + 'url' => 'goods', + 'subset' => [ + [ + 'name' => '商品管理', + 'url' => 'goods', + 'subset' => [ + [ + 'name' => '商品列表', + 'url' => 'goods/index', + ], + [ + 'name' => '添加商品', + 'url' => 'goods/add', + ], + [ + 'name' => '编辑商品', + 'url' => 'goods/edit', + ], + [ + 'name' => '复制商品', + 'url' => 'goods/copy', + ], + [ + 'name' => '删除商品', + 'url' => 'goods/delete', + ], + [ + 'name' => '商品上下架', + 'url' => 'goods/state', + ], + ] + ], + [ + 'name' => '商品分类', + 'url' => 'goods.category', + 'subset' => [ + [ + 'name' => '分类列表', + 'url' => 'goods.category/index', + ], + [ + 'name' => '添加分类', + 'url' => 'goods.category/add', + ], + [ + 'name' => '编辑分类', + 'url' => 'goods.category/edit', + ], + [ + 'name' => '删除分类', + 'url' => 'goods.category/delete', + ], + ], + ], + [ + 'name' => '商品评价', + 'url' => 'goods.comment', + 'subset' => [ + [ + 'name' => '评价列表', + 'url' => 'goods.comment/index', + ], + [ + 'name' => '评价详情', + 'url' => 'goods.comment/detail', + ], + [ + 'name' => '删除评价', + 'url' => 'goods.comment/delete', + ], + ], + ], + ] + ], + [ + 'name' => '订单管理', + 'url' => 'order', + 'subset' => [ + [ + 'name' => '订单列表', + 'url' => '', + 'subset' => [ + [ + 'name' => '待发货', + 'url' => 'order/delivery_list' + ], + [ + 'name' => '待收货', + 'url' => 'order/receipt_list' + ], + [ + 'name' => '待付款', + 'url' => 'order/pay_list' + ], + [ + 'name' => '已完成', + 'url' => 'order/complete_list' + ], + [ + 'name' => '已取消', + 'url' => 'order/cancel_list' + ], + [ + 'name' => '全部订单', + 'url' => 'order/all_list', + ], + ] + ], + [ + 'name' => '订单详情', + 'url' => '', + 'subset' => [ + [ + 'name' => '详情信息', + 'url' => 'order/detail', + ], + [ + 'name' => '确认发货', + 'url' => 'order/delivery', + ], + [ + 'name' => '修改订单价格', + 'url' => 'order/updateprice', + ], + ] + ], + [ + 'name' => '订单导出', + 'url' => 'order.operate/export', + ], + [ + 'name' => '批量发货', + 'url' => 'order.operate/batchdelivery', + ], + ] + ], + [ + 'name' => '用户管理', + 'url' => 'user', + 'subset' => [ + [ + 'name' => '用户列表', + 'url' => 'user/index' + ], + [ + 'name' => '删除用户', + 'url' => 'user/delete' + ], + ] + ], + [ + 'name' => '营销设置', + 'url' => 'market', + 'subset' => [ + [ + 'name' => '优惠券', + 'url' => 'coupon', + 'subset' => [ + [ + 'name' => '优惠券列表', + 'url' => 'market.coupon/index', + ], + [ + 'name' => '新增优惠券', + 'url' => 'market.coupon/add', + ], + [ + 'name' => '编辑优惠券', + 'url' => 'market.coupon/edit', + ], + [ + 'name' => '删除优惠券', + 'url' => 'market.coupon/delete', + ], + [ + 'name' => '领取记录', + 'url' => 'market.coupon/receive', + ] + ] + ] + ] + ], + [ + 'name' => '小程序', + 'url' => 'wxapp', + 'subset' => [ + [ + 'name' => '小程序设置', + 'url' => 'wxapp/setting', + ], + [ + 'name' => '页面管理', + 'url' => 'wxapp.page', + 'subset' => [ + [ + 'name' => '页面设计', + 'url' => '', + 'subset' => [ + [ + 'name' => '页面列表', + 'url' => 'wxapp.page/index', + ], + [ + 'name' => '新增页面', + 'url' => 'wxapp.page/add', + ], + [ + 'name' => '编辑页面', + 'url' => 'wxapp.page/edit', + ], + [ + 'name' => '设为首页', + 'url' => 'wxapp.page/sethome', + ], + ] + ], + [ + 'name' => '分类页模板', + 'url' => 'wxapp.page/category', + ], + [ + 'name' => '页面链接', + 'url' => 'wxapp.page/links', + ], + ] + ], + [ + 'name' => '帮助中心', + 'url' => 'wxapp.help', + 'subset' => [ + [ + 'name' => '帮助列表', + 'url' => 'wxapp.help/index', + ], + [ + 'name' => '新增帮助', + 'url' => 'wxapp.help/add', + ], + [ + 'name' => '编辑帮助', + 'url' => 'wxapp.help/edit', + ], + [ + 'name' => '删除帮助', + 'url' => 'wxapp.help/delete', + ], + ] + ], + ] + ], + [ + 'name' => '应用中心', + 'url' => 'apps', + 'subset' => [ + [ + 'name' => '分销中心', + 'url' => 'apps.dealer', + 'subset' => [ + [ + 'name' => '入驻申请', + 'url' => 'apps.dealer.apply', + 'subset' => [ + [ + 'name' => '申请列表', + 'url' => 'apps.dealer.apply/index' + ], + [ + 'name' => '分销商审核', + 'url' => 'apps.dealer.apply/submit' + ] + ] + ], + [ + 'name' => '分销商用户', + 'url' => 'apps.dealer.user', + 'subset' => [ + [ + 'name' => '分销商列表', + 'url' => 'apps.dealer.user/index', + ], + [ + 'name' => '删除分销商', + 'url' => 'apps.dealer.user/delete' + ], + [ + 'name' => '分销商二维码', + 'url' => 'apps.dealer.user/qrcode' + ] + ] + ], + [ + 'name' => '分销订单', + 'url' => 'apps.dealer.order/index', + ], + [ + 'name' => '提现申请', + 'url' => 'apps.dealer.withdraw', + 'subset' => [ + [ + 'name' => '申请列表', + 'url' => 'apps.dealer.withdraw/index', + ], + [ + 'name' => '提现审核', + 'url' => 'apps.dealer.withdraw/submit' + ], + [ + 'name' => '确认打款', + 'url' => 'apps.dealer.withdraw/money' + ] + ] + ], + [ + 'name' => '分销设置', + 'url' => 'apps.dealer.setting/index', + ], + [ + 'name' => '分销海报', + 'url' => 'apps.dealer.setting/qrcode', + ], + ] + ], + ] + ], + [ + 'name' => '设置', + 'url' => 'setting', + 'subset' => [ + [ + 'name' => '商城设置', + 'url' => 'setting/store', + ], + [ + 'name' => '交易设置', + 'url' => 'setting/trade', + ], + [ + 'name' => '配送设置', + 'url' => 'setting.delivery', + 'subset' => [ + [ + 'name' => '运费模板列表', + 'url' => 'setting.delivery/index' + ], + [ + 'name' => '新增运费模板', + 'url' => 'setting.delivery/add' + ], + [ + 'name' => '编辑运费模板', + 'url' => 'setting.delivery/edit' + ], + [ + 'name' => '删除运费模板', + 'url' => 'setting.delivery/delete' + ], + ] + ], + [ + 'name' => '物流公司', + 'url' => 'setting.express', + 'subset' => [ + [ + 'name' => '物流公司列表', + 'url' => 'setting.express/index' + ], + [ + 'name' => '新增物流公司', + 'url' => 'setting.express/add' + ], + [ + 'name' => '编辑物流公司', + 'url' => 'setting.express/edit' + ], + [ + 'name' => '删除物流公司', + 'url' => 'setting.express/delete' + ], + ] + ], + [ + 'name' => '短信通知', + 'url' => 'setting/sms', + ], + [ + 'name' => '模板消息', + 'url' => 'setting/tplmsg', + ], + [ + 'name' => '上传设置', + 'url' => 'setting/storage', + ], + [ + 'name' => '其他', + 'url' => '', + 'subset' => [ + [ + 'name' => '清理缓存', + 'url' => 'setting.cache/clear', + ], + ] + ] + ] + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/admin/model/store/User.php b/source/application/admin/model/store/User.php new file mode 100644 index 0000000..076e460 --- /dev/null +++ b/source/application/admin/model/store/User.php @@ -0,0 +1,46 @@ +error = '商家用户名已存在'; + return false; + } + return $this->save([ + 'user_name' => $data['user_name'], + 'password' => yoshop_hash($data['password']), + 'wxapp_id' => $wxapp_id, + ]); + } + + /** + * 商家用户登录 + * @param $wxapp_id + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function login($wxapp_id) + { + // 验证用户名密码是否正确 + $user = self::detail(['wxapp_id' => $wxapp_id], ['wxapp']); + $this->loginState($user); + } + +} diff --git a/source/application/admin/view/admin/user/renew.php b/source/application/admin/view/admin/user/renew.php new file mode 100644 index 0000000..30f51cf --- /dev/null +++ b/source/application/admin/view/admin/user/renew.php @@ -0,0 +1,55 @@ +
+
+
+
+
+
+
+
+
管理员设置
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/admin/view/index/index.php b/source/application/admin/view/index/index.php new file mode 100644 index 0000000..a85ded8 --- /dev/null +++ b/source/application/admin/view/index/index.php @@ -0,0 +1,13 @@ + +
+
+
+
小程序运营管理系统
+
Welcome To YoShop System
+
+

平台运营管理后台

+
+
+
+
+ diff --git a/source/application/admin/view/layouts/layout.php b/source/application/admin/view/layouts/layout.php new file mode 100644 index 0000000..03dc60c --- /dev/null +++ b/source/application/admin/view/layouts/layout.php @@ -0,0 +1,104 @@ + + + + + + + 运营管理系统 + + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+
+
+ + +
+ + + + + +
+ {__CONTENT__} +
+ +
+ + +
+ 当前系统版本号:v +
+
+ + + + + + + + + diff --git a/source/application/admin/view/passport/login.php b/source/application/admin/view/passport/login.php new file mode 100644 index 0000000..16fcf1e --- /dev/null +++ b/source/application/admin/view/passport/login.php @@ -0,0 +1,63 @@ + + + + + + 商城系统登录 + + + + +
+ +
+ + + + + + diff --git a/source/application/admin/view/setting/cache/clear.php b/source/application/admin/view/setting/cache/clear.php new file mode 100644 index 0000000..9e6cfe6 --- /dev/null +++ b/source/application/admin/view/setting/cache/clear.php @@ -0,0 +1,78 @@ +
+
+
+
+
+
+
+
清理缓存
+
+
+ +
+ + +
+
+ +
+ +
+ + +
+ 此操作将会强制清空所有缓存文件,包含用户授权登录状态、用户购物车数据,仅允许在开发环境中使用 + +
+
+
+ + + +
+
+ +
+
+
+
+
+
+
+
+ diff --git a/source/application/admin/view/setting/science/index.php b/source/application/admin/view/setting/science/index.php new file mode 100644 index 0000000..7c731bc --- /dev/null +++ b/source/application/admin/view/setting/science/index.php @@ -0,0 +1,100 @@ +
+
+
+
+
+
+
+
服务器信息
+
+
+
+ + + + + + + + + + + + + + + +
参数
+
+
+ +
+
PHP环境要求
+
+
+
+ + + + + + + + + + + + + + + + + +
选项要求状态
+ + + + + +
+
+
+ +
+
目录权限监测
+
+
+
+ + + + + + + + + + + + + + + + + +
名称路径状态
+ + + + + +
+
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/source/application/admin/view/store/access/add.php b/source/application/admin/view/store/access/add.php new file mode 100644 index 0000000..b59d92d --- /dev/null +++ b/source/application/admin/view/store/access/add.php @@ -0,0 +1,67 @@ +
+
+
+
+
+
+
+
+
添加权限
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 例如:index/index +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/admin/view/store/access/edit.php b/source/application/admin/view/store/access/edit.php new file mode 100644 index 0000000..7c7b09e --- /dev/null +++ b/source/application/admin/view/store/access/edit.php @@ -0,0 +1,69 @@ +
+
+
+
+
+
+
+
+
编辑权限
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/admin/view/store/access/index.php b/source/application/admin/view/store/access/index.php new file mode 100644 index 0000000..91201b9 --- /dev/null +++ b/source/application/admin/view/store/access/index.php @@ -0,0 +1,83 @@ +
+
+
+
+
+
权限列表
+
+
+
+
+

1:此处数据为商城商户后台的页面url,用于给管理员角色设置操作权限

+

2:如非二次开发需求,本页面数据无需更改

+

3:商户后台菜单配置文件:source/application/store/extra/menus.php

+
+
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
权限ID权限名称权限url排序添加时间操作
+ +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/admin/view/store/add.php b/source/application/admin/view/store/add.php new file mode 100644 index 0000000..0118250 --- /dev/null +++ b/source/application/admin/view/store/add.php @@ -0,0 +1,70 @@ +
+
+
+
+
+
+
+
新增小程序商城
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+ +
+ + 商家后台用户名 +
+
+
+ +
+ + 商家后台用户密码 +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ diff --git a/source/application/admin/view/store/index.php b/source/application/admin/view/store/index.php new file mode 100644 index 0000000..53cb201 --- /dev/null +++ b/source/application/admin/view/store/index.php @@ -0,0 +1,80 @@ +
+
+
+
+
商城列表
+
+
+
+
+
+ +
+
+
+
+ + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + +
商城ID商城名称添加时间操作
+

+
+

+
+ +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+ + diff --git a/source/application/admin/view/store/recycle.php b/source/application/admin/view/store/recycle.php new file mode 100644 index 0000000..4376327 --- /dev/null +++ b/source/application/admin/view/store/recycle.php @@ -0,0 +1,75 @@ +
+
+
+
+
回收站列表
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + +
商城ID商城名称添加时间操作
+

+
+

+
+ +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+ + diff --git a/source/application/api/behavior/order/PaySuccess.php b/source/application/api/behavior/order/PaySuccess.php new file mode 100644 index 0000000..02baa03 --- /dev/null +++ b/source/application/api/behavior/order/PaySuccess.php @@ -0,0 +1,114 @@ + 'app\api\service\master\order\PaySuccess', + OrderSourceEnum::BARGAIN => 'app\api\service\bargain\order\PaySuccess', + OrderSourceEnum::SHARP => 'app\api\service\sharp\order\PaySuccess', + ]; + + /** + * 执行入口 + * @param $order + * @param int $orderType + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function run($order, $orderType = OrderTypeEnum::MASTER) + { + // 设置当前类的属性 + $this->setAttribute($order, $orderType); + // 订单公共业务 + $this->onCommonEvent(); + // 普通订单业务 + if ($orderType == OrderTypeEnum::MASTER) { + $this->onMasterEvent(); + } + // 订单来源回调业务 + $this->onSourceCallback(); + return true; + } + + /** + * 设置当前类的属性 + * @param $order + * @param int $orderType + */ + private function setAttribute($order, $orderType = OrderTypeEnum::MASTER) + { + $this->order = $order; + $this->wxappId = $this->order['wxapp_id']; + $this->orderType = $orderType; + } + + /** + * 订单公共业务 + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + private function onCommonEvent() + { + // 发送消息通知 + (new MessageService)->payment($this->order, $this->orderType); + // 小票打印 + (new PrinterService)->printTicket($this->order, OrderStatusEnum::ORDER_PAYMENT); + } + + /** + * 普通订单业务 + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + private function onMasterEvent() + { + // 同步好物圈 + (new WowOrder($this->wxappId))->import([$this->order], true); + } + + /** + * 订单来源回调业务 + * @return bool + */ + private function onSourceCallback() + { + if (!isset($this->order['order_source'])) { + return false; + } + if (!isset($this->sourceCallbackClass[$this->order['order_source']])) { + return false; + } + $class = $this->sourceCallbackClass[$this->order['order_source']]; + return !is_null($class) ? (new $class)->onPaySuccess($this->order) : false; + } + +} \ No newline at end of file diff --git a/source/application/api/config.php b/source/application/api/config.php new file mode 100644 index 0000000..df15d4b --- /dev/null +++ b/source/application/api/config.php @@ -0,0 +1,8 @@ + 'json', + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => 'trim,htmlspecialchars,filter_emoji', +]; \ No newline at end of file diff --git a/source/application/api/controller/Address.php b/source/application/api/controller/Address.php new file mode 100644 index 0000000..d807129 --- /dev/null +++ b/source/application/api/controller/Address.php @@ -0,0 +1,112 @@ +getUser(); + $model = new UserAddress; + $list = $model->getList($user['user_id']); + return $this->renderSuccess([ + 'list' => $list, + 'default_id' => $user['address_id'], + ]); + } + + /** + * 添加收货地址 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function add() + { + $model = new UserAddress; + if ($model->add($this->getUser(), $this->request->post())) { + return $this->renderSuccess([], '添加成功'); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 收货地址详情 + * @param $address_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function detail($address_id) + { + $user = $this->getUser(); + $detail = UserAddress::detail($user['user_id'], $address_id); + $region = array_values($detail['region']); + return $this->renderSuccess(compact('detail', 'region')); + } + + /** + * 编辑收货地址 + * @param $address_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function edit($address_id) + { + $user = $this->getUser(); + $model = UserAddress::detail($user['user_id'], $address_id); + if ($model->edit($this->request->post())) { + return $this->renderSuccess([], '更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 设为默认地址 + * @param $address_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function setDefault($address_id) + { + $user = $this->getUser(); + $model = UserAddress::detail($user['user_id'], $address_id); + if ($model->setDefault($user)) { + return $this->renderSuccess([], '设置成功'); + } + return $this->renderError($model->getError() ?: '设置失败'); + } + + /** + * 删除收货地址 + * @param $address_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function delete($address_id) + { + $user = $this->getUser(); + $model = UserAddress::detail($user['user_id'], $address_id); + if ($model->remove($user)) { + return $this->renderSuccess([], '删除成功'); + } + return $this->renderError($model->getError() ?: '删除失败'); + } + +} diff --git a/source/application/api/controller/Article.php b/source/application/api/controller/Article.php new file mode 100644 index 0000000..1c76060 --- /dev/null +++ b/source/application/api/controller/Article.php @@ -0,0 +1,53 @@ +renderSuccess(compact('categoryList')); + } + + /** + * 文章列表 + * @param int $category_id + * @return array + * @throws \think\exception\DbException + */ + public function lists($category_id = 0) + { + $model = new ArticleModel; + $list = $model->getList($category_id); + return $this->renderSuccess(compact('list')); + } + + /** + * 文章详情 + * @param $article_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function detail($article_id) + { + $detail = ArticleModel::detail($article_id); + return $this->renderSuccess(compact('detail')); + } + +} diff --git a/source/application/api/controller/Cart.php b/source/application/api/controller/Cart.php new file mode 100644 index 0000000..86d2796 --- /dev/null +++ b/source/application/api/controller/Cart.php @@ -0,0 +1,96 @@ +user = $this->getUser(); + $this->model = new CartModel($this->user); + } + + /** + * 购物车列表 + * @return array + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function lists() + { + // 请求参数 + $param = $this->request->param(); + $cartIds = isset($param['cart_ids']) ? $param['cart_ids'] : ''; + // 购物车商品列表 + $goodsList = $this->model->getList($cartIds); + // 获取订单结算信息 + $Checkout = new CheckoutModel; + $orderInfo = $Checkout->onCheckout($this->user, $goodsList); + return $this->renderSuccess($orderInfo); + } + + /** + * 加入购物车 + * @param int $goods_id 商品id + * @param int $goods_num 商品数量 + * @param string $goods_sku_id 商品sku索引 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function add($goods_id, $goods_num, $goods_sku_id) + { + if (!$this->model->add($goods_id, $goods_num, $goods_sku_id)) { + return $this->renderError($this->model->getError() ?: '加入购物车失败'); + } + // 购物车商品总数量 + $totalNum = $this->model->getGoodsNum(); + return $this->renderSuccess(['cart_total_num' => $totalNum], '加入购物车成功'); + } + + /** + * 减少购物车商品数量 + * @param $goods_id + * @param $goods_sku_id + * @return array + */ + public function sub($goods_id, $goods_sku_id) + { + $this->model->sub($goods_id, $goods_sku_id); + return $this->renderSuccess(); + } + + /** + * 删除购物车中指定商品 + * @param $goods_sku_id (支持字符串ID集) + * @return array + */ + public function delete($goods_sku_id) + { + $this->model->delete($goods_sku_id); + return $this->renderSuccess(); + } + +} diff --git a/source/application/api/controller/Category.php b/source/application/api/controller/Category.php new file mode 100644 index 0000000..27c18ff --- /dev/null +++ b/source/application/api/controller/Category.php @@ -0,0 +1,29 @@ +renderSuccess(compact('templet', 'list')); + } + +} diff --git a/source/application/api/controller/Comment.php b/source/application/api/controller/Comment.php new file mode 100644 index 0000000..6dab846 --- /dev/null +++ b/source/application/api/controller/Comment.php @@ -0,0 +1,29 @@ +getGoodsCommentList($goods_id, $scoreType); + $total = $model->getTotal($goods_id); + return $this->renderSuccess(compact('list', 'total')); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/Controller.php b/source/application/api/controller/Controller.php new file mode 100644 index 0000000..8850353 --- /dev/null +++ b/source/application/api/controller/Controller.php @@ -0,0 +1,139 @@ +wxapp_id = $this->getWxappId(); + // 验证当前小程序状态 + $this->checkWxapp(); + } + + /** + * 获取当前小程序ID + * @return mixed + * @throws BaseException + */ + private function getWxappId() + { + if (!$wxapp_id = $this->request->param('wxapp_id')) { + throw new BaseException(['msg' => '缺少必要的参数:wxapp_id']); + } + return $wxapp_id; + } + + /** + * 验证当前小程序状态 + * @throws BaseException + * @throws \think\exception\DbException + */ + private function checkWxapp() + { + $wxapp = WxappModel::detail($this->wxapp_id); + if (empty($wxapp)) { + throw new BaseException(['msg' => '当前小程序信息不存在']); + } + if ($wxapp['is_recycle'] || $wxapp['is_delete']) { + throw new BaseException(['msg' => '当前小程序已删除']); + } + } + + /** + * 获取当前用户信息 + * @param bool $is_force + * @return UserModel|bool|null + * @throws BaseException + * @throws \think\exception\DbException + */ + protected function getUser($is_force = true) + { + if (!$token = $this->request->param('token')) { + $is_force && $this->throwError('缺少必要的参数:token', -1); + return false; + } + if (!$user = UserModel::getUser($token)) { + $is_force && $this->throwError('没有找到用户信息', -1); + return false; + } + return $user; + } + + /** + * 输出错误信息 + * @param int $code + * @param $msg + * @throws BaseException + */ + protected function throwError($msg, $code = 0) + { + throw new BaseException(['code' => $code, 'msg' => $msg]); + } + + /** + * 返回封装后的 API 数据到客户端 + * @param int $code + * @param string $msg + * @param array $data + * @return array + */ + protected function renderJson($code = self::JSON_SUCCESS_STATUS, $msg = '', $data = []) + { + return compact('code', 'msg', 'data'); + } + + /** + * 返回操作成功json + * @param array $data + * @param string|array $msg + * @return array + */ + protected function renderSuccess($data = [], $msg = 'success') + { + return $this->renderJson(self::JSON_SUCCESS_STATUS, $msg, $data); + } + + /** + * 返回操作失败json + * @param string $msg + * @param array $data + * @return array + */ + protected function renderError($msg = 'error', $data = []) + { + return $this->renderJson(self::JSON_ERROR_STATUS, $msg, $data); + } + + /** + * 获取post数据 (数组) + * @param $key + * @return mixed + */ + protected function postData($key = null) + { + return $this->request->post(is_null($key) ? '' : $key . '/a'); + } + +} diff --git a/source/application/api/controller/Coupon.php b/source/application/api/controller/Coupon.php new file mode 100644 index 0000000..b4fa88b --- /dev/null +++ b/source/application/api/controller/Coupon.php @@ -0,0 +1,29 @@ +getList($this->getUser(false)); + return $this->renderSuccess(compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/Goods.php b/source/application/api/controller/Goods.php new file mode 100644 index 0000000..8804b7c --- /dev/null +++ b/source/application/api/controller/Goods.php @@ -0,0 +1,81 @@ +request->param(), [ + 'status' => 10 + ]); + // 获取列表数据 + $model = new GoodsModel; + $list = $model->getList($param, $this->getUser(false)); + return $this->renderSuccess(compact('list')); + } + + /** + * 获取商品详情 + * @param $goods_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function detail($goods_id) + { + // 用户信息 + $user = $this->getUser(false); + // 商品详情 + $model = new GoodsModel; + $goods = $model->getDetails($goods_id, $this->getUser(false)); + if ($goods === false) { + return $this->renderError($model->getError() ?: '商品信息不存在'); + } + // 多规格商品sku信息, todo: 已废弃 v1.1.25 + $specData = $goods['spec_type'] == 20 ? $model->getManySpecData($goods['spec_rel'], $goods['sku']) : null; + return $this->renderSuccess([ + // 商品详情 + 'detail' => $goods, + // 购物车商品总数量 + 'cart_total_num' => $user ? (new CartModel($user))->getGoodsNum() : 0, + // 多规格商品sku信息 + 'specData' => $specData, + ]); + } + + /** + * 生成商品海报 + * @param $goods_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function poster($goods_id) + { + // 商品详情 + $detail = GoodsModel::detail($goods_id); + $Qrcode = new GoodsPoster($detail, $this->getUser(false)); + return $this->renderSuccess([ + 'qrcode' => $Qrcode->getImage(), + ]); + } + +} diff --git a/source/application/api/controller/Notify.php b/source/application/api/controller/Notify.php new file mode 100644 index 0000000..938b340 --- /dev/null +++ b/source/application/api/controller/Notify.php @@ -0,0 +1,25 @@ +notify(); + } +} diff --git a/source/application/api/controller/Order.php b/source/application/api/controller/Order.php new file mode 100644 index 0000000..bad70b8 --- /dev/null +++ b/source/application/api/controller/Order.php @@ -0,0 +1,141 @@ +user = $this->getUser(); + // 验证类 + $this->validate = new CheckoutValidate; + } + + /** + * 订单确认-立即购买 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function buyNow() + { + // 实例化结算台服务 + $Checkout = new CheckoutModel; + // 订单结算api参数 + $params = $Checkout->setParam($this->getParam([ + 'goods_id' => 0, + 'goods_num' => 0, + 'goods_sku_id' => '', + ])); + // 表单验证 + if (!$this->validate->scene('buyNow')->check($params)) { + return $this->renderError($this->validate->getError()); + } + // 立即购买:获取订单商品列表 + $model = new OrderModel; + $goodsList = $model->getOrderGoodsListByNow( + $params['goods_id'], + $params['goods_sku_id'], + $params['goods_num'] + ); + // 获取订单确认信息 + $orderInfo = $Checkout->onCheckout($this->user, $goodsList); + if ($this->request->isGet()) { + return $this->renderSuccess($orderInfo); + } + // 订单结算提交 + if ($Checkout->hasError()) { + return $this->renderError($Checkout->getError()); + } + // 创建订单 + if (!$Checkout->createOrder($orderInfo)) { + return $this->renderError($Checkout->getError() ?: '订单创建失败'); + } + // 构建微信支付请求 + $payment = $model->onOrderPayment($this->user, $Checkout->model, $params['pay_type']); + // 返回结算信息 + return $this->renderSuccess([ + 'order_id' => $Checkout->model['order_id'], // 订单id + 'pay_type' => $params['pay_type'], // 支付方式 + 'payment' => $payment // 微信支付参数 + ], ['success' => '支付成功', 'error' => '订单未支付']); + } + + /** + * 订单确认-购物车结算 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function cart() + { + // 实例化结算台服务 + $Checkout = new CheckoutModel; + // 订单结算api参数 + $params = $Checkout->setParam($this->getParam([ + 'cart_ids' => '', + ])); + // 商品结算信息 + $CartModel = new CartModel($this->user); + // 购物车商品列表 + $goodsList = $CartModel->getList($params['cart_ids']); + // 获取订单结算信息 + $orderInfo = $Checkout->onCheckout($this->user, $goodsList); + if ($this->request->isGet()) { + return $this->renderSuccess($orderInfo); + } + // 创建订单 + if (!$Checkout->createOrder($orderInfo)) { + return $this->renderError($Checkout->getError() ?: '订单创建失败'); + } + // 移出购物车中已下单的商品 + $CartModel->clearAll($params['cart_ids']); + // 构建微信支付请求 + $payment = $Checkout->onOrderPayment(); + // 返回状态 + return $this->renderSuccess([ + 'order_id' => $Checkout->model['order_id'], // 订单id + 'pay_type' => $params['pay_type'], // 支付方式 + 'payment' => $payment // 微信支付参数 + ], ['success' => '支付成功', 'error' => '订单未支付']); + } + + /** + * 订单结算提交的参数 + * @param array $define + * @return array + */ + private function getParam($define = []) + { + return array_merge($define, $this->request->param()); + } + +} diff --git a/source/application/api/controller/Page.php b/source/application/api/controller/Page.php new file mode 100644 index 0000000..ed17cc3 --- /dev/null +++ b/source/application/api/controller/Page.php @@ -0,0 +1,31 @@ +getUser(false), $page_id); + return $this->renderSuccess($data); + } + +} diff --git a/source/application/api/controller/Recharge.php b/source/application/api/controller/Recharge.php new file mode 100644 index 0000000..3b28a77 --- /dev/null +++ b/source/application/api/controller/Recharge.php @@ -0,0 +1,67 @@ +getUser(); + // 充值套餐列表 + $planList = (new PlanModel)->getList(); + // 充值设置 + $setting = SettingModel::getItem('recharge'); + return $this->renderSuccess(compact('userInfo', 'planList', 'setting')); + } + + /** + * 确认充值 + * @param null $planId + * @param int $customMoney + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function submit($planId = null, $customMoney = 0) + { + // 用户信息 + $userInfo = $this->getUser(); + // 生成充值订单 + $model = new OrderModel; + if (!$model->createOrder($userInfo, $planId, $customMoney)) { + return $this->renderError($model->getError() ?: '充值失败'); + } + // 构建微信支付 + $payment = PaymentService::wechat( + $userInfo, + $model['order_id'], + $model['order_no'], + $model['pay_price'], + OrderTypeEnum::RECHARGE + ); + // 充值状态提醒 + $message = ['success' => '充值成功', 'error' => '订单未支付']; + return $this->renderSuccess(compact('payment', 'message'), $message); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/Shop.php b/source/application/api/controller/Shop.php new file mode 100644 index 0000000..d617e81 --- /dev/null +++ b/source/application/api/controller/Shop.php @@ -0,0 +1,41 @@ +getList(true, $longitude, $latitude); + return $this->renderSuccess(compact('list')); + } + + /** + * 门店详情 + * @param $shop_id + * @return array + * @throws \think\exception\DbException + */ + public function detail($shop_id) + { + $detail = ShopModel::detail($shop_id); + return $this->renderSuccess(compact('detail')); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/Upload.php b/source/application/api/controller/Upload.php new file mode 100644 index 0000000..6a590d4 --- /dev/null +++ b/source/application/api/controller/Upload.php @@ -0,0 +1,86 @@ +config = SettingModel::getItem('storage'); + // 验证用户 + $this->user = $this->getUser(); + } + + /** + * 图片上传接口 + * @return array + * @throws \think\Exception + */ + public function image() + { + // 实例化存储驱动 + $StorageDriver = new StorageDriver($this->config); + // 设置上传文件的信息 + $StorageDriver->setUploadFile('iFile'); + // 上传图片 + if (!$StorageDriver->upload()) { + return json(['code' => 0, 'msg' => '图片上传失败' . $StorageDriver->getError()]); + } + // 图片上传路径 + $fileName = $StorageDriver->getFileName(); + // 图片信息 + $fileInfo = $StorageDriver->getFileInfo(); + // 添加文件库记录 + $uploadFile = $this->addUploadFile($fileName, $fileInfo, 'image'); + // 图片上传成功 + return json(['code' => 1, 'msg' => '图片上传成功', 'data' => $uploadFile->visible(['file_id'])]); + } + + /** + * 添加文件库上传记录 + * @param $fileName + * @param $fileInfo + * @param $fileType + * @return UploadFile + */ + private function addUploadFile($fileName, $fileInfo, $fileType) + { + // 存储引擎 + $storage = $this->config['default']; + // 存储域名 + $fileUrl = isset($this->config['engine'][$storage]['domain']) + ? $this->config['engine'][$storage]['domain'] : ''; + // 添加文件库记录 + $model = new UploadFile; + $model->add([ + 'storage' => $storage, + 'file_url' => $fileUrl, + 'file_name' => $fileName, + 'file_size' => $fileInfo['size'], + 'file_type' => $fileType, + 'extension' => pathinfo($fileInfo['name'], PATHINFO_EXTENSION), + 'is_user' => 1 + ]); + return $model; + } + +} \ No newline at end of file diff --git a/source/application/api/controller/User.php b/source/application/api/controller/User.php new file mode 100644 index 0000000..511548a --- /dev/null +++ b/source/application/api/controller/User.php @@ -0,0 +1,43 @@ +renderSuccess([ + 'user_id' => $model->login($this->request->post()), + 'token' => $model->getToken() + ]); + } + + /** + * 当前用户详情 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function detail() + { + // 当前用户信息 + $userInfo = $this->getUser(); + return $this->renderSuccess(compact('userInfo')); + } + +} diff --git a/source/application/api/controller/Wxapp.php b/source/application/api/controller/Wxapp.php new file mode 100644 index 0000000..d3bafea --- /dev/null +++ b/source/application/api/controller/Wxapp.php @@ -0,0 +1,39 @@ +renderSuccess([]); + } + + /** + * 帮助中心 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function help() + { + $model = new WxappHelp; + $list = $model->getList(); + return $this->renderSuccess(compact('list')); + } + +} diff --git a/source/application/api/controller/balance/Log.php b/source/application/api/controller/balance/Log.php new file mode 100644 index 0000000..7cdc29f --- /dev/null +++ b/source/application/api/controller/balance/Log.php @@ -0,0 +1,28 @@ +getUser(); + $list = (new BalanceLogModel)->getList($user['user_id']); + return $this->renderSuccess(compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/bargain/Active.php b/source/application/api/controller/bargain/Active.php new file mode 100644 index 0000000..f532f15 --- /dev/null +++ b/source/application/api/controller/bargain/Active.php @@ -0,0 +1,84 @@ +getHallList(); + return $this->renderSuccess(compact('activeList')); + } + + /** + * 砍价活动详情 + * @param $active_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function detail($active_id) + { + // 获取砍价活动详情 + $model = new ActiveModel; + $active = $model->getDetail($active_id); + if ($active === false) { + return $this->renderError($model->getError()); + } + // 标记当前用户是否正在参与 + $task_id = $model->getWhetherPartake($active_id, $this->getUser(false)); + $is_partake = $task_id > 0; + // 获取商品详情 + $goods = GoodsModel::detail($active['goods_id']); + // 砍价规则 + $setting = SettingModel::getBasic(); + return $this->renderSuccess(compact('active', 'goods', 'setting', 'is_partake', 'task_id')); + } + + /** + * 生成商品海报 + * @param $active_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function poster($active_id) + { + // 获取砍价活动详情 + $model = new ActiveModel; + $active = $model->getDetail($active_id); + if ($active === false) { + return $this->renderError($model->getError()); + } + // 获取商品详情 + $goods = GoodsModel::detail($active['goods_id']); + // 生成商品海报图 + $Qrcode = new GoodsPoster($active, $goods, $this->getUser(false)); + return $this->renderSuccess([ + 'qrcode' => $Qrcode->getImage(), + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/bargain/Order.php b/source/application/api/controller/bargain/Order.php new file mode 100644 index 0000000..60596a8 --- /dev/null +++ b/source/application/api/controller/bargain/Order.php @@ -0,0 +1,101 @@ +user = $this->getUser(); + } + + /** + * 砍价订单结算 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function checkout() + { + // 实例化结算台服务 + $Checkout = new CheckoutModel; + // 订单结算api参数 + $params = $Checkout->setParam($this->getParam([ + 'task_id' => 0 + ])); + // 获取砍价任务详情 + $task = TaskModel::detail($params['task_id']); + // 获取砍价商品信息 + $goodsList = $task->getTaskGoods($params['task_id']); + if ($goodsList === false) { + return $this->renderError($task->getError()); + } + // 设置订单来源 + $Checkout->setOrderSource([ + 'source' => OrderSourceEnum::BARGAIN, + 'source_id' => $params['task_id'], + ]); + // 砍价商品不参与 等级折扣和优惠券折扣 + $Checkout->setCheckoutRule([ + 'is_user_grade' => false, + 'is_coupon' => false, + 'is_use_points' => false, + 'is_dealer' => SettingModel::getIsDealer(), + ]); + // 获取订单结算信息 + $orderInfo = $Checkout->onCheckout($this->user, $goodsList); + if ($this->request->isGet()) { + return $this->renderSuccess($orderInfo); + } + // submit:订单结算提交 + if ($Checkout->hasError()) { + return $this->renderError($Checkout->getError()); + } + // 创建订单 + if (!$Checkout->createOrder($orderInfo)) { + return $this->renderError($Checkout->getError() ?: '订单创建失败'); + } + // 订单创建后将砍价任务结束 + $task->setTaskEnd(); + // 构建微信支付请求 + $payment = $Checkout->onOrderPayment(); + // 支付状态提醒 + $message = ['success' => '支付成功', 'error' => '订单未支付']; + return $this->renderSuccess([ + 'order_id' => $Checkout->model['order_id'], // 订单id + 'pay_type' => $params['pay_type'], // 支付方式 + 'payment' => $payment // 微信支付参数 + ], $message); + } + + /** + * 订单结算提交的参数 + * @param array $define + * @return array + */ + private function getParam($define = []) + { + return array_merge($define, $this->request->param()); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/bargain/Task.php b/source/application/api/controller/bargain/Task.php new file mode 100644 index 0000000..7b8abb3 --- /dev/null +++ b/source/application/api/controller/bargain/Task.php @@ -0,0 +1,91 @@ +getMyList($this->getUser()['user_id']); + return $this->renderSuccess(compact('myList')); + } + + /** + * 创建砍价任务 + * @param $active_id + * @param $goods_sku_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function partake($active_id, $goods_sku_id) + { + // 用户信息 + $user = $this->getUser(); + // 创建砍价任务 + $model = new TaskModel; + if (!$model->partake($user['user_id'], $active_id, $goods_sku_id)) { + return $this->renderError($model->getError() ?: '砍价任务创建失败'); + } + return $this->renderSuccess([ + 'task_id' => $model['task_id'] + ]); + } + + /** + * 获取砍价任务详情 + * @param $task_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function detail($task_id) + { + $model = new TaskModel; + $detail = $model->getTaskDetail($task_id, $this->getUser(false)); + if ($detail === false) { + return $this->renderError($model->getError()); + } + // 砍价规则 + $setting = SettingModel::getBasic(); + return $this->renderSuccess(array_merge($detail, ['setting' => $setting])); + } + + /** + * 帮砍一刀 + * @param $task_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function help_cut($task_id) + { + // 加阻塞锁, 防止并发 + Lock::lockUp("bargain_help_cut_{$task_id}"); + // 砍价任务详情 + $model = TaskModel::detail($task_id); + // 砍一刀的金额 + $cut_money = $model->getCutMoney(); + // 帮砍一刀事件 + $status = $model->helpCut($this->getUser()); + // 解除并发锁 + Lock::unLock("bargain_help_cut_{$task_id}"); + if ($status == true) { + return $this->renderSuccess(compact('cut_money'), '砍价成功'); + } + return $this->renderError($model->getError() ?: '砍价失败'); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/points/Log.php b/source/application/api/controller/points/Log.php new file mode 100644 index 0000000..df39314 --- /dev/null +++ b/source/application/api/controller/points/Log.php @@ -0,0 +1,28 @@ +getUser(); + $list = (new PointsLogModel)->getList($user['user_id']); + return $this->renderSuccess(compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/recharge/Order.php b/source/application/api/controller/recharge/Order.php new file mode 100644 index 0000000..865a8eb --- /dev/null +++ b/source/application/api/controller/recharge/Order.php @@ -0,0 +1,28 @@ +getUser(); + $list = (new OrderModel)->getList($user['user_id']); + return $this->renderSuccess(compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/sharing/Active.php b/source/application/api/controller/sharing/Active.php new file mode 100644 index 0000000..e766e5d --- /dev/null +++ b/source/application/api/controller/sharing/Active.php @@ -0,0 +1,38 @@ +renderError('很抱歉,拼单不存在'); + } + // 拼团商品详情 + $model = new GoodsModel; + $goods = $model->getDetails($detail['goods_id'], $this->getUser(false)); + // 更多拼团商品 + $goodsList = $model->getList([], $this->getUser(false)); + return $this->renderSuccess(compact('detail', 'goods', 'goodsList')); + } + +} diff --git a/source/application/api/controller/sharing/Comment.php b/source/application/api/controller/sharing/Comment.php new file mode 100644 index 0000000..e2634b5 --- /dev/null +++ b/source/application/api/controller/sharing/Comment.php @@ -0,0 +1,68 @@ +getUser(); + // 订单信息 + $order = OrderModel::getUserOrderDetail($order_id, $user['user_id']); + // 验证订单是否已完成 + $model = new CommentModel; + if (!$model->checkOrderAllowComment($order)) { + return $this->renderError($model->getError()); + } + // 待评价商品列表 + /* @var \think\Collection $goodsList */ + $goodsList = OrderGoodsModel::getNotCommentGoodsList($order_id); + if ($goodsList->isEmpty()) { + return $this->renderError('该订单没有可评价的商品'); + } + // 提交商品评价 + if ($this->request->isPost()) { + $formData = $this->request->post('formData', '', null); + if ($model->addForOrder($order, $goodsList, $formData)) { + return $this->renderSuccess([], '评价发表成功'); + } + return $this->renderError($model->getError() ?: '评价发表失败'); + } + return $this->renderSuccess(compact('goodsList')); + } + + /** + * 商品评价列表 + * @param $goods_id + * @param int $scoreType + * @return array + * @throws \think\exception\DbException + */ + public function lists($goods_id, $scoreType = -1) + { + $model = new CommentModel; + $list = $model->getGoodsCommentList($goods_id, $scoreType); + $total = $model->getTotal($goods_id); + return $this->renderSuccess(compact('list', 'total')); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/sharing/Goods.php b/source/application/api/controller/sharing/Goods.php new file mode 100644 index 0000000..9dd5ba7 --- /dev/null +++ b/source/application/api/controller/sharing/Goods.php @@ -0,0 +1,83 @@ +request->param(), [ + 'status' => 10 + ]); + // 获取列表数据 + $model = new GoodsModel; + $list = $model->getList($param, $this->getUser(false)); + return $this->renderSuccess(compact('list')); + } + + /** + * 获取商品详情 + * @param $goods_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function detail($goods_id) + { + // 商品详情 + $model = new GoodsModel; + $goods = $model->getDetails($goods_id, $this->getUser(false)); + if ($goods === false) { + return $this->renderError($model->getError() ?: '商品信息不存在'); + } + // 多规格商品sku信息, todo: 已废弃 v1.1.25 + $specData = $goods['spec_type'] == 20 ? $model->getManySpecData($goods['spec_rel'], $goods['sku']) : null; + // 当前进行中的拼单 + $activeList = ActiveModel::getActivityListByGoods($goods_id, 2); + return $this->renderSuccess([ + // 商品详情 + 'detail' => $goods, + // 当前进行中的拼单 + 'activeList' => $activeList, + // 多规格商品sku信息 + 'specData' => $specData, + ]); + } + + /** + * 生成商品海报 + * @param $goods_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function poster($goods_id) + { + // 商品详情 + $detail = GoodsModel::detail($goods_id); + // 生成推广二维码 + $Qrcode = new GoodsPoster($detail, $this->getUser(false), 20); + return $this->renderSuccess([ + 'qrcode' => $Qrcode->getImage(), + ]); + } + +} diff --git a/source/application/api/controller/sharing/Index.php b/source/application/api/controller/sharing/Index.php new file mode 100644 index 0000000..cd07dcd --- /dev/null +++ b/source/application/api/controller/sharing/Index.php @@ -0,0 +1,26 @@ +renderSuccess(compact('categoryList')); + } + +} diff --git a/source/application/api/controller/sharing/Order.php b/source/application/api/controller/sharing/Order.php new file mode 100644 index 0000000..a12c9fd --- /dev/null +++ b/source/application/api/controller/sharing/Order.php @@ -0,0 +1,242 @@ +user = $this->getUser(); + // 验证类 + $this->validate = new CheckoutValidate; + } + + /** + * 订单结算台 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function checkout() + { + // 实例化结算台服务 + $Checkout = new CheckoutModel; + // 订单结算api参数 + $params = $Checkout->setParam($this->getParam([ + 'goods_id' => 0, + 'goods_num' => 0, + 'goods_sku_id' => '', + ])); + // 表单验证 + if (!$this->validate->scene('buyNow')->check($params)) { + return $this->renderError($this->validate->getError()); + } + // 立即购买:获取订单商品列表 + $model = new OrderModel; + $goodsList = $model->getOrderGoodsListByNow($params); + // 获取订单确认信息 + $orderInfo = $Checkout->onCheckout($this->user, $goodsList); + if ($this->request->isGet()) { + return $this->renderSuccess($orderInfo); + } + // 订单结算提交 + if ($Checkout->hasError()) { + return $this->renderError($Checkout->getError()); + } + // 创建订单 + if (!$Checkout->createOrder($orderInfo)) { + return $this->renderError($Checkout->getError() ?: '订单创建失败'); + } + // 构建微信支付请求 + $payment = $model->onOrderPayment($this->user, $Checkout->model, $params['pay_type']); + // 返回结算信息 + return $this->renderSuccess([ + 'order_id' => $Checkout->model['order_id'], // 订单id + 'pay_type' => $params['pay_type'], // 支付方式 + 'payment' => $payment // 微信支付参数 + ], ['success' => '支付成功', 'error' => '订单未支付']); + } + + /** + * 订单结算提交的参数 + * @param array $define + * @return array + */ + private function getParam($define = []) + { + return array_merge($define, $this->request->param()); + } + + /** + * 我的拼团订单列表 + * @param $dataType + * @return array + * @throws \think\exception\DbException + */ + public function lists($dataType) + { + $model = new OrderModel; + $list = $model->getList($this->user['user_id'], $dataType); + return $this->renderSuccess(compact('list')); + } + + /** + * 拼团订单详情信息 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + */ + public function detail($order_id) + { + // 订单详情 + $model = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + // 该订单是否允许申请售后 + $model['isAllowRefund'] = $model->isAllowRefund(); + return $this->renderSuccess([ + 'order' => $model, // 订单详情 + 'setting' => [ + // 积分名称 + 'points_name' => SettingModel::getPointsName(), + ], + ]); + } + + /** + * 获取物流信息 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + */ + public function express($order_id) + { + // 订单信息 + $order = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + if (!$order['express_no']) { + return $this->renderError('没有物流信息'); + } + // 获取物流信息 + /* @var \app\store\model\Express $model */ + $model = $order['express']; + $express = $model->dynamic($model['express_name'], $model['express_code'], $order['express_no']); + if ($express === false) { + return $this->renderError($model->getError()); + } + return $this->renderSuccess(compact('express')); + } + + /** + * 取消订单 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + */ + public function cancel($order_id) + { + $model = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + if ($model->cancel($this->user)) { + return $this->renderSuccess($model->getError() ?: '订单取消成功'); + } + return $this->renderError($model->getError() ?: '订单取消失败'); + } + + /** + * 确认收货 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + */ + public function receipt($order_id) + { + $model = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + if ($model->receipt()) { + return $this->renderSuccess(); + } + return $this->renderError($model->getError()); + } + + /** + * 立即支付 + * @param int $order_id 订单id + * @param int $payType 支付方式 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function pay($order_id, $payType = PayTypeEnum::WECHAT) + { + // 订单详情 + $model = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + // 订单支付事件 + if (!$model->onPay($payType)) { + return $this->renderError($model->getError() ?: '订单支付失败'); + } + // 构建微信支付请求 + $payment = $model->onOrderPayment($this->user, $model, $payType); + // 支付状态提醒 + return $this->renderSuccess([ + 'order_id' => $model['order_id'], // 订单id + 'pay_type' => $payType, // 支付方式 + 'payment' => $payment // 微信支付参数 + ], ['success' => '支付成功', 'error' => '订单未支付']); + } + + /** + * 获取订单核销二维码 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function extractQrcode($order_id) + { + // 订单详情 + $order = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + // 判断是否为待核销订单 + if (!$order->checkExtractOrder($order)) { + return $this->renderError($order->getError()); + } + $Qrcode = new ExtractQRcode( + $this->wxapp_id, + $this->user, + $order_id, + OrderTypeEnum::SHARING + ); + return $this->renderSuccess([ + 'qrcode' => $Qrcode->getImage(), + ]); + } + +} diff --git a/source/application/api/controller/sharing/Refund.php b/source/application/api/controller/sharing/Refund.php new file mode 100644 index 0000000..21152c2 --- /dev/null +++ b/source/application/api/controller/sharing/Refund.php @@ -0,0 +1,109 @@ +user = $this->getUser(); // 用户信息 + } + + /** + * 用户售后单列表 + * @param int $state + * @return array + * @throws \think\exception\DbException + */ + public function lists($state = -1) + { + $model = new OrderRefundModel; + $list = $model->getList($this->user['user_id'], (int)$state); + return $this->renderSuccess(compact('list')); + } + + /** + * 申请售后 + * @param $order_goods_id + * @return array + * @throws \think\exception\DbException + * @throws \Exception + */ + public function apply($order_goods_id) + { + // 订单商品详情 + $goods = OrderGoodsModel::detail($order_goods_id); + if (isset($goods['refund']) && !empty($goods['refund'])) { + return $this->renderError('当前商品已申请售后'); + } + if (!$this->request->isPost()) { + return $this->renderSuccess(['detail' => $goods]); + } + // 新增售后单记录 + $model = new OrderRefundModel; + if ($model->apply($this->user, $goods, $this->request->post())) { + return $this->renderSuccess([], '提交成功'); + } + return $this->renderError($model->getError() ?: '提交失败'); + } + + /** + * 售后单详情 + * @param $order_refund_id + * @return array + * @throws \think\exception\DbException + */ + public function detail($order_refund_id) + { + // 售后单详情 + $detail = OrderRefundModel::detail([ + 'user_id' => $this->user['user_id'], + 'order_refund_id' => $order_refund_id + ]); + if (empty($detail)) { + return $this->renderError('售后单不存在'); + } + // 物流公司列表 + $expressList = ExpressModel::getAll(); + return $this->renderSuccess(compact('detail', 'expressList')); + } + + /** + * 用户发货 + * @param $order_refund_id + * @return array + * @throws \think\exception\DbException + */ + public function delivery($order_refund_id) + { + // 售后单详情 + $model = OrderRefundModel::detail([ + 'user_id' => $this->user['user_id'], + 'order_refund_id' => $order_refund_id + ]); + if ($model->delivery($this->postData())) { + return $this->renderSuccess([], '操作成功'); + } + return $this->renderError($model->getError() ?: '提交失败'); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/sharing/Setting.php b/source/application/api/controller/sharing/Setting.php new file mode 100644 index 0000000..a8f44a4 --- /dev/null +++ b/source/application/api/controller/sharing/Setting.php @@ -0,0 +1,25 @@ +renderSuccess(['setting' => compact('basic')]); + } + +} diff --git a/source/application/api/controller/sharp/Goods.php b/source/application/api/controller/sharp/Goods.php new file mode 100644 index 0000000..3fdfcf5 --- /dev/null +++ b/source/application/api/controller/sharp/Goods.php @@ -0,0 +1,70 @@ +getGoodsListByActiveTimeId($active_time_id); + return $this->renderSuccess(compact('list')); + } + + /** + * 获取活动商品详情 + * @param $active_time_id + * @param $sharp_goods_id + * @return array + * @throws \think\exception\DbException + */ + public function detail($active_time_id, $sharp_goods_id) + { + // 获取秒杀活动商品详情 + $service = new ActiveService; + $data = $service->getyActiveGoodsDetail($active_time_id, $sharp_goods_id); + if ($data === false) { + return $this->renderError($service->getError()); + } + return $this->renderSuccess($data); + } + + /** + * 生成商品海报 + * @param $active_time_id + * @param $sharp_goods_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function poster($active_time_id, $sharp_goods_id) + { + // 获取秒杀活动商品详情 + $service = new ActiveService; + $data = $service->getyActiveGoodsDetail($active_time_id, $sharp_goods_id); + if ($data === false) { + return $this->renderError($service->getError()); + } + // 生成商品海报图 + $Qrcode = new GoodsPoster($data['active'], $data['goods'], $this->getUser(false)); + return $this->renderSuccess([ + 'qrcode' => $Qrcode->getImage(), + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/sharp/Index.php b/source/application/api/controller/sharp/Index.php new file mode 100644 index 0000000..5b1bda8 --- /dev/null +++ b/source/application/api/controller/sharp/Index.php @@ -0,0 +1,32 @@ +getHallIndex(); + if (empty($data['tabbar'])) { + return $this->renderError('很抱歉,暂无秒杀活动'); + } + return $this->renderSuccess($data); + } +} \ No newline at end of file diff --git a/source/application/api/controller/sharp/Order.php b/source/application/api/controller/sharp/Order.php new file mode 100644 index 0000000..41eeb88 --- /dev/null +++ b/source/application/api/controller/sharp/Order.php @@ -0,0 +1,114 @@ +user = $this->getUser(); + } + + /** + * 秒杀订单结算 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function checkout() + { + // 实例化结算台服务 + $Checkout = new CheckoutModel; + // 订单结算api参数 + $params = $Checkout->setParam($this->getParam([ + 'active_time_id' => 0, + 'sharp_goods_id' => 0, + 'goods_sku_id' => '', + 'goods_num' => 0, + ])); + // 设置并发锁 + $lockId = "sharp_order_{$params['active_time_id']}_{$params['sharp_goods_id']}"; + Lock::lockUp($lockId); + // 获取秒杀商品信息 + $service = new ActiveService; + $goodsList = $service->getCheckoutGoodsList( + $params['active_time_id'], + $params['sharp_goods_id'], + $params['goods_sku_id'], + $params['goods_num'] + ); + if ($goodsList === false) { + Lock::unLock($lockId); + return $this->renderError($service->getError()); + } + // 设置订单来源 + $Checkout->setOrderSource([ + 'source' => OrderSourceEnum::SHARP, + 'source_id' => $params['active_time_id'], + ]) + // 秒杀商品不参与 等级折扣和优惠券折扣 + ->setCheckoutRule([ + 'is_user_grade' => false, + 'is_coupon' => false, + 'is_use_points' => false, + 'is_dealer' => SettingModel::getIsDealer(), + ]); + // 获取订单结算信息 + $orderInfo = $Checkout->onCheckout($this->user, $goodsList); + if ($this->request->isGet()) { + Lock::unLock($lockId); + return $this->renderSuccess($orderInfo); + } + // submit:订单结算提交 + if ($Checkout->hasError()) { + Lock::unLock($lockId); + return $this->renderError($Checkout->getError()); + } + // 创建订单 + if (!$Checkout->createOrder($orderInfo)) { + Lock::unLock($lockId); + return $this->renderError($Checkout->getError() ?: '订单创建失败'); + } + Lock::unLock($lockId); + // 构建微信支付请求 + $payment = $Checkout->onOrderPayment(); + // 支付状态提醒 + return $this->renderSuccess([ + 'order_id' => $Checkout->model['order_id'], // 订单id + 'pay_type' => $params['pay_type'], // 支付方式 + 'payment' => $payment // 微信支付参数 + ], ['success' => '支付成功', 'error' => '订单未支付']); + } + + /** + * 订单结算提交的参数 + * @param array $define + * @return array + */ + private function getParam($define = []) + { + return array_merge($define, $this->request->param()); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/shop/Order.php b/source/application/api/controller/shop/Order.php new file mode 100644 index 0000000..f5feb08 --- /dev/null +++ b/source/application/api/controller/shop/Order.php @@ -0,0 +1,80 @@ +user = $this->getUser(); // 用户信息 + } + + /** + * 核销订单详情 + * @param $order_id + * @param int $order_type + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function detail($order_id, $order_type = OrderTypeEnum::MASTER) + { + // 订单详情 + $model = OrderService::getOrderDetail($order_id, $order_type); + // 验证是否为该门店的核销员 + $clerkModel = ClerkModel::detail(['user_id' => $this->user['user_id']]); + return $this->renderSuccess([ + 'order' => $model, // 订单详情 + 'clerkModel' => $clerkModel, + 'setting' => [ + // 积分名称 + 'points_name' => SettingModel::getPointsName(), + ], + ]); + } + + /** + * 确认核销 + * @param $order_id + * @param int $order_type + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function extract($order_id, $order_type = OrderTypeEnum::MASTER) + { + // 订单详情 + $order = OrderService::getOrderDetail($order_id, $order_type); + // 验证是否为该门店的核销员 + $ClerkModel = ClerkModel::detail(['user_id' => $this->user['user_id']]); + if (!$ClerkModel->checkUser($order['extract_shop_id'])) { + return $this->renderError($ClerkModel->getError()); + } + // 确认核销 + if ($order->verificationOrder($ClerkModel['clerk_id'])) { + return $this->renderSuccess([], '订单核销成功'); + } + return $this->renderError($order->getError() ?: '核销失败'); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/Comment.php b/source/application/api/controller/user/Comment.php new file mode 100644 index 0000000..9cb0165 --- /dev/null +++ b/source/application/api/controller/user/Comment.php @@ -0,0 +1,53 @@ +getUser(); + // 订单信息 + $order = OrderModel::getUserOrderDetail($order_id, $user['user_id']); + // 验证订单是否已完成 + $model = new CommentModel; + if (!$model->checkOrderAllowComment($order)) { + return $this->renderError($model->getError()); + } + // 待评价商品列表 + /* @var \think\Collection $goodsList */ + $goodsList = OrderGoodsModel::getNotCommentGoodsList($order_id); + if ($goodsList->isEmpty()) { + return $this->renderError('该订单没有可评价的商品'); + } + // 提交商品评价 + if ($this->request->isPost()) { + $formData = $this->request->post('formData', '', null); + if ($model->addForOrder($order, $goodsList, $formData)) { + return $this->renderSuccess([], '评价发表成功'); + } + return $this->renderError($model->getError() ?: '评价发表失败'); + } + return $this->renderSuccess(compact('goodsList')); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/Coupon.php b/source/application/api/controller/user/Coupon.php new file mode 100644 index 0000000..92cb8a5 --- /dev/null +++ b/source/application/api/controller/user/Coupon.php @@ -0,0 +1,74 @@ +model = new UserCouponModel; + $this->user = $this->getUser(); + } + + /** + * 优惠券列表 + * @param string $data_type + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function lists($data_type = 'all') + { + $is_use = false; + $is_expire = false; + switch ($data_type) { + case 'not_use': + $is_use = false; + break; + case 'is_use': + $is_use = true; + break; + case 'is_expire': + $is_expire = true; + break; + } + $list = $this->model->getList($this->user['user_id'], $is_use, $is_expire); + return $this->renderSuccess(compact('list')); + } + + /** + * 领取优惠券 + * @param $coupon_id + * @return array + * @throws \think\exception\DbException + */ + public function receive($coupon_id) + { + if ($this->model->receive($this->user, $coupon_id)) { + return $this->renderSuccess([], '领取成功'); + } + return $this->renderError($this->model->getError() ?: '添加失败'); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/Dealer.php b/source/application/api/controller/user/Dealer.php new file mode 100644 index 0000000..7f48e45 --- /dev/null +++ b/source/application/api/controller/user/Dealer.php @@ -0,0 +1,115 @@ +user = $this->getUser(); + // 分销商用户信息 + $this->dealer = DealerUserModel::detail($this->user['user_id']); + // 分销商设置 + $this->setting = Setting::getAll(); + } + + /** + * 分销商中心 + * @return array + */ + public function center() + { + return $this->renderSuccess([ + // 当前是否为分销商 + 'is_dealer' => $this->isDealerUser(), + // 当前用户信息 + 'user' => $this->user, + // 分销商用户信息 + 'dealer' => $this->dealer, + // 背景图 + 'background' => $this->setting['background']['values']['index'], + // 页面文字 + 'words' => $this->setting['words']['values'], + ]); + } + + /** + * 分销商申请状态 + * @param null $referee_id + * @return array + * @throws \think\exception\DbException + */ + public function apply($referee_id = null) + { + // 推荐人昵称 + $referee_name = '平台'; + if ($referee_id > 0 && ($referee = DealerUserModel::detail($referee_id))) { + $referee_name = $referee['user']['nickName']; + } + return $this->renderSuccess([ + // 当前是否为分销商 + 'is_dealer' => $this->isDealerUser(), + // 当前是否在申请中 + 'is_applying' => DealerApplyModel::isApplying($this->user['user_id']), + // 推荐人昵称 + 'referee_name' => $referee_name, + // 背景图 + 'background' => $this->setting['background']['values']['apply'], + // 页面文字 + 'words' => $this->setting['words']['values'], + // 申请协议 + 'license' => $this->setting['license']['values']['license'], + ]); + } + + /** + * 分销商提现信息 + * @return array + */ + public function withdraw() + { + return $this->renderSuccess([ + // 分销商用户信息 + 'dealer' => $this->dealer, + // 结算设置 + 'settlement' => $this->setting['settlement']['values'], + // 背景图 + 'background' => $this->setting['background']['values']['withdraw_apply'], + // 页面文字 + 'words' => $this->setting['words']['values'], + ]); + } + + /** + * 当前用户是否为分销商 + * @return bool + */ + private function isDealerUser() + { + return !!$this->dealer && !$this->dealer['is_delete']; + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/Index.php b/source/application/api/controller/user/Index.php new file mode 100644 index 0000000..ed15f18 --- /dev/null +++ b/source/application/api/controller/user/Index.php @@ -0,0 +1,44 @@ +getUser(false); + // 订单总数 + $model = new OrderModel; + return $this->renderSuccess([ + 'userInfo' => $user, + 'orderCount' => [ + 'payment' => $model->getCount($user, 'payment'), + 'received' => $model->getCount($user, 'received'), + 'comment' => $model->getCount($user, 'comment'), + ], + 'setting' => [ + 'points_name' => SettingModel::getPointsName(), + ], + 'menus' => (new UserModel)->getMenus() // 个人中心菜单列表 + ]); + } + +} diff --git a/source/application/api/controller/user/Order.php b/source/application/api/controller/user/Order.php new file mode 100644 index 0000000..9ea27d4 --- /dev/null +++ b/source/application/api/controller/user/Order.php @@ -0,0 +1,179 @@ +user = $this->getUser(); // 用户信息 + } + + /** + * 我的订单列表 + * @param $dataType + * @return array + * @throws \think\exception\DbException + */ + public function lists($dataType) + { + $model = new OrderModel; + $list = $model->getList($this->user['user_id'], $dataType); + return $this->renderSuccess(compact('list')); + } + + /** + * 订单详情信息 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function detail($order_id) + { + // 订单详情 + $model = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + // 该订单是否允许申请售后 + $model['isAllowRefund'] = $model->isAllowRefund(); + return $this->renderSuccess([ + 'order' => $model, // 订单详情 + 'setting' => [ + // 积分名称 + 'points_name' => SettingModel::getPointsName(), + ], + ]); + } + + /** + * 获取物流信息 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function express($order_id) + { + // 订单信息 + $order = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + if (!$order['express_no']) { + return $this->renderError('没有物流信息'); + } + // 获取物流信息 + /* @var \app\store\model\Express $model */ + $model = $order['express']; + $express = $model->dynamic($model['express_name'], $model['express_code'], $order['express_no']); + if ($express === false) { + return $this->renderError($model->getError()); + } + return $this->renderSuccess(compact('express')); + } + + /** + * 取消订单 + * @param $order_id + * @return array + * @throws \Exception + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function cancel($order_id) + { + $model = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + if ($model->cancel($this->user)) { + return $this->renderSuccess($model->getError() ?: '订单取消成功'); + } + return $this->renderError($model->getError() ?: '订单取消失败'); + } + + /** + * 确认收货 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function receipt($order_id) + { + $model = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + if ($model->receipt()) { + return $this->renderSuccess(); + } + return $this->renderError($model->getError()); + } + + /** + * 立即支付 + * @param int $order_id 订单id + * @param int $payType 支付方式 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function pay($order_id, $payType = PayTypeEnum::WECHAT) + { + // 获取订单详情 + $model = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + // 订单支付事件 + if (!$model->onPay($payType)) { + return $this->renderError($model->getError() ?: '订单支付失败'); + } + // 构建微信支付请求 + $payment = $model->onOrderPayment($this->user, $model, $payType); + // 支付状态提醒 + return $this->renderSuccess([ + 'order_id' => $model['order_id'], // 订单id + 'pay_type' => $payType, // 支付方式 + 'payment' => $payment // 微信支付参数 + ], ['success' => '支付成功', 'error' => '订单未支付']); + } + + /** + * 获取订单核销二维码 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function extractQrcode($order_id) + { + // 订单详情 + $order = OrderModel::getUserOrderDetail($order_id, $this->user['user_id']); + // 判断是否为待核销订单 + if (!$order->checkExtractOrder($order)) { + return $this->renderError($order->getError()); + } + $Qrcode = new ExtractQRcode( + $this->wxapp_id, + $this->user, + $order_id, + OrderTypeEnum::MASTER + ); + return $this->renderSuccess([ + 'qrcode' => $Qrcode->getImage(), + ]); + } + +} diff --git a/source/application/api/controller/user/Refund.php b/source/application/api/controller/user/Refund.php new file mode 100644 index 0000000..563d58b --- /dev/null +++ b/source/application/api/controller/user/Refund.php @@ -0,0 +1,109 @@ +user = $this->getUser(); // 用户信息 + } + + /** + * 用户售后单列表 + * @param int $state + * @return array + * @throws \think\exception\DbException + */ + public function lists($state = -1) + { + $model = new OrderRefundModel; + $list = $model->getList($this->user['user_id'], (int)$state); + return $this->renderSuccess(compact('list')); + } + + /** + * 申请售后 + * @param $order_goods_id + * @return array + * @throws \think\exception\DbException + * @throws \Exception + */ + public function apply($order_goods_id) + { + // 订单商品详情 + $goods = OrderGoodsModel::detail($order_goods_id); + if (isset($goods['refund']) && !empty($goods['refund'])) { + return $this->renderError('当前商品已申请售后'); + } + if (!$this->request->isPost()) { + return $this->renderSuccess(['detail' => $goods]); + } + // 新增售后单记录 + $model = new OrderRefundModel; + if ($model->apply($this->user, $goods, $this->request->post())) { + return $this->renderSuccess([], '提交成功'); + } + return $this->renderError($model->getError() ?: '提交失败'); + } + + /** + * 售后单详情 + * @param $order_refund_id + * @return array + * @throws \think\exception\DbException + */ + public function detail($order_refund_id) + { + // 售后单详情 + $detail = OrderRefundModel::detail([ + 'user_id' => $this->user['user_id'], + 'order_refund_id' => $order_refund_id + ]); + if (empty($detail)) { + return $this->renderError('售后单不存在'); + } + // 物流公司列表 + $expressList = ExpressModel::getAll(); + return $this->renderSuccess(compact('detail', 'expressList')); + } + + /** + * 用户发货 + * @param $order_refund_id + * @return array + * @throws \think\exception\DbException + */ + public function delivery($order_refund_id) + { + // 售后单详情 + $model = OrderRefundModel::detail([ + 'user_id' => $this->user['user_id'], + 'order_refund_id' => $order_refund_id + ]); + if ($model->delivery($this->postData())) { + return $this->renderSuccess([], '操作成功'); + } + return $this->renderError($model->getError() ?: '提交失败'); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/Wallet.php b/source/application/api/controller/user/Wallet.php new file mode 100644 index 0000000..99c9c50 --- /dev/null +++ b/source/application/api/controller/user/Wallet.php @@ -0,0 +1,44 @@ +user = $this->getUser(); // 用户信息 + } + + /** + * 我的钱包信息 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function index() + { + // 当前用户信息 + $user = $this->getUser(); + // 获取充值设置 + $setting = SettingModel::getItem('recharge'); + return $this->renderSuccess([ + 'userInfo' => $user, + 'setting' => [ + 'is_entrance' => (bool)$setting['is_entrance'] + ] + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/dealer/Apply.php b/source/application/api/controller/user/dealer/Apply.php new file mode 100644 index 0000000..485acd2 --- /dev/null +++ b/source/application/api/controller/user/dealer/Apply.php @@ -0,0 +1,46 @@ +user = $this->getUser(); // 用户信息 + } + + /** + * 提交分销商申请 + * @param string $name + * @param string $mobile + * @return array + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function submit($name = '', $mobile = '') + { + $model = new DealerApplyModel; + if ($model->submit($this->user, $name, $mobile)) { + return $this->renderSuccess(); + } + return $this->renderError($model->getError() ?: '提交失败'); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/dealer/Order.php b/source/application/api/controller/user/dealer/Order.php new file mode 100644 index 0000000..f3c6421 --- /dev/null +++ b/source/application/api/controller/user/dealer/Order.php @@ -0,0 +1,56 @@ +user = $this->getUser(); + // 分销商用户信息 + $this->dealer = DealerUserModel::detail($this->user['user_id']); + // 分销商设置 + $this->setting = Setting::getAll(); + } + + /** + * 分销商订单列表 + * @param int $settled + * @return array + * @throws \think\exception\DbException + */ + public function lists($settled = -1) + { + $model = new OrderModel; + return $this->renderSuccess([ + // 提现明细列表 + 'list' => $model->getList($this->user['user_id'], (int)$settled), + // 页面文字 + 'words' => $this->setting['words']['values'], + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/dealer/Qrcode.php b/source/application/api/controller/user/dealer/Qrcode.php new file mode 100644 index 0000000..bbf2693 --- /dev/null +++ b/source/application/api/controller/user/dealer/Qrcode.php @@ -0,0 +1,57 @@ +user = $this->getUser(); + // 分销商用户信息 + $this->dealer = DealerUserModel::detail($this->user['user_id']); + // 分销商设置 + $this->setting = Setting::getAll(); + } + + /** + * 获取推广二维码 + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function poster() + { + $Qrcode = new Poster($this->dealer); + return $this->renderSuccess([ + // 二维码图片地址 + 'qrcode' => $Qrcode->getImage(), + // 页面文字 + 'words' => $this->setting['words']['values'], + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/dealer/Team.php b/source/application/api/controller/user/dealer/Team.php new file mode 100644 index 0000000..5717d89 --- /dev/null +++ b/source/application/api/controller/user/dealer/Team.php @@ -0,0 +1,60 @@ +user = $this->getUser(); + // 分销商用户信息 + $this->dealer = DealerUserModel::detail($this->user['user_id']); + // 分销商设置 + $this->setting = Setting::getAll(); + } + + /** + * 我的团队列表 + * @param int $level + * @return array + * @throws \think\exception\DbException + */ + public function lists($level = -1) + { + $model = new RefereeModel; + return $this->renderSuccess([ + // 分销商用户信息 + 'dealer' => $this->dealer, + // 我的团队列表 + 'list' => $model->getList($this->user['user_id'], (int)$level), + // 基础设置 + 'setting' => $this->setting['basic']['values'], + // 页面文字 + 'words' => $this->setting['words']['values'], + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/user/dealer/Withdraw.php b/source/application/api/controller/user/dealer/Withdraw.php new file mode 100644 index 0000000..99e9011 --- /dev/null +++ b/source/application/api/controller/user/dealer/Withdraw.php @@ -0,0 +1,72 @@ +user = $this->getUser(); + // 分销商用户信息 + $this->dealer = DealerUserModel::detail($this->user['user_id']); + // 分销商设置 + $this->setting = Setting::getAll(); + } + + /** + * 提交提现申请 + * @param $data + * @return array + * @throws \app\common\exception\BaseException + */ + public function submit($data) + { + $formData = json_decode(htmlspecialchars_decode($data), true); + $model = new WithdrawModel; + if ($model->submit($this->dealer, $formData)) { + return $this->renderSuccess([], '申请提现成功'); + } + return $this->renderError($model->getError() ?: '提交失败'); + } + + /** + * 分销商提现明细 + * @param int $status + * @return array + * @throws \think\exception\DbException + */ + public function lists($status = -1) + { + $model = new WithdrawModel; + return $this->renderSuccess([ + // 提现明细列表 + 'list' => $model->getList($this->user['user_id'], (int)$status), + // 页面文字 + 'words' => $this->setting['words']['values'], + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/controller/wxapp/Formid.php b/source/application/api/controller/wxapp/Formid.php new file mode 100644 index 0000000..a6471d2 --- /dev/null +++ b/source/application/api/controller/wxapp/Formid.php @@ -0,0 +1,33 @@ +getUser(false)) { + return $this->renderSuccess(); + } + if (FormidModel::add($user['user_id'], $formId)) { + return $this->renderSuccess(); + } + return $this->renderError(); + } + +} \ No newline at end of file diff --git a/source/application/api/model/Article.php b/source/application/api/model/Article.php new file mode 100644 index 0000000..3cfeb87 --- /dev/null +++ b/source/application/api/model/Article.php @@ -0,0 +1,88 @@ + '文章不存在']); + } + // 累积阅读数 + $model->setInc('actual_views', 1); + return $model; + } + + /** + * 获取文章列表 + * @param int $category_id + * @param int $limit + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList($category_id = 0, $limit = 15) + { + $category_id > 0 && $this->where('category_id', '=', $category_id); + return $this->field(['article_content'], true) + ->with(['image', 'category']) + ->where('article_status', '=', 1) + ->where('is_delete', '=', 0) + ->order(['article_sort' => 'asc', 'create_time' => 'desc']) + ->paginate($limit, false, [ + 'query' => \request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/model/Cart.php b/source/application/api/model/Cart.php new file mode 100644 index 0000000..c6b8563 --- /dev/null +++ b/source/application/api/model/Cart.php @@ -0,0 +1,292 @@ +user = $user; + $this->user_id = $this->user['user_id']; + $this->wxapp_id = $this->user['wxapp_id']; + $this->cart = Cache::get('cart_' . $this->user_id) ?: []; + } + + /** + * 购物车列表 (含商品信息) + * @return array + * @param string $cartIds 请求参数 + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getList($cartIds) + { + // 获取购物车商品列表 + return $this->getOrderGoodsList($cartIds); + } + + /** + * 获取购物车列表 + * @param string|null $cartIds 购物车索引集 (为null时则获取全部) + * @return array + */ + public function getCartList($cartIds = null) + { + if (empty($cartIds)) return $this->cart; + $cartList = []; + $indexArr = (strpos($cartIds, ',') !== false) ? explode(',', $cartIds) : [$cartIds]; + foreach ($indexArr as $index) { + isset($this->cart[$index]) && $cartList[$index] = $this->cart[$index]; + } + return $cartList; + } + + /** + * 获取购物车中的商品列表 + * @param $cartIds + * @return array|bool + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getOrderGoodsList($cartIds) + { + // 购物车商品列表 + $goodsList = []; + // 获取购物车列表 + $cartList = $this->getCartList($cartIds); + if (empty($cartList)) { + $this->setError('当前购物车没有商品'); + return $goodsList; + } + // 购物车中所有商品id集 + $goodsIds = array_unique(helper::getArrayColumn($cartList, 'goods_id')); + // 获取并格式化商品数据 + $sourceData = (new GoodsModel)->getListByIds($goodsIds); + $sourceData = helper::arrayColumn2Key($sourceData, 'goods_id'); + // 格式化购物车数据列表 + foreach ($cartList as $key => $item) { + // 判断商品不存在则自动删除 + if (!isset($sourceData[$item['goods_id']])) { + $this->delete($key); + continue; + } + /* @var GoodsModel $goods 商品信息 */ + $goods = clone $sourceData[$item['goods_id']]; + // 判断商品是否已删除 + if ($goods['is_delete']) { + $this->delete($key); + continue; + } + // 商品sku信息 + $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $item['goods_sku_id']); + $goods['goods_sku_id'] = $item['goods_sku_id']; + $goods['spec_sku_id'] = $goods['goods_sku']['spec_sku_id']; + // 商品sku不存在则自动删除 + if (empty($goods['goods_sku'])) { + $this->delete($key); + continue; + } + // 商品单价 + $goods['goods_price'] = $goods['goods_sku']['goods_price']; + // 购买数量 + $goods['total_num'] = $item['goods_num']; + // 商品总价 + $goods['total_price'] = bcmul($goods['goods_price'], $item['goods_num'], 2); + $goodsList[] = $goods->hidden(['category', 'content', 'image', 'sku']); + } + return $goodsList; + } + + /** + * 加入购物车 + * @param int $goodsId 商品id + * @param int $goodsNum 加入购物车的数量 + * @param string $goodsSkuId 商品sku索引 + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function add($goodsId, $goodsNum, $goodsSkuId) + { + // 购物车商品索引 + $index = "{$goodsId}_{$goodsSkuId}"; + // 加入购物车后的商品数量 + $cartGoodsNum = $goodsNum + (isset($this->cart[$index]) ? $this->cart[$index]['goods_num'] : 0); + // 获取商品信息 + $goods = GoodsModel::detail($goodsId); + // 验证商品能否加入 + if (!$this->checkGoods($goods, $goodsSkuId, $cartGoodsNum)) { + return false; + } + // 将商品同步到好物圈 + if (!$this->isExistGoodsId($goodsId)) { + (new WowService($this->wxapp_id))->add($this->user, [$goods]); + } + // 记录到购物车列表 + $this->cart[$index] = [ + 'goods_id' => $goodsId, + 'goods_num' => $cartGoodsNum, + 'goods_sku_id' => $goodsSkuId, + 'create_time' => time() + ]; + return true; + } + + /** + * 验证购物车中是否存在某商品 + * @param $goodsId + * @return bool + */ + private function isExistGoodsId($goodsId) + { + foreach ($this->cart as $item) { + if ($item['goods_id'] == $goodsId) return true; + } + return false; + } + + /** + * 验证商品是否可以购买 + * @param GoodsModel $goods 商品信息 + * @param string $goodsSkuId 商品sku索引 + * @param $cartGoodsNum + * @return bool + */ + private function checkGoods($goods, $goodsSkuId, $cartGoodsNum) + { + // 判断商品是否下架 + if (!$goods || $goods['is_delete'] || $goods['goods_status']['value'] != 10) { + $this->setError('很抱歉,商品信息不存在或已下架'); + return false; + } + // 商品sku信息 + $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $goodsSkuId); + // 判断商品库存 + if ($cartGoodsNum > $goods['goods_sku']['stock_num']) { + $this->setError('很抱歉,商品库存不足'); + return false; + } + return true; + } + + /** + * 减少购物车中某商品数量 + * @param int $goodsId + * @param string $goodsSkuId + */ + public function sub($goodsId, $goodsSkuId) + { + $index = "{$goodsId}_{$goodsSkuId}"; + $this->cart[$index]['goods_num'] > 1 && $this->cart[$index]['goods_num']--; + } + + /** + * 删除购物车中指定商品 + * @param string $cartIds (支持字符串ID集) + */ + public function delete($cartIds) + { + $indexArr = strpos($cartIds, ',') !== false ? explode(',', $cartIds) : [$cartIds]; + foreach ($indexArr as $index) { + if (isset($this->cart[$index])) unset($this->cart[$index]); + } + } + + /** + * 获取当前用户购物车商品总数量(含件数) + * @return int + */ + public function getTotalNum() + { + return helper::getArrayColumnSum($this->cart, 'goods_num'); + } + + /** + * 获取当前用户购物车商品总数量(不含件数) + * @return int + */ + public function getGoodsNum() + { + return count($this->cart); + } + + /** + * 析构方法 + * 将cart数据保存到缓存文件 + */ + public function __destruct() + { + $this->clear !== true && Cache::set('cart_' . $this->user_id, $this->cart, 86400 * 15); + } + + /** + * 清空当前用户购物车 + * @param null $cartIds + */ + public function clearAll($cartIds = null) + { + if (empty($cartIds)) { + $this->clear = true; + Cache::rm('cart_' . $this->user_id); + } else { + $this->delete($cartIds); + } + } + + /** + * 设置错误信息 + * @param $error + */ + private function setError($error) + { + empty($this->error) && $this->error = $error; + } + + /** + * 获取错误信息 + * @return string + */ + public function getError() + { + return $this->error; + } + +} diff --git a/source/application/api/model/Category.php b/source/application/api/model/Category.php new file mode 100644 index 0000000..7130a97 --- /dev/null +++ b/source/application/api/model/Category.php @@ -0,0 +1,28 @@ +belongsTo('User')->field(['user_id', 'nickName', 'avatarUrl']); + } + + /** + * 获取指定商品评价列表 + * @param $goods_id + * @param int $scoreType + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getGoodsCommentList($goods_id, $scoreType = -1) + { + // 筛选条件 + $filter = [ + 'goods_id' => $goods_id, + 'is_delete' => 0, + 'status' => 1, + ]; + // 评分 + $scoreType > 0 && $filter['score'] = $scoreType; + return $this->with(['user', 'OrderGoods', 'image.file']) + ->where($filter) + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + + /** + * 获取指定评分总数 + * @param $goods_id + * @return array|false|\PDOStatement|string|\think\Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getTotal($goods_id) + { + return $this->field([ + 'count(comment_id) AS `all`', + 'count(score = 10 OR NULL) AS `praise`', + 'count(score = 20 OR NULL) AS `review`', + 'count(score = 30 OR NULL) AS `negative`', + ])->where([ + 'goods_id' => $goods_id, + 'is_delete' => 0, + 'status' => 1 + ])->find(); + } + + /** + * 验证订单是否允许评价 + * @param Order $order + * @return boolean + */ + public function checkOrderAllowComment($order) + { + // 验证订单是否已完成 + if ($order['order_status']['value'] != 30) { + $this->error = '该订单未完成,无法评价'; + return false; + } + // 验证订单是否已评价 + if ($order['is_comment'] == 1) { + $this->error = '该订单已完成评价'; + return false; + } + return true; + } + + /** + * 根据已完成订单商品 添加评价 + * @param Order $order + * @param \think\Collection|OrderGoods $goodsList + * @param $formJsonData + * @return boolean + * @throws \Exception + */ + public function addForOrder($order, $goodsList, $formJsonData) + { + // 生成 formData + $formData = $this->formatFormData($formJsonData); + // 生成评价数据 + $data = $this->createCommentData($order['user_id'], $order['order_id'], $goodsList, $formData); + if (empty($data)) { + $this->error = '没有输入评价内容'; + return false; + } + return $this->transaction(function () use ($order, $goodsList, $formData, $data) { + // 记录评价内容 + $result = $this->isUpdate(false)->saveAll($data); + // 记录评价图片 + $this->saveAllImages($result, $formData); + // 更新订单评价状态 + $isComment = count($goodsList) === count($data); + $this->updateOrderIsComment($order, $isComment, $result); + return true; + }); + } + + /** + * 更新订单评价状态 + * @param Order $order + * @param $isComment + * @param $commentList + * @return array|false + * @throws \Exception + */ + private function updateOrderIsComment($order, $isComment, $commentList) + { + // 更新订单商品 + $orderGoodsData = []; + foreach ($commentList as $comment) { + $orderGoodsData[] = [ + 'order_goods_id' => $comment['order_goods_id'], + 'is_comment' => 1 + ]; + } + // 更新订单 + $isComment && $order->save(['is_comment' => 1]); + return (new OrderGoods)->saveAll($orderGoodsData); + } + + /** + * 生成评价数据 + * @param $user_id + * @param $order_id + * @param $goodsList + * @param $formData + * @return array + * @throws BaseException + */ + private function createCommentData($user_id, $order_id, $goodsList, $formData) + { + $data = []; + foreach ($goodsList as $goods) { + if (!isset($formData[$goods['order_goods_id']])) { + throw new BaseException(['msg' => '提交的数据不合法']); + } + $item = $formData[$goods['order_goods_id']]; + !empty($item['content']) && $data[$goods['order_goods_id']] = [ + 'score' => $item['score'], + 'content' => $item['content'], + 'is_picture' => !empty($item['uploaded']), + 'sort' => 100, + 'status' => 1, + 'user_id' => $user_id, + 'order_id' => $order_id, + 'goods_id' => $item['goods_id'], + 'order_goods_id' => $item['order_goods_id'], + 'wxapp_id' => self::$wxapp_id + ]; + } + return $data; + } + + /** + * 格式化 formData + * @param string $formJsonData + * @return array + */ + private function formatFormData($formJsonData) + { + return array_column(json_decode($formJsonData, true), null, 'order_goods_id'); + } + + /** + * 记录评价图片 + * @param $commentList + * @param $formData + * @return bool + * @throws \Exception + */ + private function saveAllImages($commentList, $formData) + { + // 生成评价图片数据 + $imageData = []; + foreach ($commentList as $comment) { + $item = $formData[$comment['order_goods_id']]; + foreach ($item['uploaded'] as $imageId) { + $imageData[] = [ + 'comment_id' => $comment['comment_id'], + 'image_id' => $imageId, + 'wxapp_id' => self::$wxapp_id + ]; + } + } + $model = new CommentImage; + return !empty($imageData) && $model->saveAll($imageData); + } + +} diff --git a/source/application/api/model/CommentImage.php b/source/application/api/model/CommentImage.php new file mode 100644 index 0000000..cdb00ac --- /dev/null +++ b/source/application/api/model/CommentImage.php @@ -0,0 +1,23 @@ +where('is_delete', '=', 0); + // 只显示可领取(未过期,未发完)的优惠券 + if ($only_receive) { + $this->where(' IF ( `total_num` > - 1, `receive_num` < `total_num`, 1 = 1 )') + ->where('IF ( `expire_type` = 20, (`end_time` + 86400) >= ' . time() . ', 1 = 1 )'); + } + + // 优惠券列表 + $couponList = $this->order(['sort' => 'asc', 'create_time' => 'desc'])->limit($limit)->select(); + + // 获取用户已领取的优惠券 + if ($user !== false) { + $UserCouponModel = new UserCoupon; + $userCouponIds = $UserCouponModel->getUserCouponIds($user['user_id']); + foreach ($couponList as $key => $item) { + $couponList[$key]['is_receive'] = in_array($item['coupon_id'], $userCouponIds); + } + } + return $couponList; + } + + /** + * 验证优惠券是否可领取 + * @return bool + */ + public function checkReceive() + { + if ($this['total_num'] > -1 && $this['receive_num'] >= $this['total_num']) { + $this->error = '优惠券已发完'; + return false; + } + if ($this['expire_type'] == 20 && ($this->getData('end_time') + 86400) < time()) { + $this->error = '优惠券已过期'; + return false; + } + return true; + } + + /** + * 累计已领取数量 + * @return int|true + * @throws \think\Exception + */ + public function setIncReceiveNum() + { + return $this->setInc('receive_num'); + } + +} diff --git a/source/application/api/model/Delivery.php b/source/application/api/model/Delivery.php new file mode 100644 index 0000000..e2baec7 --- /dev/null +++ b/source/application/api/model/Delivery.php @@ -0,0 +1,24 @@ +isEmpty() && $data->hidden(['category', 'content', 'image', 'sku']); + // 整理列表数据并返回 + return $this->setGoodsListDataFromApi($data, true, ['userInfo' => $userInfo]); + } + + /** + * 商品详情 + * @param $goodsId + * @return GoodsModel + */ + public static function detail($goodsId) + { + // 商品详情 + $detail = parent::detail($goodsId); + // 多规格商品sku信息 + $detail['goods_multi_spec'] = GoodsService::getSpecData($detail); + return $detail; + } + + /** + * 获取商品详情页面 + * @param int $goodsId 商品id + * @param array|bool $userInfo 用户信息 + * @return array|bool|false|mixed|\PDOStatement|string|\think\Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getDetails($goodsId, $userInfo = false) + { + // 获取商品详情 + $detail = $this->with([ + 'category', + 'image' => ['file'], + 'sku' => ['image'], + 'spec_rel' => ['spec'], + 'delivery' => ['rule'], + 'commentData' => function ($query) { + $query->with('user')->where(['is_delete' => 0, 'status' => 1])->limit(2); + } + ])->withCount(['commentData' => function ($query) { + $query->where(['is_delete' => 0, 'status' => 1]); + }]) + ->where('goods_id', '=', $goodsId) + ->find(); + // 判断商品的状态 + if (empty($detail) || $detail['is_delete'] || $detail['goods_status']['value'] != 10) { + $this->error = '很抱歉,商品信息不存在或已下架'; + return false; + } + // 设置商品展示的数据 + $detail = $this->setGoodsListDataFromApi($detail, false, ['userInfo' => $userInfo]); + // 多规格商品sku信息 + $detail['goods_multi_spec'] = GoodsService::getSpecData($detail); + return $detail; + } + + /** + * 根据商品id集获取商品列表 + * @param $goodsIds + * @param bool $userInfo + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIdsFromApi($goodsIds, $userInfo = false) + { + // 获取商品列表 + $data = parent::getListByIds($goodsIds, 10); + // 整理列表数据并返回 + return $this->setGoodsListDataFromApi($data, true, ['userInfo' => $userInfo]); + } + + + /** + * 设置商品展示的数据 api模块 + * @param $data + * @param bool $isMultiple + * @param array $param + * @return mixed + */ + private function setGoodsListDataFromApi(&$data, $isMultiple, $param) + { + return parent::setGoodsListData($data, $isMultiple, function ($goods) use ($param) { + // 计算并设置商品会员价 + $this->setGoodsGradeMoney($param['userInfo'], $goods); + }); + } + + /** + * 设置商品的会员价 + * @param $user + * @param $goods + */ + private function setGoodsGradeMoney($user, &$goods) + { + // 会员等级状态 + $gradeStatus = (!empty($user) && $user['grade_id'] > 0 && !empty($user['grade'])) + && (!$user['grade']['is_delete'] && $user['grade']['status']); + // 判断商品是否参与会员折扣 + if (!$gradeStatus || !$goods['is_enable_grade']) { + $goods['is_user_grade'] = false; + return; + } + // 商品单独设置了会员折扣 + if ($goods['is_alone_grade'] && isset($goods['alone_grade_equity'][$user['grade_id']])) { + // 折扣比例 + $discountRatio = helper::bcdiv($goods['alone_grade_equity'][$user['grade_id']], 10); + } else { + // 折扣比例 + $discountRatio = helper::bcdiv($user['grade']['equity']['discount'], 10); + } + if ($discountRatio > 0) { + // 标记参与会员折扣 + $goods['is_user_grade'] = true; + // 会员折扣价 + foreach ($goods['sku'] as &$skuItem) { + $skuItem['goods_price'] = helper::number2(helper::bcmul($skuItem['goods_price'], $discountRatio), true); + } + } + } + +} diff --git a/source/application/api/model/GoodsImage.php b/source/application/api/model/GoodsImage.php new file mode 100644 index 0000000..7c4a7c6 --- /dev/null +++ b/source/application/api/model/GoodsImage.php @@ -0,0 +1,23 @@ + $orderNo, 'pay_status' => 10, 'is_delete' => 0], ['goods', 'user']); + } + + /** + * 订单支付事件 + * @param int $payType + * @return bool + * @throws \think\exception\DbException + */ + public function onPay($payType = PayTypeEnum::WECHAT) + { + // 判断订单状态 + $orderSource = OrderSourceFactory::getFactory($this['order_source']); + if (!$orderSource->checkOrderStatusOnPay($this)) { + $this->error = $orderSource->getError(); + return false; + } + // 余额支付 + if ($payType == PayTypeEnum::BALANCE) { + return $this->onPaymentByBalance($this['order_no']); + } + return true; + } + + /** + * 构建支付请求的参数 + * @param $user + * @param $order + * @param $payType + * @return array + * @throws BaseException + * @throws \think\exception\DbException + */ + public function onOrderPayment($user, $order, $payType) + { + if ($payType == PayTypeEnum::WECHAT) { + return $this->onPaymentByWechat($user, $order); + } + return []; + } + + /** + * 构建微信支付请求 + * @param $user + * @param $order + * @return array + * @throws BaseException + * @throws \think\exception\DbException + */ + protected function onPaymentByWechat($user, $order) + { + return PaymentService::wechat( + $user, + $order['order_id'], + $order['order_no'], + $order['pay_price'], + OrderTypeEnum::MASTER + ); + } + + /** + * 立即购买:获取订单商品列表 + * @param $goodsId + * @param $goodsSkuId + * @param $goodsNum + * @return array + */ + public function getOrderGoodsListByNow($goodsId, $goodsSkuId, $goodsNum) + { + // 商品详情 + /* @var GoodsModel $goods */ + $goods = GoodsModel::detail($goodsId); + // 商品sku信息 + $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $goodsSkuId); + // 商品列表 + $goodsList = [$goods->hidden(['category', 'content', 'image', 'sku'])]; + foreach ($goodsList as &$item) { + // 商品单价 + $item['goods_price'] = $item['goods_sku']['goods_price']; + // 商品购买数量 + $item['total_num'] = $goodsNum; + $item['spec_sku_id'] = $item['goods_sku']['spec_sku_id']; + // 商品购买总金额 + $item['total_price'] = helper::bcmul($item['goods_price'], $goodsNum); + } + return $goodsList; + } + + /** + * 余额支付标记订单已支付 + * @param $orderNo + * @return bool + * @throws \think\exception\DbException + */ + public function onPaymentByBalance($orderNo) + { + // 获取订单详情 + $PaySuccess = new PaySuccess($orderNo); + // 发起余额支付 + $status = $PaySuccess->onPaySuccess(PayTypeEnum::BALANCE); + if (!$status) { + $this->error = $PaySuccess->getError(); + } + return $status; + } + + /** + * 用户中心订单列表 + * @param $user_id + * @param string $type + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList($user_id, $type = 'all') + { + // 筛选条件 + $filter = []; + // 订单数据类型 + switch ($type) { + case 'all': + break; + case 'payment'; + $filter['pay_status'] = PayStatusEnum::PENDING; + $filter['order_status'] = 10; + break; + case 'delivery'; + $filter['pay_status'] = PayStatusEnum::SUCCESS; + $filter['delivery_status'] = 10; + $filter['order_status'] = 10; + break; + case 'received'; + $filter['pay_status'] = PayStatusEnum::SUCCESS; + $filter['delivery_status'] = 20; + $filter['receipt_status'] = 10; + $filter['order_status'] = 10; + break; + case 'comment'; + $filter['is_comment'] = 0; + $filter['order_status'] = 30; + break; + } + return $this->with(['goods.image']) + ->where('user_id', '=', $user_id) + ->where($filter) + ->where('is_delete', '=', 0) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 取消订单 + * @param UserModel $user + * @return bool|mixed + */ + public function cancel($user) + { + if ($this['delivery_status']['value'] == 20) { + $this->error = '已发货订单不可取消'; + return false; + } + // 订单取消事件 + return $this->transaction(function () use ($user) { + // 订单是否已支付 + $isPay = $this['pay_status']['value'] == PayStatusEnum::SUCCESS; + // 未付款的订单 + if ($isPay == false) { + // 回退商品库存 + FactoryStock::getFactory($this['order_source'])->backGoodsStock($this['goods'], $isPay); + // 回退用户优惠券 + $this['coupon_id'] > 0 && UserCouponModel::setIsUse($this['coupon_id'], false); + // 回退用户积分 + $describe = "订单取消:{$this['order_no']}"; + $this['points_num'] > 0 && $user->setIncPoints($this['points_num'], $describe); + } + // 更新订单状态 + return $this->save(['order_status' => $isPay ? OrderStatusEnum::APPLY_CANCEL : OrderStatusEnum::CANCELLED]); + }); + } + + /** + * 确认收货 + * @return bool|mixed + */ + public function receipt() + { + // 验证订单是否合法 + // 条件1: 订单必须已发货 + // 条件2: 订单必须未收货 + if ($this['delivery_status']['value'] != 20 || $this['receipt_status']['value'] != 10) { + $this->error = '该订单不合法'; + return false; + } + return $this->transaction(function () { + // 更新订单状态 + $status = $this->save([ + 'receipt_status' => 20, + 'receipt_time' => time(), + 'order_status' => 30 + ]); +// // 获取已完成的订单 +// $completed = self::detail($this['order_id'], [ +// 'user', 'address', 'goods', 'express', // 用于好物圈 +// ]); + // 执行订单完成后的操作 + $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::MASTER); + $OrderCompleteService->complete([$this], static::$wxapp_id); + return $status; + }); + } + + /** + * 获取订单总数 + * @param $user + * @param string $type + * @return int|string + * @throws \think\Exception + */ + public function getCount($user, $type = 'all') + { + if ($user === false) { + return false; + } + // 筛选条件 + $filter = []; + // 订单数据类型 + switch ($type) { + case 'all': + break; + case 'payment'; + $filter['pay_status'] = PayStatusEnum::PENDING; + break; + case 'received'; + $filter['pay_status'] = PayStatusEnum::SUCCESS; + $filter['delivery_status'] = 20; + $filter['receipt_status'] = 10; + break; + case 'comment'; + $filter['order_status'] = 30; + $filter['is_comment'] = 0; + break; + } + return $this->where('user_id', '=', $user['user_id']) + ->where('order_status', '<>', 20) + ->where($filter) + ->where('is_delete', '=', 0) + ->count(); + } + + /** + * 订单详情 + * @param $order_id + * @param null $user_id + * @return null|static + * @throws BaseException + * @throws \think\exception\DbException + */ + public static function getUserOrderDetail($order_id, $user_id) + { + if (!$order = self::get([ + 'order_id' => $order_id, + 'user_id' => $user_id, + ], [ + 'goods' => ['image', 'goods', 'refund'], + 'address', 'express', 'extract_shop' + ]) + ) { + throw new BaseException(['msg' => '订单不存在']); + } + return $order; + } + + /** + * 判断当前订单是否允许核销 + * @param static $order + * @return bool + */ + public function checkExtractOrder(&$order) + { + if ( + $order['pay_status']['value'] == PayStatusEnum::SUCCESS + && $order['delivery_type']['value'] == DeliveryTypeEnum::EXTRACT + && $order['delivery_status']['value'] == 10 + ) { + return true; + } + $this->setError('该订单不能被核销'); + return false; + } + + /** + * 当前订单是否允许申请售后 + * @return bool + */ + public function isAllowRefund() + { + // 必须是已发货的订单 + if ($this['delivery_status']['value'] != 20) { + return false; + } + // 允许申请售后期限(天) + $refundDays = SettingModel::getItem('trade')['order']['refund_days']; + // 不允许售后 + if ($refundDays == 0) { + return false; + } + // 当前时间超出允许申请售后期限 + if ( + $this['receipt_status'] == 20 + && time() > ($this['receipt_time'] + ((int)$refundDays * 86400)) + ) { + return false; + } + return true; + } + + /** + * 设置错误信息 + * @param $error + */ + protected function setError($error) + { + empty($this->error) && $this->error = $error; + } + + /** + * 是否存在错误 + * @return bool + */ + public function hasError() + { + return !empty($this->error); + } + +} diff --git a/source/application/api/model/OrderAddress.php b/source/application/api/model/OrderAddress.php new file mode 100644 index 0000000..c78ece2 --- /dev/null +++ b/source/application/api/model/OrderAddress.php @@ -0,0 +1,23 @@ + $order_id, 'is_comment' => 0], ['orderM', 'image']); + } + +} diff --git a/source/application/api/model/OrderRefund.php b/source/application/api/model/OrderRefund.php new file mode 100644 index 0000000..36e73b6 --- /dev/null +++ b/source/application/api/model/OrderRefund.php @@ -0,0 +1,171 @@ + '已同意退货并已退款', 20 => '已同意换货']; + return $text[$data['type']]; + } + // 已取消 + if ($data['status'] == 30) { + return '已取消'; + } + // 已拒绝 + if ($data['status'] == 10) { +// return '已拒绝'; + return $data['type'] == 10 ? '已拒绝退货退款' : '已拒绝换货'; + } + // 进行中 + if ($data['status'] == 0) { + if ($data['is_agree'] == 0) { + return '等待审核中'; + } + if ($data['type'] == 10) { + return $data['is_user_send'] ? '已发货,待平台确认' : '已同意退货,请及时发货'; + } + } + return $value; + } + + /** + * 获取用户售后单列表 + * @param $user_id + * @param int $state + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList($user_id, $state = -1) + { + $state > -1 && $this->where('status', '=', $state); + return $this->with(['order_master', 'order_goods.image']) + ->where('user_id', '=', $user_id) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 用户发货 + * @param $data + * @return false|int + */ + public function delivery($data) + { + if ( + $this['type']['value'] != 10 + || $this['is_agree']['value'] != 10 + || $this['is_user_send'] != 0 + ) { + $this->error = '当前售后单不合法,不允许该操作'; + return false; + } + if ($data['express_id'] <= 0) { + $this->error = '请选择物流公司'; + return false; + } + if (empty($data['express_no'])) { + $this->error = '请填写物流单号'; + return false; + } + return $this->save([ + 'is_user_send' => 1, + 'send_time' => time(), + 'express_id' => (int)$data['express_id'], + 'express_no' => $data['express_no'], + ]); + } + + /** + * 新增售后单记录 + * @param $user + * @param $goods + * @param $data + * @return bool + * @throws \Exception + */ + public function apply($user, $goods, $data) + { + $this->startTrans(); + try { + // 新增售后单记录 + $this->save([ + 'order_goods_id' => $data['order_goods_id'], + 'order_id' => $goods['order_id'], + 'user_id' => $user['user_id'], + 'type' => $data['type'], + 'apply_desc' => $data['content'], + 'is_agree' => 0, + 'status' => 0, + 'wxapp_id' => self::$wxapp_id, + ]); + // 记录凭证图片关系 + if (isset($data['images']) && !empty($data['images'])) { + $this->saveImages($this['order_refund_id'], $data['images']); + } + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 记录售后单图片 + * @param $order_refund_id + * @param $images + * @return bool + * @throws \Exception + */ + private function saveImages($order_refund_id, $images) + { + // 生成评价图片数据 + $data = []; + foreach (explode(',', $images) as $image_id) { + $data[] = [ + 'order_refund_id' => $order_refund_id, + 'image_id' => $image_id, + 'wxapp_id' => self::$wxapp_id + ]; + } + return !empty($data) && (new OrderRefundImage)->saveAll($data); + } + +} \ No newline at end of file diff --git a/source/application/api/model/OrderRefundAddress.php b/source/application/api/model/OrderRefundAddress.php new file mode 100644 index 0000000..f9bfdff --- /dev/null +++ b/source/application/api/model/OrderRefundAddress.php @@ -0,0 +1,23 @@ + $openId], ['address', 'addressDefault', 'grade']); + } + + /** + * 用户登录 + * @param array $post + * @return string + * @throws BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function login($post) + { + // 微信登录 获取session_key + $session = $this->wxlogin($post['code']); + // 自动注册用户 + $refereeId = isset($post['referee_id']) ? $post['referee_id'] : null; + $userInfo = json_decode(htmlspecialchars_decode($post['user_info']), true); + $user_id = $this->register($session['openid'], $userInfo, $refereeId); + // 生成token (session3rd) + $this->token = $this->token($session['openid']); + // 记录缓存, 7天 + Cache::set($this->token, $session, 86400 * 7); + return $user_id; + } + + /** + * 获取token + * @return mixed + */ + public function getToken() + { + return $this->token; + } + + /** + * 微信登录 + * @param $code + * @return array|mixed + * @throws BaseException + * @throws \think\exception\DbException + */ + private function wxlogin($code) + { + // 获取当前小程序信息 + $wxConfig = Wxapp::getWxappCache(); + // 验证appid和appsecret是否填写 + if (empty($wxConfig['app_id']) || empty($wxConfig['app_secret'])) { + throw new BaseException(['msg' => '请到 [后台-小程序设置] 填写appid 和 appsecret']); + } + // 微信登录 (获取session_key) + $WxUser = new WxUser($wxConfig['app_id'], $wxConfig['app_secret']); + if (!$session = $WxUser->sessionKey($code)) { + throw new BaseException(['msg' => $WxUser->getError()]); + } + return $session; + } + + /** + * 生成用户认证的token + * @param $openid + * @return string + */ + private function token($openid) + { + $wxapp_id = self::$wxapp_id; + // 生成一个不会重复的随机字符串 + $guid = \getGuidV4(); + // 当前时间戳 (精确到毫秒) + $timeStamp = microtime(true); + // 自定义一个盐 + $salt = 'token_salt'; + return md5("{$wxapp_id}_{$timeStamp}_{$openid}_{$guid}_{$salt}"); + } + + /** + * 自动注册用户 + * @param $open_id + * @param $data + * @param int $refereeId + * @return mixed + * @throws \Exception + * @throws \think\exception\DbException + */ + private function register($open_id, $data, $refereeId = null) + { + // 查询用户是否已存在 + $user = self::detail(['open_id' => $open_id]); + $model = $user ?: $this; + $this->startTrans(); + try { + // 保存/更新用户记录 + if (!$model->allowField(true)->save(array_merge($data, [ + 'open_id' => $open_id, + 'wxapp_id' => self::$wxapp_id + ]))) { + throw new BaseException(['msg' => '用户注册失败']); + } + // 记录推荐人关系 + if (!$user && $refereeId > 0) { + RefereeModel::createRelation($model['user_id'], $refereeId); + } + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw new BaseException(['msg' => $e->getMessage()]); + } + return $model['user_id']; + } + + /** + * 个人中心菜单列表 + * @return array + */ + public function getMenus() + { + $menus = [ + 'address' => [ + 'name' => '收货地址', + 'url' => 'pages/address/index', + 'icon' => 'map' + ], + 'coupon' => [ + 'name' => '领券中心', + 'url' => 'pages/coupon/coupon', + 'icon' => 'lingquan' + ], + 'my_coupon' => [ + 'name' => '我的优惠券', + 'url' => 'pages/user/coupon/coupon', + 'icon' => 'youhuiquan' + ], + 'sharing_order' => [ + 'name' => '拼团订单', + 'url' => 'pages/sharing/order/index', + 'icon' => 'pintuan' + ], + 'my_bargain' => [ + 'name' => '我的砍价', + 'url' => 'pages/bargain/index/index?tab=1', + 'icon' => 'kanjia' + ], + 'dealer' => [ + 'name' => '分销中心', + 'url' => 'pages/dealer/index/index', + 'icon' => 'fenxiaozhongxin' + ], + 'help' => [ + 'name' => '我的帮助', + 'url' => 'pages/user/help/index', + 'icon' => 'help' + ], + ]; + // 判断分销功能是否开启 + if (DealerSettingModel::isOpen()) { + $menus['dealer']['name'] = DealerSettingModel::getDealerTitle(); + } else { + unset($menus['dealer']); + } + return $menus; + } + +} diff --git a/source/application/api/model/UserAddress.php b/source/application/api/model/UserAddress.php new file mode 100644 index 0000000..8d199c5 --- /dev/null +++ b/source/application/api/model/UserAddress.php @@ -0,0 +1,148 @@ +transaction(function () use ($user, $data) { + // 整理地区信息 + $region = explode(',', $data['region']); + $provinceId = Region::getIdByName($region[0], 1); + $cityId = Region::getIdByName($region[1], 2, $provinceId); + $regionId = Region::getIdByName($region[2], 3, $cityId); + // 验证城市ID是否合法 + if (!$this->checkCityId($cityId)) return false; + // 添加收货地址 + $this->allowField(true)->save([ + 'name' => $data['name'], + 'phone' => $data['phone'], + 'province_id' => $provinceId, + 'city_id' => $cityId, + 'region_id' => $regionId, + 'detail' => $data['detail'], + 'district' => ($regionId === 0 && !empty($region[2])) ? $region[2] : '', + 'user_id' => $user['user_id'], + 'wxapp_id' => self::$wxapp_id + ]); + // 设为默认收货地址 + !$user['address_id'] && $user->save(['address_id' => $this['address_id']]); + return true; + }); + } + + /** + * 编辑收货地址 + * @param $data + * @return false|int + */ + public function edit($data) + { + // 整理地区信息 + $region = explode(',', $data['region']); + $provinceId = Region::getIdByName($region[0], 1); + $cityId = Region::getIdByName($region[1], 2, $provinceId); + $regionId = Region::getIdByName($region[2], 3, $cityId); + // 验证城市ID是否合法 + if (!$this->checkCityId($cityId)) return false; + // 更新收货地址 + return $this->allowField(true)->save([ + 'name' => $data['name'], + 'phone' => $data['phone'], + 'province_id' => $provinceId, + 'city_id' => $cityId, + 'region_id' => $regionId, + 'detail' => $data['detail'], + 'district' => ($regionId === 0 && !empty($region[2])) ? $region[2] : '', + ]) !== false; + } + + /** + * 验证城市ID是否合法 + * @param $cityId + * @return bool + */ + private function checkCityId($cityId) + { + if ($cityId <= 0) { + \log_write([ + 'system_msg' => '选择的城市不存在', + 'param' => \request()->param() + ], 'error'); + $this->error = '很抱歉,您选择的城市不存在'; + return false; + } + return true; + } + + /** + * 设为默认收货地址 + * @param User $user + * @return int + */ + public function setDefault($user) + { + // 设为默认地址 + return $user->save(['address_id' => $this['address_id']]); + } + + /** + * 删除收货地址 + * @param User $user + * @return int + */ + public function remove($user) + { + // 查询当前是否为默认地址 + $user['address_id'] == $this['address_id'] && $user->save(['address_id' => 0]); + return $this->delete(); + } + + /** + * 收货地址详情 + * @param $user_id + * @param $address_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($user_id, $address_id) + { + return self::get(compact('user_id', 'address_id')); + } + +} diff --git a/source/application/api/model/UserCoupon.php b/source/application/api/model/UserCoupon.php new file mode 100644 index 0000000..a4185a3 --- /dev/null +++ b/source/application/api/model/UserCoupon.php @@ -0,0 +1,190 @@ +where('user_id', '=', $user_id) + ->where('is_use', '=', $is_use ? 1 : 0) + ->where('is_expire', '=', $is_expire ? 1 : 0) + ->select(); + } + + /** + * 获取用户优惠券总数量(可用) + * @param $user_id + * @return int|string + * @throws \think\Exception + */ + public function getCount($user_id) + { + return $this->where('user_id', '=', $user_id) + ->where('is_use', '=', 0) + ->where('is_expire', '=', 0) + ->count(); + } + + /** + * 获取用户优惠券ID集 + * @param $user_id + * @return array + */ + public function getUserCouponIds($user_id) + { + return $this->where('user_id', '=', $user_id)->column('coupon_id'); + } + + /** + * 领取优惠券 + * @param $user + * @param $coupon_id + * @return bool|false|int + * @throws \think\exception\DbException + */ + public function receive($user, $coupon_id) + { + // 获取优惠券信息 + $coupon = Coupon::detail($coupon_id); + // 验证优惠券是否可领取 + if (!$this->checkReceive($user, $coupon)) { + return false; + } + // 添加领取记录 + return $this->add($user, $coupon); + } + + /** + * 添加领取记录 + * @param $user + * @param Coupon $coupon + * @return bool + */ + private function add($user, $coupon) + { + // 计算有效期 + if ($coupon['expire_type'] == 10) { + $start_time = time(); + $end_time = $start_time + ($coupon['expire_day'] * 86400); + } else { + $start_time = $coupon['start_time']['value']; + $end_time = $coupon['end_time']['value']; + } + // 整理领取记录 + $data = [ + 'coupon_id' => $coupon['coupon_id'], + 'name' => $coupon['name'], + 'color' => $coupon['color']['value'], + 'coupon_type' => $coupon['coupon_type']['value'], + 'reduce_price' => $coupon['reduce_price'], + 'discount' => $coupon->getData('discount'), + 'min_price' => $coupon['min_price'], + 'expire_type' => $coupon['expire_type'], + 'expire_day' => $coupon['expire_day'], + 'start_time' => $start_time, + 'end_time' => $end_time, + 'apply_range' => $coupon['apply_range'], + 'user_id' => $user['user_id'], + 'wxapp_id' => self::$wxapp_id + ]; + return $this->transaction(function () use ($data, $coupon) { + // 添加领取记录 + $status = $this->save($data); + if ($status) { + // 更新优惠券领取数量 + $coupon->setIncReceiveNum(); + } + return $status; + }); + } + + /** + * 验证优惠券是否可领取 + * @param $user + * @param Coupon $coupon + * @return bool + */ + private function checkReceive($user, $coupon) + { + if (!$coupon) { + $this->error = '优惠券不存在'; + return false; + } + if (!$coupon->checkReceive()) { + $this->error = $coupon->getError(); + return false; + } + // 验证是否已领取 + $userCouponIds = $this->getUserCouponIds($user['user_id']); + if (in_array($coupon['coupon_id'], $userCouponIds)) { + $this->error = '该优惠券已领取'; + return false; + } + return true; + } + + /** + * 订单结算优惠券列表 + * @param int $user_id 用户id + * @param double $orderPayPrice 订单商品总金额 + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public static function getUserCouponList($user_id, $orderPayPrice) + { + // todo: 新增筛选条件: 最低消费金额 + // 获取用户可用的优惠券列表 + $list = (new self)->getList($user_id); + $data = []; + foreach ($list as $coupon) { + // 最低消费金额 + if ($orderPayPrice < $coupon['min_price']) continue; + // 有效期范围内 + if ($coupon['start_time']['value'] > time()) continue; + $key = $coupon['user_coupon_id']; + $data[$key] = [ + 'user_coupon_id' => $coupon['user_coupon_id'], + 'name' => $coupon['name'], + 'color' => $coupon['color'], + 'coupon_type' => $coupon['coupon_type'], + 'reduce_price' => $coupon['reduce_price'], + 'discount' => $coupon['discount'], + 'min_price' => $coupon['min_price'], + 'expire_type' => $coupon['expire_type'], + 'start_time' => $coupon['start_time'], + 'end_time' => $coupon['end_time'], + ]; + // 计算打折金额 + if ($coupon['coupon_type']['value'] == 20) { +// $reduce_price = $orderPayPrice * ($coupon['discount'] / 10); + $reducePrice = helper::bcmul($orderPayPrice, $coupon['discount'] / 10); + $data[$key]['reduced_price'] = bcsub($orderPayPrice, $reducePrice, 2); + } else + $data[$key]['reduced_price'] = $coupon['reduce_price']; + } + // 根据折扣金额排序并返回 + return array_sort($data, 'reduced_price', true); + } + +} diff --git a/source/application/api/model/Wxapp.php b/source/application/api/model/Wxapp.php new file mode 100644 index 0000000..bbf194f --- /dev/null +++ b/source/application/api/model/Wxapp.php @@ -0,0 +1,29 @@ + 0 ? parent::detail($page_id) : parent::getHomePage(); + // 页面diy元素 + $items = $detail['page_data']['items']; + // 页面顶部导航 + isset($detail['page_data']['page']) && $items['page'] = $detail['page_data']['page']; + // 获取动态数据 + $model = new self; + foreach ($items as $key => $item) { + if ($item['type'] === 'window') { + $items[$key]['data'] = array_values($item['data']); + } else if ($item['type'] === 'goods') { + $items[$key]['data'] = $model->getGoodsList($user, $item); + } else if ($item['type'] === 'sharingGoods') { + $items[$key]['data'] = $model->getSharingGoodsList($user, $item); + } else if ($item['type'] === 'bargainGoods') { + $items[$key]['data'] = $model->getBargainGoodsList($item); + } else if ($item['type'] === 'sharpGoods') { + $items[$key]['data'] = $model->getSharpGoodsList($item); + } else if ($item['type'] === 'coupon') { + $items[$key]['data'] = $model->getCouponList($user, $item); + } else if ($item['type'] === 'article') { + $items[$key]['data'] = $model->getArticleList($item); + } else if ($item['type'] === 'special') { + $items[$key]['data'] = $model->getSpecialList($item); + } else if ($item['type'] === 'shop') { + $items[$key]['data'] = $model->getShopList($item); + } + } + return ['page' => $items['page'], 'items' => $items]; + } + + /** + * 商品组件:获取商品列表 + * @param $user + * @param $item + * @return array + * @throws \think\exception\DbException + */ + private function getGoodsList($user, $item) + { + // 获取商品数据 + $model = new GoodsModel; + if ($item['params']['source'] === 'choice') { + // 数据来源:手动 + $goodsIds = array_column($item['data'], 'goods_id'); + $goodsList = $model->getListByIdsFromApi($goodsIds, $user); + } else { + // 数据来源:自动 + $goodsList = $model->getList([ + 'status' => 10, + 'category_id' => $item['params']['auto']['category'], + 'sortType' => $item['params']['auto']['goodsSort'], + 'listRows' => $item['params']['auto']['showNum'] + ], $user); + } + if ($goodsList->isEmpty()) return []; + // 格式化商品列表 + $data = []; + foreach ($goodsList as $goods) { + $data[] = [ + 'goods_id' => $goods['goods_id'], + 'goods_name' => $goods['goods_name'], + 'selling_point' => $goods['selling_point'], + 'image' => $goods['image'][0]['file_path'], + 'goods_image' => $goods['image'][0]['file_path'], + 'goods_price' => $goods['sku'][0]['goods_price'], + 'line_price' => $goods['sku'][0]['line_price'], + 'goods_sales' => $goods['goods_sales'], + ]; + } + return $data; + } + + /** + * 商品组件:获取拼团商品列表 + * @param $user + * @param $item + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getSharingGoodsList($user, $item) + { + // 获取商品数据 + $model = new SharingGoodsModel; + if ($item['params']['source'] === 'choice') { + // 数据来源:手动 + $goodsIds = array_column($item['data'], 'goods_id'); + $goodsList = $model->getListByIdsFromApi($goodsIds, $user); + } else { + // 数据来源:自动 + $goodsList = $model->getList([ + 'status' => 10, + 'category_id' => $item['params']['auto']['category'], + 'sortType' => $item['params']['auto']['goodsSort'], + 'listRows' => $item['params']['auto']['showNum'] + ], $user); + } + if ($goodsList->isEmpty()) return []; + // 格式化商品列表 + $data = []; + foreach ($goodsList as $goods) { + $data[] = [ + 'goods_id' => $goods['goods_id'], + 'goods_name' => $goods['goods_name'], + 'selling_point' => $goods['selling_point'], + 'people' => $goods['people'], + 'goods_sales' => $goods['goods_sales'], + 'image' => $goods['image'][0]['file_path'], + 'goods_image' => $goods['image'][0]['file_path'], + 'sharing_price' => $goods['sku'][0]['sharing_price'], + 'goods_price' => $goods['sku'][0]['goods_price'], + 'line_price' => $goods['sku'][0]['line_price'], + ]; + } + return $data; + } + + /** + * 商品组件:获取拼团商品列表 + * @param $item + * @return array + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getBargainGoodsList($item) + { + // 获取商品数据 + $model = new BargainActiveModel; + if ($item['params']['source'] === 'choice') { + // 数据来源:手动 + $activeIds = helper::getArrayColumn($item['data'], 'active_id'); + $activeList = $model->getListByIds($activeIds); + } else { + // 数据来源:自动 + $activeList = $model->getHallList([ + 'sortType' => $item['params']['auto']['goodsSort'], + 'listRows' => $item['params']['auto']['showNum'] + ]); + } + if ($activeList->isEmpty()) return []; + // 格式化商品列表 + $data = []; + foreach ($activeList as $item) { + $data[] = [ + 'active_id' => $item['active_id'], + 'goods_name' => $item['goods']['goods_name'], + 'goods_image' => $item['goods']['goods_image'], + 'floor_price' => $item['floor_price'], + 'original_price' => $item['goods']['goods_sku']['goods_price'], + 'peoples' => $item['peoples'], + 'helps_count' => $item['helps_count'], + 'helps' => $item['helps'], + ]; + } + return $data; + } + + /** + * 秒杀商品组件:获取秒杀活动 + * @param $item + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getSharpGoodsList($item) + { + // 获取商品数据 + $service = new SharpActiveService; + // 获取秒杀活动及商品列表 + return $service->getSharpModular(['limit' => $item['params']['showNum']]); + } + + /** + * 优惠券组件:获取优惠券列表 + * @param $user + * @param $item + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getCouponList($user, $item) + { + // 获取优惠券数据 + return (new Coupon)->getList($user, $item['params']['limit'], true); + } + + /** + * 文章组件:获取文章列表 + * @param $item + * @return array + * @throws \think\exception\DbException + */ + private function getArticleList($item) + { + // 获取文章数据 + $model = new Article; + $articleList = $model->getList($item['params']['auto']['category'], $item['params']['auto']['showNum']); + return $articleList->isEmpty() ? [] : $articleList->toArray()['data']; + } + + /** + * 头条快报:获取头条列表 + * @param $item + * @return array + * @throws \think\exception\DbException + */ + private function getSpecialList($item) + { + // 获取头条数据 + $model = new Article; + $articleList = $model->getList($item['params']['auto']['category'], $item['params']['auto']['showNum']); + return $articleList->isEmpty() ? [] : $articleList->toArray()['data']; + } + + /** + * 线下门店组件:获取门店列表 + * @param $item + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getShopList($item) + { + // 获取商品数据 + $model = new ShopModel; + if ($item['params']['source'] === 'choice') { + // 数据来源:手动 + $shopIds = array_column($item['data'], 'shop_id'); + $shopList = $model->getListByIds($shopIds); + } else { + // 数据来源:自动 + $shopList = $model->getList(null, false, false, $item['params']['auto']['showNum']); + } + if ($shopList->isEmpty()) return []; + // 格式化商品列表 + $data = []; + foreach ($shopList as $shop) { + $data[] = [ + 'shop_id' => $shop['shop_id'], + 'shop_name' => $shop['shop_name'], + 'logo_image' => $shop['logo']['file_path'], + 'phone' => $shop['phone'], + 'region' => $shop['region'], + 'address' => $shop['address'], + ]; + } + return $data; + } + +} diff --git a/source/application/api/model/WxappPrepayId.php b/source/application/api/model/WxappPrepayId.php new file mode 100644 index 0000000..6a50815 --- /dev/null +++ b/source/application/api/model/WxappPrepayId.php @@ -0,0 +1,37 @@ +save([ + 'prepay_id' => $prepayId, + 'order_id' => $orderId, + 'order_type' => $orderType, + 'user_id' => $userId, + 'can_use_times' => 0, + 'used_times' => 0, + 'expiry_time' => time() + (7 * 86400), + 'wxapp_id' => self::$wxapp_id, + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/model/article/Category.php b/source/application/api/model/article/Category.php new file mode 100644 index 0000000..abe46df --- /dev/null +++ b/source/application/api/model/article/Category.php @@ -0,0 +1,76 @@ +deleteCache(); + return $this->allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + $this->deleteCache(); + return $this->allowField(true)->save($data); + } + + /** + * 删除商品分类 + * @param $category_id + * @return bool|int + */ + public function remove($category_id) + { + // 判断是否存在文章 + $articleCount = ArticleModel::getArticleTotal(['category_id' => $category_id]); + if ($articleCount > 0) { + $this->error = '该分类下存在' . $articleCount . '个文章,不允许删除'; + return false; + } + $this->deleteCache(); + return $this->delete(); + } + + /** + * 删除缓存 + * @return bool + */ + private function deleteCache() + { + return Cache::rm('article_category_' . self::$wxapp_id); + } + +} diff --git a/source/application/api/model/bargain/Active.php b/source/application/api/model/bargain/Active.php new file mode 100644 index 0000000..c625f02 --- /dev/null +++ b/source/application/api/model/bargain/Active.php @@ -0,0 +1,184 @@ +getList($param); + } + + /** + * 获取砍价活动列表(根据活动id集) + * @param $activeIds + * @param array $param + * @return mixed|\think\Paginator + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($activeIds, $param = []) + { + $this->where('active_id', 'in', $activeIds); + return $this->getList($param); + } + + /** + * 获取砍价活动列表 + * @param $param + * @return mixed|\think\Paginator + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getList($param) + { + // 商品列表获取条件 + $params = array_merge([ + 'status' => 1, // 商品状态 + 'sortType' => 'all', // 排序类型 + 'listRows' => 15, // 每页数量 + ], $param); + // 排序规则 + if ($params['sortType'] === 'all') { + $this->order(['sort' => 'asc']); + } elseif ($params['sortType'] === 'sales') { + $this->order(['active_sales' => 'desc']); + } elseif ($params['sortType'] === 'price') { + $this->order(['floor_price' => $params['sortPrice'] ? 'desc' : 'asc']); + } + // 砍价活动列表 + $list = $this->field(['*', '(actual_sales + initial_sales) as active_sales']) + ->where('start_time', '<=', time()) + ->where('end_time', '>=', time()) + ->where('status', '=', 1) + ->where('is_delete', '=', 0) + ->order(['sort' => 'asc']) + ->paginate($params['listRows'], false, [ + 'query' => \request()->request() + ]); + // 设置商品数据 + $list = GoodsService::setGoodsData($list); + // 整理正在砍价的助力信息 + $list = $this->setHelpsData($list); + return $list; + } + + /** + * 整理正在砍价的助力信息 + * @param $list + * @return mixed + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function setHelpsData($list) + { + $model = new TaskHelpModel; + foreach ($list as &$item) { + $item['helps'] = $model->getHelpListByActiveId($item['active_id']); + $item['helps_count'] = $model->getHelpCountByActiveId($item['active_id']); + } + return $list; + } + + /** + * 获取砍价活动详情 + * @param $activeId + * @return Active|bool|null + * @throws \think\exception\DbException + */ + public function getDetail($activeId) + { + $model = static::detail($activeId); + if (empty($model) || $model['is_delete'] == true || $model['status'] == false) { + $this->error = '很抱歉,该砍价商品不存在或已下架'; + return false; + } + return $model; + } + + /** + * 获取用户是否正在参与改砍价活动,如果已参与则返回task_id + * @param $activeId + * @param bool $user + * @return bool|int + */ + public function getWhetherPartake($activeId, $user = false) + { + if ($user === false) { + return false; + } + return TaskModel::getHandByUser($activeId, $user['user_id']); + } + + /** + * 累计活动销量(实际) + * @return int|true + * @throws \think\Exception + */ + public function setIncSales() + { + return $this->setInc('actual_sales'); + } + +} \ No newline at end of file diff --git a/source/application/api/model/bargain/Setting.php b/source/application/api/model/bargain/Setting.php new file mode 100644 index 0000000..7622f7b --- /dev/null +++ b/source/application/api/model/bargain/Setting.php @@ -0,0 +1,35 @@ + static::getItem('basic')['bargain_rules'], + ]; + } + + /** + * 获取是否开启分销 + * @return array + */ + public static function getIsDealer() + { + return static::getItem('basic')['is_dealer']; + } + +} \ No newline at end of file diff --git a/source/application/api/model/bargain/Task.php b/source/application/api/model/bargain/Task.php new file mode 100644 index 0000000..74d66e2 --- /dev/null +++ b/source/application/api/model/bargain/Task.php @@ -0,0 +1,366 @@ +where('user_id', '=', $userId) + ->where('is_delete', '=', 0) + ->order(['create_time' => 'desc']) + ->paginate(5, false, [ + 'query' => \request()->request() + ]); + // 设置商品数据 + $list = GoodsService::setGoodsData($list); + return $list; + } + + /** + * 获取砍价任务详情 + * @param $taskId + * @param bool $user + * @return array|bool + * @throws \think\exception\DbException + */ + public function getTaskDetail($taskId, $user = false) + { + // 砍价任务详情 + $task = static::detail($taskId, ['user']); + if (empty($task)) { + $this->error = '砍价任务不存在'; + return false; + } + // 砍价活动详情 + $active = ActiveModel::detail($task['active_id']); + // 砍价商品详情 + $goods = GoodsModel::detail($task['goods_id']); + // 商品sku信息 + $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $task['spec_sku_id']); + // 好友助力榜 + $help_list = TaskHelpModel::getListByTaskId($taskId); + // 当前是否为发起人 + $is_creater = $this->isCreater($task, $user); + // 当前是否已砍 + $is_cut = $this->isCut($help_list, $user); + return compact('task', 'is_creater', 'is_cut', 'active', 'goods', 'help_list'); + } + + /** + * 获取砍价任务的商品列表(用于订单结算) + * @param $taskId + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function getTaskGoods($taskId) + { + // 砍价任务详情 + $task = static::detail($taskId); + if (empty($task) || $task['is_delete'] || $task['status'] == false) { + $this->error = '砍价任务不存在或已结束'; + return false; + } + if ($task['is_buy'] == true) { + $this->error = '该砍价商品已购买'; + return false; + } + // 砍价商品详情 + $goods = GoodsModel::detail($task['goods_id']); + // 商品sku信息 + $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $task['spec_sku_id']); + // 商品列表 + $goodsList = [$goods->hidden(['category', 'content', 'image', 'sku'])]; + foreach ($goodsList as &$item) { + // 商品单价 + $item['goods_price'] = $task['actual_price']; + // 商品购买数量 + $item['total_num'] = 1; + $item['spec_sku_id'] = $item['goods_sku']['spec_sku_id']; + // 商品购买总金额 + $item['total_price'] = $task['actual_price']; + } + return $goodsList; + } + + /** + * 订单创建后将砍价任务结束 + * @return false|int + */ + public function setTaskEnd() + { + return $this->save(['status' => 0]); + } + + /** + * 获取用户是否正在参与改砍价活动,如果已参与则返回task_id + * @param $activeId + * @param $userId + * @return bool|int + */ + public static function getHandByUser($activeId, $userId) + { + $taskId = (new static)->where('active_id', '=', $activeId) + ->where('user_id', '=', $userId) + ->where('end_time', '>', time()) + ->where('status', '=', 1) + ->where('is_delete', '=', 0) + ->value('task_id'); + return $taskId ?: false; + } + + /** + * 新增砍价任务 + * @param $userId + * @param $activeId + * @param $goodsSkuId + * @return bool + * @throws \think\exception\DbException + * @throws \Exception + */ + public function partake($userId, $activeId, $goodsSkuId) + { + // 获取活动详情 + if (!$active = $this->getActiveDetail($activeId)) { + return false; + } + // 验证能否创建砍价任务 + if (!$this->onVerify($active, $userId)) { + return false; + } + // 获取商品详情 + $goods = GoodsModel::detail($active['goods_id']); + // 商品sku信息 + $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $goodsSkuId); + // 事务处理 + return $this->transaction(function () use ($userId, $active, $goodsSkuId, $goods) { + // 创建砍价任务 + $this->add($userId, $active, $goodsSkuId, $goods); + // 发起人自砍一刀 + $active['is_self_cut'] && $this->onCutEvent($userId, true); + return true; + }); + } + + /** + * 帮砍一刀 + * @param $user + * @return bool|false|int + */ + public function helpCut($user) + { + // 好友助力榜 + $helpList = TaskHelpModel::getListByTaskId($this['task_id']); + // 当前是否已砍 + if ($this->isCut($helpList, $user)) { + $this->error = '您已参与砍价,请不要重复操作'; + return false; + } + // 帮砍一刀事件 + return $this->transaction(function () use ($user) { + return $this->onCutEvent($user['user_id'], $this->isCreater($this, $user)); + }); + } + + /** + * 砍一刀的金额 + * @return mixed + */ + public function getCutMoney() + { + return $this['section'][$this['cut_people']]; + } + + /** + * 帮砍一刀事件 + * @param $userId + * @param bool $isCreater + * @return false|int + */ + private function onCutEvent($userId, $isCreater = false) + { + // 砍价金额 + $cutMoney = $this->getCutMoney(); + // 砍价助力记录 + $model = new TaskHelpModel; + $model->add($this, $userId, $cutMoney, $isCreater); + // 实际购买金额 + $actualPrice = helper::bcsub($this['actual_price'], $cutMoney); + // 更新砍价任务信息 + $this->save([ + 'cut_people' => ['inc', 1], + 'cut_money' => ['inc', $cutMoney], + 'actual_price' => $actualPrice, + 'is_floor' => helper::bcequal($actualPrice, $this['floor_price']), + ]); + return true; + } + + /** + * 创建砍价任务记录 + * @param $userId + * @param $active + * @param $goodsSkuId + * @param $goods + * @return false|int + * @throws \Exception + */ + private function add($userId, $active, $goodsSkuId, $goods) + { + // 分配砍价金额区间 + $section = $this->calcBargainSection( + $goods['goods_sku']['goods_price'], + $active['floor_price'], + $active['peoples'] + ); + // 新增记录 + return $this->save([ + 'active_id' => $active['active_id'], + 'user_id' => $userId, + 'goods_id' => $active['goods_id'], + 'spec_sku_id' => $goodsSkuId, + 'goods_price' => $goods['goods_sku']['goods_price'], + 'floor_price' => $active['floor_price'], + 'peoples' => $active['peoples'], + 'cut_people' => 0, + 'section' => $section, + 'cut_money' => 0.00, + 'actual_price' => $goods['goods_sku']['goods_price'], + 'end_time' => time() + ($active['expiryt_time'] * 3600), + 'is_buy' => 0, + 'status' => 1, + 'wxapp_id' => static::$wxapp_id, + ]); + } + + /** + * 砍价任务标记为已购买 + * @return false|int + */ + public function setIsBuy() + { + return $this->save(['is_buy' => 1]); + } + + /** + * 分配砍价金额区间 + * @param $goodsPrice + * @param $floorPrice + * @param $peoples + * @return mixed + * @throws \Exception + */ + private function calcBargainSection($goodsPrice, $floorPrice, $peoples) + { + $AmountService = new AmountService(helper::bcsub($goodsPrice, $floorPrice), $peoples); + return $AmountService->handle()['items']; + } + + /** + * 当前是否为发起人 + * @param $task + * @param $user + * @return bool + */ + private function isCreater($task, $user) + { + if ($user === false) return false; + return $user['user_id'] == $task['user_id']; + } + + /** + * 当前是否已砍 + * @param $helpList + * @param $user + * @return bool + */ + private function isCut($helpList, $user) + { + if ($user === false) return false; + foreach ($helpList as $item) { + if ($item['user_id'] == $user['user_id']) return true; + } + return false; + } + + /** + * 获取活动详情 + * @param $activeId + * @return Active|bool|null + * @throws \think\exception\DbException + */ + private function getActiveDetail($activeId) + { + // 获取活动详情 + $ActiveModel = new ActiveModel; + $detail = $ActiveModel->getDetail($activeId); + // 活动详情不存在 + if ($detail === false) { + $this->error = $ActiveModel->getError(); + return false; + } + return $detail; + } + + /** + * 验证能否创建砍价任务 + * @param $active + * @param $userId + * @return bool + */ + private function onVerify($active, $userId) + { + // 活动是否开始 + if (!$active['is_start']) { + $this->error = '很抱歉,当前砍价活动未开始'; + return false; + } + // 活动是否到期合法 + if ($active['is_end']) { + $this->error = '很抱歉,当前砍价活动已结束'; + return false; + } + // 判断当前用户是否已参加 + $taskId = static::getHandByUser($active['active_id'], $userId); + if ($taskId !== false && $taskId > 0) { + $this->error = '很抱歉,当前砍价活动您已参加,无需重复参与'; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/model/bargain/TaskHelp.php b/source/application/api/model/bargain/TaskHelp.php new file mode 100644 index 0000000..d5d6d08 --- /dev/null +++ b/source/application/api/model/bargain/TaskHelp.php @@ -0,0 +1,108 @@ +with(['user']) + ->where('task_id', '=', $taskId) + ->order(['create_time' => 'desc']) + ->select(); + // 隐藏会员昵称 + foreach ($list as &$item) { + $item['user']['nickName'] = \substr_cut($item['user']['nickName']); + } + return $list; + } + + /** + * 新增记录 + * @param $task + * @param $userId + * @param $cutMoney + * @param $isCreater + * @return false|int + */ + public function add($task, $userId, $cutMoney, $isCreater = false) + { + return $this->save([ + 'task_id' => $task['task_id'], + 'active_id' => $task['active_id'], + 'user_id' => $userId, + 'cut_money' => $cutMoney, + 'is_creater' => $isCreater, + 'wxapp_id' => static::$wxapp_id, + ]); + } + + /** + * 根据砍价活动id获取正在砍价的助力信息列表 + * @param $activeId + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getHelpListByActiveId($activeId) + { + return $this + ->with('user') // todo: 废弃 + ->alias('help') + ->field(['help.user_id', 'user.nickName', 'user.avatarUrl']) + ->join('user', 'user.user_id = help.user_id') + ->join('bargain_task task', 'task.task_id = help.task_id') + ->where('help.active_id', '=', $activeId) + // is_creater 只统计发起人 +// ->where('help.is_creater', '=', 1) + ->where('task.status', '=', 1) + ->where('task.is_delete', '=', 0) + ->group('help.user_id') + ->limit(5) + ->select(); + } + + /** + * 根据砍价活动id获取正在砍价的助力人数 + * @param $activeId + * @return int|string + * @throws \think\Exception + */ + public function getHelpCountByActiveId($activeId) + { + return $this->alias('help') + ->join('user', 'user.user_id = help.user_id') + ->join('bargain_task task', 'task.task_id = help.task_id') + ->where('help.active_id', '=', $activeId) + // is_creater 只统计发起人 +// ->where('help.is_creater', '=', 1) + ->where('task.status', '=', 1) + ->where('task.is_delete', '=', 0) + ->group('help.user_id') + ->count(); + } + +} \ No newline at end of file diff --git a/source/application/api/model/dealer/Apply.php b/source/application/api/model/dealer/Apply.php new file mode 100644 index 0000000..3c5dd01 --- /dev/null +++ b/source/application/api/model/dealer/Apply.php @@ -0,0 +1,92 @@ + $user_id]); + return $detail ? ((int)$detail['apply_status'] === 10) : false; + } + + /** + * 提交申请 + * @param $user + * @param $name + * @param $mobile + * @return mixed + * @throws \think\exception\DbException + */ + public function submit($user, $name, $mobile) + { + // 成为分销商条件 + $config = Setting::getItem('condition'); + // 数据整理 + $data = [ + 'user_id' => $user['user_id'], + 'real_name' => trim($name), + 'mobile' => trim($mobile), + 'referee_id' => Referee::getRefereeUserId($user['user_id'], 1), + 'apply_type' => $config['become'], + 'apply_time' => time(), + 'wxapp_id' => self::$wxapp_id, + ]; + if ($config['become'] == 10) { + $data['apply_status'] = 10; + } elseif ($config['become'] == 20) { + $data['apply_status'] = 20; + } + return $this->add($user, $data); + } + + /** + * 更新分销商申请信息 + * @param $user + * @param $data + * @return mixed + */ + private function add($user, $data) + { + // 更新记录 + return $this->transaction(function () use ($user, $data) { + // 实例化模型 + $model = self::detail(['user_id' => $user['user_id']]) ?: $this; + // 保存申请信息 + $model->save($data); + // 无需审核,自动通过 + if ($data['apply_type'] == 20) { + // 新增分销商用户记录 + User::add($user['user_id'], [ + 'real_name' => $data['real_name'], + 'mobile' => $data['mobile'], + 'referee_id' => $data['referee_id'] + ]); + } + return true; + }); + } + +} diff --git a/source/application/api/model/dealer/Capital.php b/source/application/api/model/dealer/Capital.php new file mode 100644 index 0000000..ea45fa6 --- /dev/null +++ b/source/application/api/model/dealer/Capital.php @@ -0,0 +1,23 @@ + -1 && $this->where('is_settled', '=', !!$is_settled); + $data = $this->with(['user']) + ->where('first_user_id|second_user_id|third_user_id', '=', $user_id) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + if ($data->isEmpty()) { + return $data; + } + // 整理订单信息 + $with = ['goods' => ['image', 'refund'], 'address', 'user']; + return OrderService::getOrderList($data, 'order_master', $with); + } + + /** + * 创建分销商订单记录 + * @param $order + * @param int $order_type 订单类型 (10商城订单 20拼团订单) + * @return bool|false|int + * @throws \think\exception\DbException + */ + public static function createOrder(&$order, $order_type = OrderTypeEnum::MASTER) + { + // 分销订单模型 + $model = new self; + // 分销商基本设置 + $setting = Setting::getItem('basic'); + // 是否开启分销功能 + if (!$setting['is_open']) { + return false; + } + // 获取当前买家的所有上级分销商用户id + $dealerUser = $model->getDealerUserId($order['user_id'], $setting['level'], $setting['self_buy']); + // 非分销订单 + if (!$dealerUser['first_user_id']) { + return false; + } + // 计算订单分销佣金 + $capital = $model->getCapitalByOrder($order); + // 保存分销订单记录 + return $model->save([ + 'user_id' => $order['user_id'], + 'order_id' => $order['order_id'], + 'order_type' => $order_type, + // 'order_no' => $order['order_no'], // 废弃 + 'order_price' => $capital['orderPrice'], + 'first_money' => max($capital['first_money'], 0), + 'second_money' => max($capital['second_money'], 0), + 'third_money' => max($capital['third_money'], 0), + 'first_user_id' => $dealerUser['first_user_id'], + 'second_user_id' => $dealerUser['second_user_id'], + 'third_user_id' => $dealerUser['third_user_id'], + 'is_settled' => 0, + 'wxapp_id' => $model::$wxapp_id + ]); + } + + /** + * 获取当前买家的所有上级分销商用户id + * @param $user_id + * @param $level + * @param $self_buy + * @return mixed + * @throws \think\exception\DbException + */ + private function getDealerUserId($user_id, $level, $self_buy) + { + $dealerUser = [ + 'first_user_id' => $level >= 1 ? Referee::getRefereeUserId($user_id, 1, true) : 0, + 'second_user_id' => $level >= 2 ? Referee::getRefereeUserId($user_id, 2, true) : 0, + 'third_user_id' => $level == 3 ? Referee::getRefereeUserId($user_id, 3, true) : 0 + ]; + // 分销商自购 + if ($self_buy && User::isDealerUser($user_id)) { + return [ + 'first_user_id' => $user_id, + 'second_user_id' => $dealerUser['first_user_id'], + 'third_user_id' => $dealerUser['second_user_id'], + ]; + } + return $dealerUser; + } + +} diff --git a/source/application/api/model/dealer/Referee.php b/source/application/api/model/dealer/Referee.php new file mode 100644 index 0000000..645e224 --- /dev/null +++ b/source/application/api/model/dealer/Referee.php @@ -0,0 +1,100 @@ +add($referee_id, $user_id, 1); + // # 记录二级推荐关系 + if ($setting['level'] >= 2) { + // 二级分销商id + $referee_2_id = self::getRefereeUserId($referee_id, 1, true); + // 新增关系记录 + $referee_2_id > 0 && $model->add($referee_2_id, $user_id, 2); + } + // # 记录三级推荐关系 + if ($setting['level'] == 3) { + // 三级分销商id + $referee_3_id = self::getRefereeUserId($referee_id, 2, true); + // 新增关系记录 + $referee_3_id > 0 && $model->add($referee_3_id, $user_id, 3); + } + return true; + } + + /** + * 新增关系记录 + * @param $dealer_id + * @param $user_id + * @param int $level + * @return bool + * @throws \think\Exception + * @throws \think\exception\DbException + */ + private function add($dealer_id, $user_id, $level = 1) + { + // 新增推荐关系 + $wxapp_id = self::$wxapp_id; + $create_time = time(); + $this->insert(compact('dealer_id', 'user_id', 'level', 'wxapp_id', 'create_time')); + // 记录分销商成员数量 + User::setMemberInc($dealer_id, $level); + return true; + } + + /** + * 是否已存在推荐关系 + * @param $user_id + * @return bool + * @throws \think\exception\DbException + */ + private static function isExistReferee($user_id) + { + return !!self::get(['user_id' => $user_id]); + } + +} \ No newline at end of file diff --git a/source/application/api/model/dealer/Setting.php b/source/application/api/model/dealer/Setting.php new file mode 100644 index 0000000..a7a5805 --- /dev/null +++ b/source/application/api/model/dealer/Setting.php @@ -0,0 +1,22 @@ +save([ + 'money' => $this['money'] - $money, + 'freeze_money' => $this['freeze_money'] + $money, + ]); + } + + /** + * 累计分销商成员数量 + * @param $dealer_id + * @param $level + * @return int|true + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public static function setMemberInc($dealer_id, $level) + { + $fields = [1 => 'first_num', 2 => 'second_num', 3 => 'third_num']; + $model = static::detail($dealer_id); + return $model->setInc($fields[$level]); + } + +} \ No newline at end of file diff --git a/source/application/api/model/dealer/Withdraw.php b/source/application/api/model/dealer/Withdraw.php new file mode 100644 index 0000000..78f12dc --- /dev/null +++ b/source/application/api/model/dealer/Withdraw.php @@ -0,0 +1,99 @@ +where('user_id', '=', $user_id); + $apply_status > -1 && $this->where('apply_status', '=', $apply_status); + return $this->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 提交申请 + * @param User $dealer + * @param $data + * @return false|int + * @throws BaseException + */ + public function submit($dealer, $data) + { + // 数据验证 + $this->validation($dealer, $data); + // 新增申请记录 + $this->save(array_merge($data, [ + 'user_id' => $dealer['user_id'], + 'apply_status' => 10, + 'wxapp_id' => self::$wxapp_id, + ])); + // 冻结用户资金 + $dealer->freezeMoney($data['money']); + return true; + } + + /** + * 数据验证 + * @param $dealer + * @param $data + * @throws BaseException + */ + private function validation($dealer, $data) + { + // 结算设置 + $settlement = Setting::getItem('settlement'); + // 最低提现佣金 + if ($data['money'] <= 0) { + throw new BaseException(['msg' => '提现金额不正确']); + } + if ($dealer['money'] <= 0) { + throw new BaseException(['msg' => '当前用户没有可提现佣金']); + } + if ($data['money'] > $dealer['money']) { + throw new BaseException(['msg' => '提现金额不能大于可提现佣金']); + } + if ($data['money'] < $settlement['min_money']) { + throw new BaseException(['msg' => '最低提现金额为' . $settlement['min_money']]); + } + if (!in_array($data['pay_type'], $settlement['pay_type'])) { + throw new BaseException(['msg' => '提现方式不正确']); + } + if ($data['pay_type'] == '20') { + if (empty($data['alipay_name']) || empty($data['alipay_account'])) { + throw new BaseException(['msg' => '请补全提现信息']); + } + } elseif ($data['pay_type'] == '30') { + if (empty($data['bank_name']) || empty($data['bank_account']) || empty($data['bank_card'])) { + throw new BaseException(['msg' => '请补全提现信息']); + } + } + } + +} \ No newline at end of file diff --git a/source/application/api/model/recharge/Order.php b/source/application/api/model/recharge/Order.php new file mode 100644 index 0000000..2ec743d --- /dev/null +++ b/source/application/api/model/recharge/Order.php @@ -0,0 +1,206 @@ +where('user_id', '=', $userId) + ->where('pay_status', '=', PayStatusEnum::SUCCESS) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + + /** + * 获取订单详情(待付款状态) + * @param $orderNo + * @return self|null + * @throws \think\exception\DbException + */ + public static function getPayDetail($orderNo) + { + return self::detail(['order_no' => $orderNo, 'pay_status' => PayStatusEnum::PENDING]); + } + + /** + * 创建充值订单 + * @param \app\api\model\User $user 当前用户信息 + * @param int $planId 套餐id + * @param double $customMoney 自定义充值金额 + * @return bool|false|int + * @throws BaseException + * @throws \think\exception\DbException + */ + public function createOrder($user, $planId = null, $customMoney = 0.00) + { + // 确定充值方式 + $rechargeType = $planId > 0 ? RechargeTypeEnum::PLAN : RechargeTypeEnum::CUSTOM; + // 验证用户输入 + if (!$this->validateForm($rechargeType, $planId, $customMoney)) { + $this->error = $this->error ?: '数据验证错误'; + return false; + } + // 获取订单数据 + $data = $this->getOrderData($user, $rechargeType, $planId, $customMoney); + // 记录订单信息 + return $this->saveOrder($data); + } + + /** + * 保存订单记录 + * @param $data + * @return bool|false|int + */ + private function saveOrder($data) + { + // 写入订单记录 + $this->save($data['order']); + // 记录订单套餐快照 + if (!empty($data['plan'])) { + $PlanModel = new OrderPlanModel; + return $PlanModel->add($this['order_id'], $data['plan']); + } + return true; + } + + /** + * 生成充值订单 + * @param $user + * @param $rechargeType + * @param $planId + * @param $customMoney + * @return array + * @throws BaseException + * @throws \think\exception\DbException + */ + private function getOrderData($user, $rechargeType, $planId, $customMoney) + { + // 订单信息 + $data = [ + 'order' => [ + 'user_id' => $user['user_id'], + 'order_no' => 'RC' . OrderService::createOrderNo(), + 'recharge_type' => $rechargeType, + 'gift_money' => 0.00, + 'wxapp_id' => self::$wxapp_id, + ], + 'plan' => [] // 订单套餐快照 + ]; + // 自定义金额充值 + if ($rechargeType == RechargeTypeEnum::CUSTOM) { + $this->createDataByCustom($data, $customMoney); + } + // 套餐充值 + if ($rechargeType == RechargeTypeEnum::PLAN) { + $this->createDataByPlan($data, $planId); + } + // 实际到账金额 + $data['order']['actual_money'] = bcadd($data['order']['pay_price'], $data['order']['gift_money'], 2); + return $data; + } + + /** + * 创建套餐充值订单数据 + * @param $order + * @param $planId + * @return bool + * @throws BaseException + * @throws \think\exception\DbException + */ + private function createDataByPlan(&$order, $planId) + { + // 获取套餐详情 + $planInfo = PlanModel::detail($planId); + if (empty($planInfo)) { + throw new BaseException(['msg' => '充值套餐不存在']); + } + $order['plan'] = $planInfo; + $order['order']['plan_id'] = $planInfo['plan_id']; + $order['order']['gift_money'] = $planInfo['gift_money']; + $order['order']['pay_price'] = $planInfo['money']; + return true; + } + + /** + * 创建自定义充值订单数据 + * @param $order + * @param $customMoney + * @return bool + */ + private function createDataByCustom(&$order, $customMoney) + { + // 用户支付金额 + $order['order']['pay_price'] = $customMoney; + // 充值设置 + $setting = SettingModel::getItem('recharge'); + if ($setting['is_custom'] == false) { + return true; + } + // 根据自定义充值金额匹配满足的套餐 + if ($setting['is_match_plan'] == true) { + $matchPlanInfo = (new PlanModel)->getMatchPlan($customMoney); + if (!empty($matchPlanInfo)) { + $order['plan'] = $matchPlanInfo; + $order['order']['plan_id'] = $matchPlanInfo['plan_id']; + $order['order']['gift_money'] = $matchPlanInfo['gift_money']; + } + } + return true; + } + + /** + * 表单验证 + * @param $rechargeType + * @param $planId + * @param $customMoney + * @return bool + */ + private function validateForm($rechargeType, $planId, $customMoney) + { + if (empty($planId) && $customMoney <= 0) { + $this->error = '请选择充值套餐或输入充值金额'; + return false; + } + // 验证自定义的金额 + if ($rechargeType == RechargeTypeEnum::CUSTOM && $customMoney <= 0) { + $this->error = '请选择充值套餐或输入充值金额'; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/model/recharge/OrderPlan.php b/source/application/api/model/recharge/OrderPlan.php new file mode 100644 index 0000000..b658139 --- /dev/null +++ b/source/application/api/model/recharge/OrderPlan.php @@ -0,0 +1,32 @@ +save([ + 'order_id' => $orderId, + 'plan_id' => $data['plan_id'], + 'plan_name' => $data['plan_name'], + 'money' => $data['money'], + 'gift_money' => $data['gift_money'], + 'wxapp_id' => self::$wxapp_id + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/model/recharge/Plan.php b/source/application/api/model/recharge/Plan.php new file mode 100644 index 0000000..9738dfd --- /dev/null +++ b/source/application/api/model/recharge/Plan.php @@ -0,0 +1,73 @@ +where('is_delete', '=', 0) + ->order(['sort' => 'asc', 'money' => 'desc', 'create_time' => 'desc']) + ->select(); + } + + /** + * 根据自定义充值金额匹配满足的套餐 + * @param $payPrice + * @return array|false|\PDOStatement|string|\think\Model + */ + public function getMatchPlan($payPrice) + { + return (new static)->where('money', '<=', $payPrice) + ->where('is_delete', '=', 0) + ->order(['money' => 'desc']) + ->find(); + } + +} \ No newline at end of file diff --git a/source/application/api/model/sharing/Active.php b/source/application/api/model/sharing/Active.php new file mode 100644 index 0000000..f6681e6 --- /dev/null +++ b/source/application/api/model/sharing/Active.php @@ -0,0 +1,50 @@ +save($data); + } + + /** + * 根据商品id获取进行中的拼单列表 + * @param $goods_id + * @param int $limit + * @return false|\PDOStatement|string|\think\Collection + */ + public static function getActivityListByGoods($goods_id, $limit = 15) + { + return (new static)->with(['user']) + ->where('goods_id', '=', $goods_id) + ->where('status', '=', 10) + ->limit($limit) + ->select(); + } + +} diff --git a/source/application/api/model/sharing/ActiveUsers.php b/source/application/api/model/sharing/ActiveUsers.php new file mode 100644 index 0000000..a541d34 --- /dev/null +++ b/source/application/api/model/sharing/ActiveUsers.php @@ -0,0 +1,23 @@ +belongsTo("app\\{$module}\\model\\User") + ->field(['user_id', 'nickName', 'avatarUrl']); + } + + /** + * 获取指定商品评价列表 + * @param $goods_id + * @param int $scoreType + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getGoodsCommentList($goods_id, $scoreType = -1) + { + // 筛选条件 + $filter = [ + 'goods_id' => $goods_id, + 'is_delete' => 0, + 'status' => 1, + ]; + // 评分 + $scoreType > 0 && $filter['score'] = $scoreType; + return $this->with(['user', 'OrderGoods', 'image.file']) + ->where($filter) + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + + /** + * 获取指定评分总数 + * @param $goods_id + * @return array|false|\PDOStatement|string|\think\Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getTotal($goods_id) + { + return $this->field([ + 'count(comment_id) AS `all`', + 'count(score = 10 OR NULL) AS `praise`', + 'count(score = 20 OR NULL) AS `review`', + 'count(score = 30 OR NULL) AS `negative`', + ])->where([ + 'goods_id' => $goods_id, + 'is_delete' => 0, + 'status' => 1 + ])->find(); + } + + /** + * 验证订单是否允许评价 + * @param Order $order + * @return boolean + */ + public function checkOrderAllowComment($order) + { + // 验证订单是否已完成 + if ($order['order_status']['value'] != 30) { + $this->error = '该订单未完成,无法评价'; + return false; + } + // 验证订单是否已评价 + if ($order['is_comment'] == 1) { + $this->error = '该订单已完成评价'; + return false; + } + return true; + } + + /** + * 根据已完成订单商品 添加评价 + * @param Order $order + * @param \think\Collection|OrderGoods $goodsList + * @param $formJsonData + * @return boolean + * @throws \Exception + */ + public function addForOrder($order, $goodsList, $formJsonData) + { + // 生成 formData + $formData = $this->formatFormData($formJsonData); + // 生成评价数据 + $data = $this->createCommentData($order['user_id'], $order['order_id'], $goodsList, $formData); + if (empty($data)) { + $this->error = '没有输入评价内容'; + return false; + } + return $this->transaction(function () use ($order, $goodsList, $formData, $data) { + // 记录评价内容 + $result = $this->isUpdate(false)->saveAll($data); + // 记录评价图片 + $this->saveAllImages($result, $formData); + // 更新订单评价状态 + $isComment = count($goodsList) === count($data); + $this->updateOrderIsComment($order, $isComment, $result); + return true; + }); + } + + /** + * 更新订单评价状态 + * @param Order $order + * @param $isComment + * @param $commentList + * @return array|false + * @throws \Exception + */ + private function updateOrderIsComment($order, $isComment, &$commentList) + { + // 更新订单商品 + $orderGoodsData = []; + foreach ($commentList as $comment) { + $orderGoodsData[] = [ + 'order_goods_id' => $comment['order_goods_id'], + 'is_comment' => 1 + ]; + } + // 更新订单 + $isComment && $order->save(['is_comment' => 1]); + return (new OrderGoods)->saveAll($orderGoodsData); + } + + /** + * 生成评价数据 + * @param $user_id + * @param $order_id + * @param $goodsList + * @param $formData + * @return array + * @throws BaseException + */ + private function createCommentData($user_id, $order_id, &$goodsList, &$formData) + { + $data = []; + foreach ($goodsList as $goods) { + if (!isset($formData[$goods['order_goods_id']])) { + throw new BaseException(['msg' => '提交的数据不合法']); + } + $item = $formData[$goods['order_goods_id']]; + !empty($item['content']) && $data[$goods['order_goods_id']] = [ + 'score' => $item['score'], + 'content' => $item['content'], + 'is_picture' => !empty($item['uploaded']), + 'sort' => 100, + 'status' => 1, + 'user_id' => $user_id, + 'order_id' => $order_id, + 'goods_id' => $item['goods_id'], + 'order_goods_id' => $item['order_goods_id'], + 'wxapp_id' => self::$wxapp_id + ]; + } + return $data; + } + + /** + * 格式化 formData + * @param string $formJsonData + * @return array + */ + private function formatFormData($formJsonData) + { + return array_column(json_decode($formJsonData, true), null, 'order_goods_id'); + } + + /** + * 记录评价图片 + * @param $commentList + * @param $formData + * @return bool + * @throws \Exception + */ + private function saveAllImages(&$commentList, &$formData) + { + // 生成评价图片数据 + $imageData = []; + foreach ($commentList as $comment) { + $item = $formData[$comment['order_goods_id']]; + foreach ($item['uploaded'] as $imageId) { + $imageData[] = [ + 'comment_id' => $comment['comment_id'], + 'image_id' => $imageId, + 'wxapp_id' => self::$wxapp_id + ]; + } + } + $model = new CommentImage; + return !empty($imageData) && $model->saveAll($imageData); + } + +} diff --git a/source/application/api/model/sharing/CommentImage.php b/source/application/api/model/sharing/CommentImage.php new file mode 100644 index 0000000..4d88b64 --- /dev/null +++ b/source/application/api/model/sharing/CommentImage.php @@ -0,0 +1,23 @@ +hidden(['category', 'content', 'image', 'sku']); + // 整理列表数据并返回 + return $this->setGoodsListDataFromApi($data, true, ['userInfo' => $userInfo]); + } + + /** + * 获取商品详情信息 + * @param int $goodsId 商品id + * @param array|bool $userInfo 用户信息 + * @return array|false|\PDOStatement|string|\think\Model|static + * @throws BaseException + */ + public function getDetails($goodsId, $userInfo = false) + { + // 获取商品详情 + $model = new static; + $goods = $model->with([ + 'category', + 'image' => ['file'], + 'sku' => ['image'], + 'spec_rel' => ['spec'], + 'delivery' => ['rule'], + 'commentData' => function ($query) { + $query->with('user')->where(['is_delete' => 0, 'status' => 1])->limit(2); + } + ])->withCount(['commentData' => function ($query) { + $query->where(['is_delete' => 0, 'status' => 1]); + }]) + ->where('goods_id', '=', $goodsId) + ->find(); + // 判断商品的状态 + if (!$goods || $goods['is_delete'] || $goods['goods_status']['value'] != 10) { + throw new BaseException(['msg' => '很抱歉,商品信息不存在或已下架']); + } + // 设置商品展示的数据 + $goods = $model->setGoodsListDataFromApi($goods, false, ['userInfo' => $userInfo]); + // 多规格商品sku信息 + $goods['goods_multi_spec'] = $goods['spec_type'] == 20 ? $model->getManySpecData($goods['spec_rel'], $goods['sku']) : null; + return $goods; + } + + /** + * 根据商品id集获取商品列表 + * @param $goodsIds + * @param bool $userInfo + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIdsFromApi($goodsIds, $userInfo = false) + { + // 获取商品列表 + $data = parent::getListByIds($goodsIds, 10); + // 整理列表数据并返回 + return $this->setGoodsListDataFromApi($data, true, ['userInfo' => $userInfo]); + } + + + /** + * 设置商品展示的数据 api模块 + * @param $data + * @param bool $isMultiple + * @param array $param + * @return mixed + */ + private function setGoodsListDataFromApi(&$data, $isMultiple, $param) + { + return parent::setGoodsListData($data, $isMultiple, function ($goods) use ($param) { + // 计算并设置商品会员价 + $this->setGoodsGradeMoney($param['userInfo'], $goods); + }); + } + + /** + * 设置商品的会员价 + * @param $user + * @param $goods + */ + private function setGoodsGradeMoney($user, &$goods) + { + // 会员等级状态 + $gradeStatus = (!empty($user) && $user['grade_id'] > 0 && !empty($user['grade'])) + && (!$user['grade']['is_delete'] && $user['grade']['status']); + // 判断商品是否参与会员折扣 + if (!$gradeStatus || !$goods['is_enable_grade']) { + $goods['is_user_grade'] = false; + return; + } + // 商品单独设置了会员折扣 + if ($goods['is_alone_grade'] && isset($goods['alone_grade_equity'][$user['grade_id']])) { + // 折扣比例 + $discountRatio = helper::bcdiv($goods['alone_grade_equity'][$user['grade_id']], 10); + } else { + // 折扣比例 + $discountRatio = helper::bcdiv($user['grade']['equity']['discount'], 10); + } + if ($discountRatio > 0) { + // 标记参与会员折扣 + $goods['is_user_grade'] = true; + // 会员折扣价 + foreach ($goods['sku'] as &$skuItem) { + $skuItem['goods_price'] = helper::number2(helper::bcmul($skuItem['goods_price'], $discountRatio), true); + $skuItem['sharing_price'] = helper::number2(helper::bcmul($skuItem['sharing_price'], $discountRatio), true); + } + } + } + +} diff --git a/source/application/api/model/sharing/GoodsImage.php b/source/application/api/model/sharing/GoodsImage.php new file mode 100644 index 0000000..1adb416 --- /dev/null +++ b/source/application/api/model/sharing/GoodsImage.php @@ -0,0 +1,23 @@ + 0, // 参与的拼单id + 'delivery' => null, // 配送方式 + 'shop_id' => 0, // 自提门店id + 'linkman' => '', // 自提联系人 + 'phone' => '', // 自提联系电话 + 'coupon_id' => 0, // 优惠券id + 'remark' => '', // 买家留言 + 'pay_type' => PayTypeEnum::WECHAT, // 支付方式 + ]; + + /** + * 待支付订单详情 + * @param $orderNo + * @return null|static + * @throws \think\exception\DbException + */ + public static function getPayDetail($orderNo) + { + return self::get(['order_no' => $orderNo, 'pay_status' => 10, 'is_delete' => 0], ['goods', 'user']); + } + + /** + * 获取订单商品列表 + * @param $params + * @return array + */ + public function getOrderGoodsListByNow($params) + { + // 商品详情 + /* @var GoodsModel $goods */ + $goods = GoodsModel::detail($params['goods_id']); + // 商品sku信息 + $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $params['goods_sku_id']); + // 商品列表 + $goodsList = [$goods->hidden(['category', 'content', 'image', 'sku'])]; + foreach ($goodsList as &$item) { + // 商品单价(根据order_type判断单买还是拼单) + // order_type:下单类型 10 => 单独购买,20 => 拼团 + $item['goods_price'] = $params['order_type'] == 10 ? $item['goods_sku']['goods_price'] + : $item['goods_sku']['sharing_price']; + // 商品购买数量 + $item['total_num'] = $params['goods_num']; + $item['spec_sku_id'] = $item['goods_sku']['spec_sku_id']; + // 商品购买总金额 + $item['total_price'] = helper::bcmul($item['goods_price'], $params['goods_num']); + } + return $goodsList; + } + + /** + * 订单支付事件 + * @param int $payType + * @return bool + * @throws \think\exception\DbException + */ + public function onPay($payType = PayTypeEnum::WECHAT) + { + // 判断商品状态、库存 + if (!$this->checkGoodsStatusFromOrder($this['goods'])) { + return false; + } + // 余额支付 + if ($payType == PayTypeEnum::BALANCE) { + return $this->onPaymentByBalance($this['order_no']); + } + return true; + } + + /** + * 构建支付请求的参数 + * @param $user + * @param $order + * @param $payType + * @return array + * @throws BaseException + * @throws \think\exception\DbException + */ + public function onOrderPayment($user, $order, $payType) + { + if ($payType == PayTypeEnum::WECHAT) { + return $this->onPaymentByWechat($user, $order); + } + return []; + } + + /** + * 构建微信支付请求 + * @param $user + * @param $order + * @return array + * @throws BaseException + * @throws \think\exception\DbException + */ + private function onPaymentByWechat($user, $order) + { + return PaymentService::wechat( + $user, + $order['order_id'], + $order['order_no'], + $order['pay_price'], + OrderTypeEnum::SHARING + ); + } + + /** + * 余额支付标记订单已支付 + * @param $orderNo + * @return bool + * @throws \think\exception\DbException + */ + public function onPaymentByBalance($orderNo) + { + // 获取订单详情 + $PaySuccess = new PaySuccess($orderNo); + // 发起余额支付 + $status = $PaySuccess->onPaySuccess(PayTypeEnum::BALANCE); + if (!$status) { + $this->error = $PaySuccess->getError(); + } + return $status; + } + + /** + * 验证拼单是否允许加入 + * @param $active_id + * @return bool + * @throws BaseException + * @throws \think\exception\DbException + */ + public function checkActiveIsAllowJoin($active_id) + { + // 拼单详情 + $detail = Active::detail($active_id); + if (!$detail) { + throw new BaseException('很抱歉,拼单不存在'); + } + // 验证当前拼单是否允许加入新成员 + return $detail->checkAllowJoin(); + } + + /** + * 保存上门自提联系人 + * @param $linkman + * @param $phone + * @return false|\think\Model + */ + public function saveOrderExtract($linkman, $phone) + { + // 记忆上门自提联系人(缓存),用于下次自动填写 + UserService::setLastExtract($this['user_id'], trim($linkman), trim($phone)); + // 保存上门自提联系人(数据库) + return $this->extract()->save([ + 'linkman' => trim($linkman), + 'phone' => trim($phone), + 'user_id' => $this['user_id'], + 'wxapp_id' => self::$wxapp_id, + ]); + } + + /** + * 用户拼团订单列表 + * @param $user_id + * @param string $type + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList($user_id, $type = 'all') + { + // 筛选条件 + $filter = []; + // 订单数据类型 + switch ($type) { + case 'all': + // 全部 + break; + case 'payment'; + // 待支付 + $filter['pay_status'] = PayStatusEnum::PENDING; + break; + case 'sharing'; + // 拼团中 + $filter['active.status'] = 10; + break; + case 'delivery'; + // 待发货 + $this->where('IF ( (`order`.`order_type` = 20), (`active`.`status` = 20), TRUE)'); + $filter['pay_status'] = 20; + $filter['delivery_status'] = 10; + break; + case 'received'; + // 待收货 + $filter['pay_status'] = 20; + $filter['delivery_status'] = 20; + $filter['receipt_status'] = 10; + break; + case 'comment'; + $filter['order_status'] = 30; + $filter['is_comment'] = 0; + break; + } + return $this->with(['goods.image', 'active']) + ->alias('order') + ->field('order.*, active.status as active_status') + ->join('sharing_active active', 'order.active_id = active.active_id', 'LEFT') + ->where('user_id', '=', $user_id) + ->where($filter) + ->where('order.is_delete', '=', 0) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 取消订单 + * @param UserModel $user + * @return bool|false|int + */ + public function cancel($user) + { + // 订单是否已支付 + $isPay = $this['pay_status']['value'] == PayStatusEnum::SUCCESS; + // 已发货订单不可取消 + if ($this['delivery_status']['value'] == 20) { + $this->error = '已发货订单不可取消'; + return false; + } + // 已付款的拼团订单不允许取消 + if ($isPay && $this['order_type']['value'] == 20) { + $this->error = '已付款的拼团订单不允许取消'; + return false; + } + // 订单取消事件 + $this->transaction(function () use ($user, $isPay) { + // 未付款的订单 + if ($isPay == false) { + // 回退商品库存 + (new OrderGoodsModel)->backGoodsStock($this['goods'], $isPay); + // 回退用户优惠券 + $this['coupon_id'] > 0 && UserCouponModel::setIsUse($this['coupon_id'], false); + // 回退用户积分 + $describe = "订单取消:{$this['order_no']}"; + $this['points_num'] > 0 && $user->setIncPoints($this['points_num'], $describe); + } + // 更新订单状态 + return $this->save(['order_status' => $isPay ? OrderStatusEnum::APPLY_CANCEL : OrderStatusEnum::CANCELLED]); + }); + return true; + } + + /** + * 确认收货 + * @return bool|mixed + */ + public function receipt() + { + // 验证订单是否合法 + // 条件1: 订单必须已发货 + // 条件2: 订单必须未收货 + if ($this['delivery_status']['value'] != 20 || $this['receipt_status']['value'] != 10) { + $this->error = '该订单不合法'; + return false; + } + return $this->transaction(function () { + // 更新订单状态 + $status = $this->save([ + 'receipt_status' => 20, + 'receipt_time' => time(), + 'order_status' => 30 + ]); + // 执行订单完成后的操作 + $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::SHARING); + $OrderCompleteService->complete([$this], static::$wxapp_id); + return $status; + }); + } + + /** + * 获取订单总数 + * @param $user_id + * @param string $type + * @return int|string + * @throws \think\Exception + */ + public function getCount($user_id, $type = 'all') + { + // 筛选条件 + $filter = []; + // 订单数据类型 + switch ($type) { + case 'all': + break; + case 'payment'; + $filter['pay_status'] = PayStatusEnum::PENDING; + break; + case 'received'; + $filter['pay_status'] = PayStatusEnum::SUCCESS; + $filter['delivery_status'] = 20; + $filter['receipt_status'] = 10; + break; + case 'comment'; + $filter['order_status'] = 30; + $filter['is_comment'] = 0; + break; + } + return $this->where('user_id', '=', $user_id) + ->where('order_status', '<>', 20) + ->where($filter) + ->where('is_delete', '=', 0) + ->count(); + } + + /** + * 订单详情 + * @param $order_id + * @param $user_id + * @return array|false|\PDOStatement|string|\think\Model|static + * @throws BaseException + */ + public static function getUserOrderDetail($order_id, $user_id) + { + $order = (new static)->with(['goods' => ['image', 'refund'], 'address', 'express', 'extract_shop']) + ->alias('order') + ->field('order.*, active.status as active_status') + ->join('sharing_active active', 'order.active_id = active.active_id', 'LEFT') + ->where([ + 'order_id' => $order_id, + 'user_id' => $user_id, + ])->find(); + if (!$order) { + throw new BaseException(['msg' => '订单不存在']); + } + return $order; + } + + /** + * 判断商品库存不足 (未付款订单) + * @param $goodsList + * @return bool + * @throws \think\exception\DbException + */ + private function checkGoodsStatusFromOrder($goodsList) + { + foreach ($goodsList as $goods) { + // 判断商品是否下架 + if ( + empty($goods['goods']) + || $goods['goods']['goods_status']['value'] != 10 + ) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 已下架"; + return false; + } + // 获取商品的sku信息 + $goodsSku = GoodsSkuModel::detail($goods['goods_id'], $goods['spec_sku_id']); + // sku已不存在 + if (empty($goodsSku)) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] sku已不存在,请重新下单"; + return false; + } + // 付款减库存 + if ($goods['deduct_stock_type'] == 20 && $goods['total_num'] > $goodsSku['stock_num']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 库存不足"; + return false; + } + } + return true; + } + + /** + * 当前订单是否允许申请售后 + * @return bool + */ + public function isAllowRefund() + { + // 必须是已发货的订单 + if ($this['delivery_status']['value'] != 20) { + return false; + } + // 允许申请售后期限(天) + $refundDays = SettingModel::getItem('trade')['order']['refund_days']; + // 不允许售后 + if ($refundDays == 0) { + return false; + } + // 当前时间超出允许申请售后期限 + if ( + $this['receipt_status'] == 20 + && time() > ($this['receipt_time'] + ((int)$refundDays * 86400)) + ) { + return false; + } + return true; + } + + /** + * 判断当前订单是否允许核销 + * @param static $order + * @return bool + */ + public function checkExtractOrder(&$order) + { + if ( + $order['pay_status']['value'] == PayStatusEnum::SUCCESS + && $order['delivery_type']['value'] == DeliveryTypeEnum::EXTRACT + && $order['delivery_status']['value'] == 10 + // 拼团订单验证拼单状态 + && ($order['order_type']['value'] == 20 ? $order['active']['status']['value'] == 20 : true) + ) { + return true; + } + $this->setError('该订单不能被核销'); + return false; + } + + /** + * 设置错误信息 + * @param $error + */ + private function setError($error) + { + empty($this->error) && $this->error = $error; + } + + /** + * 是否存在错误 + * @return bool + */ + public function hasError() + { + return !empty($this->error); + } + +} diff --git a/source/application/api/model/sharing/OrderAddress.php b/source/application/api/model/sharing/OrderAddress.php new file mode 100644 index 0000000..5897a46 --- /dev/null +++ b/source/application/api/model/sharing/OrderAddress.php @@ -0,0 +1,23 @@ + $order_id, 'is_comment' => 0], ['orderM', 'image']); + } + +} diff --git a/source/application/api/model/sharing/OrderRefund.php b/source/application/api/model/sharing/OrderRefund.php new file mode 100644 index 0000000..8658430 --- /dev/null +++ b/source/application/api/model/sharing/OrderRefund.php @@ -0,0 +1,171 @@ + '已同意退货并已退款', 20 => '已同意换货']; + return $text[$data['type']]; + } + // 已取消 + if ($data['status'] == 30) { + return '已取消'; + } + // 已拒绝 + if ($data['status'] == 10) { +// return '已拒绝'; + return $data['type'] == 10 ? '已拒绝退货退款' : '已拒绝换货'; + } + // 进行中 + if ($data['status'] == 0) { + if ($data['is_agree'] == 0) { + return '等待审核中'; + } + if ($data['type'] == 10) { + return $data['is_user_send'] ? '已发货,待平台确认' : '已同意退货,请及时发货'; + } + } + return $value; + } + + /** + * 获取用户售后单列表 + * @param $user_id + * @param int $state + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList($user_id, $state = -1) + { + $state > -1 && $this->where('status', '=', $state); + return $this->with(['order_goods.image']) + ->where('user_id', '=', $user_id) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 用户发货 + * @param $data + * @return false|int + */ + public function delivery($data) + { + if ( + $this['type']['value'] != 10 + || $this['is_agree']['value'] != 10 + || $this['is_user_send'] != 0 + ) { + $this->error = '当前售后单不合法,不允许该操作'; + return false; + } + if ($data['express_id'] <= 0) { + $this->error = '请选择物流公司'; + return false; + } + if (empty($data['express_no'])) { + $this->error = '请填写物流单号'; + return false; + } + return $this->save([ + 'is_user_send' => 1, + 'send_time' => time(), + 'express_id' => (int)$data['express_id'], + 'express_no' => $data['express_no'], + ]); + } + + /** + * 新增售后单记录 + * @param $user + * @param $goods + * @param $data + * @return bool + * @throws \Exception + */ + public function apply($user, $goods, $data) + { + $this->startTrans(); + try { + // 新增售后单记录 + $this->save([ + 'order_goods_id' => $data['order_goods_id'], + 'order_id' => $goods['order_id'], + 'user_id' => $user['user_id'], + 'type' => $data['type'], + 'apply_desc' => $data['content'], + 'is_agree' => 0, + 'status' => 0, + 'wxapp_id' => self::$wxapp_id, + ]); + // 记录凭证图片关系 + if (isset($data['images']) && !empty($data['images'])) { + $this->saveImages($this['order_refund_id'], $data['images']); + } + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 记录售后单图片 + * @param $order_refund_id + * @param $images + * @return bool + * @throws \Exception + */ + private function saveImages($order_refund_id, $images) + { + // 生成评价图片数据 + $data = []; + foreach (explode(',', $images) as $image_id) { + $data[] = [ + 'order_refund_id' => $order_refund_id, + 'image_id' => $image_id, + 'wxapp_id' => self::$wxapp_id + ]; + } + return !empty($data) && (new OrderRefundImage)->saveAll($data); + } + +} \ No newline at end of file diff --git a/source/application/api/model/sharing/OrderRefundAddress.php b/source/application/api/model/sharing/OrderRefundAddress.php new file mode 100644 index 0000000..f52b961 --- /dev/null +++ b/source/application/api/model/sharing/OrderRefundAddress.php @@ -0,0 +1,23 @@ +getActiveByDate($todayTime, '='); + } + + /** + * 获取当天的活动 + * @return array|false|\PDOStatement|string|\think\Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getNextActive() + { + $todayTime = strtotime(date('Y-m-d')); + return $this->getActiveByDate($todayTime, '>'); + } + + /** + * 根据日期获取活动 + * @param $date + * @param string $op + * @return array|false|\PDOStatement|string|\think\Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getActiveByDate($date, $op = '=') + { + return $this->where('active_date', $op, $date) + ->where('status', '=', 1) + ->where('is_delete', '=', 0) + ->find(); + } + +} \ No newline at end of file diff --git a/source/application/api/model/sharp/ActiveGoods.php b/source/application/api/model/sharp/ActiveGoods.php new file mode 100644 index 0000000..dade77e --- /dev/null +++ b/source/application/api/model/sharp/ActiveGoods.php @@ -0,0 +1,115 @@ +with(['active', 'activeTime']) + ->where('active_time_id', '=', $activeTimeId) + ->where('sharp_goods_id', '=', $sharpGoodsId) + ->find(); + } + + /** + * 获取活动商品详情 + * @param $active + * @param $sharpGoodsId + * @param $isCheckStatus + * @return GoodsModel|bool|\think\model\Collection + * @throws \think\exception\DbException + */ + public function getGoodsActiveDetail($active, $sharpGoodsId, $isCheckStatus = true) + { + // 获取商品详情 + $goods = $this->getGoodsDetail($sharpGoodsId); + if (empty($goods)) return false; + if ($isCheckStatus == true && ($goods['is_delete'] || !$goods['status'])) { + $this->error = '很抱歉,秒杀商品不存在或已下架'; + return false; + } + // 活动商品的销量 + $goods['sales_actual'] = $active['sales_actual']; + // 商品销售进度 + $goods['progress'] = $this->getProgress($active['sales_actual'], $goods['seckill_stock']); + /* @var $goods \think\model\Collection */ + return $goods; + } + + /** + * 获取商品详情 + * @param $sharpGoodsId + * @return GoodsModel|bool + * @throws \think\exception\DbException + */ + private function getGoodsDetail($sharpGoodsId) + { + // 获取秒杀商品详情 + $model = $this->getGoodsModel(); + $sharpGoods = $model::detail($sharpGoodsId, ['sku']); + if (empty($sharpGoods)) { + $this->error = '秒杀商品信息不存在'; + return false; + } + // 获取主商品详情 + $goods = GoodsModel::detail($sharpGoods['goods_id']); + if (empty($goods)) return false; + // 整理商品信息 + $goods['sharp_goods_id'] = $sharpGoods['sharp_goods_id']; + $goods['deduct_stock_type'] = $sharpGoods['deduct_stock_type']; + $goods['limit_num'] = $sharpGoods['limit_num']; + $goods['seckill_stock'] = $sharpGoods['seckill_stock']; + $goods['total_sales'] = $sharpGoods['total_sales']; + $goods['status'] = $sharpGoods['status']; + $goods['is_delete'] = $sharpGoods['is_delete']; + // 商品sku信息 + $goods['sku'] = $this->getSharpSku($sharpGoods['sku'], $goods['sku']); + /* @var \think\Collection $goods */ + return $goods->hidden(['category', 'sku']); + } + + /** + * 获取秒杀商品的sku信息 + * @param $sharpSku + * @param $goodsSku + * @return array + */ + protected function getSharpSku($sharpSku, $goodsSku) + { + $sharpSku = helper::arrayColumn2Key($sharpSku, 'spec_sku_id'); + foreach ($goodsSku as &$item) { + $sharpSkuItem = clone $sharpSku[$item['spec_sku_id']]; + $item['original_price'] = $item['goods_price']; + $item['seckill_price'] = $sharpSkuItem['seckill_price']; + $item['seckill_stock'] = $sharpSkuItem['seckill_stock']; + } + return $goodsSku; + } + +} \ No newline at end of file diff --git a/source/application/api/model/sharp/ActiveTime.php b/source/application/api/model/sharp/ActiveTime.php new file mode 100644 index 0000000..3985d42 --- /dev/null +++ b/source/application/api/model/sharp/ActiveTime.php @@ -0,0 +1,77 @@ +where('active_id', '=', $activeId) + ->where('active_time', '=', $nowTime) + ->where('status', '=', 1) + ->find(); + } + + /** + * 获取下一场活动场次 + * @param $activeId + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getNextActiveTimes($activeId) + { + // 当前的时间点 + $nowTime = date('H'); + return $this->where('active_id', '=', $activeId) + ->where('active_time', '>', $nowTime) + ->where('status', '=', 1) + ->order(['active_time' => 'asc']) + ->select(); + } + + /** + * 获取指定日期最近的活动场次 + * @param $activeId + * @return array|false|\PDOStatement|string|\think\Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getRecentActiveTime($activeId) + { + return $this->where('active_id', '=', $activeId) + ->where('status', '=', 1) + ->order(['active_time' => 'asc']) + ->find(); + } + +} \ No newline at end of file diff --git a/source/application/api/model/sharp/Goods.php b/source/application/api/model/sharp/Goods.php new file mode 100644 index 0000000..4894fe8 --- /dev/null +++ b/source/application/api/model/sharp/Goods.php @@ -0,0 +1,25 @@ +where('is_check', '=', $is_check); + // 获取数量 + $limit != false && $this->limit($limit); + // 获取门店列表数据 + $data = $this->where('is_delete', '=', '0') + ->where('status', '=', '1') + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->select(); + // 根据距离排序 + if (!empty($longitude) && !empty($latitude)) { + return $this->sortByDistance($data, $longitude, $latitude); + } + return $data; + } + + /** + * 根据距离排序 + * @param string $longitude + * @param string $latitude + * @param \think\Collection|false|\PDOStatement|string $data + * @return array + * @throws + */ + private function sortByDistance(&$data, $longitude, $latitude) + { + // 根据距离排序 + $list = $data->isEmpty() ? [] : $data->toArray(); + $sortArr = []; + foreach ($list as &$shop) { + // 计算距离 + $distance = self::getDistance($longitude, $latitude, $shop['longitude'], $shop['latitude']); + // 排序列 + $sortArr[] = $distance; + $shop['distance'] = $distance; + if ($distance >= 1000) { + $distance = bcdiv($distance, 1000, 2); + $shop['distance_unit'] = $distance . 'km'; + } else + $shop['distance_unit'] = $distance . 'm'; + } + // 根据距离排序 + array_multisort($sortArr, SORT_ASC, $list); + return $list; + } + + /** + * 获取两个坐标点的距离 + * @param $ulon + * @param $ulat + * @param $slon + * @param $slat + * @return float + */ + private static function getDistance($ulon, $ulat, $slon, $slat) + { + // 地球半径 + $R = 6378137; + // 将角度转为狐度 + $radLat1 = deg2rad($ulat); + $radLat2 = deg2rad($slat); + $radLng1 = deg2rad($ulon); + $radLng2 = deg2rad($slon); + // 结果 + $s = acos(cos($radLat1) * cos($radLat2) * cos($radLng1 - $radLng2) + sin($radLat1) * sin($radLat2)) * $R; + // 精度 + $s = round($s * 10000) / 10000; + return round($s); + } + + /** + * 根据门店id集获取门店列表 + * @param $shopIds + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($shopIds) + { + // 筛选条件 + $filter = ['shop_id' => ['in', $shopIds]]; + if (!empty($shopIds)) { + $this->orderRaw('field(shop_id, ' . implode(',', $shopIds) . ')'); + } + // 获取商品列表数据 + return $this->with(['logo']) + ->where('is_delete', '=', '0') + ->where('status', '=', '1') + ->where($filter) + ->select(); + } + +} \ No newline at end of file diff --git a/source/application/api/model/store/shop/Clerk.php b/source/application/api/model/store/shop/Clerk.php new file mode 100644 index 0000000..b2721fe --- /dev/null +++ b/source/application/api/model/store/shop/Clerk.php @@ -0,0 +1,65 @@ + '未找到店员信息']); + } + return $model; + } + + /** + * 验证用户是否为核销员 + * @param $shop_id + * @return bool + */ + public function checkUser($shop_id) + { + if ($this['is_delete']) { + $this->error = '未找到店员信息'; + return false; + } + if ($this['shop_id'] != $shop_id) { + $this->error = '当前店员不属于该门店,没有核销权限'; + return false; + } + if (!$this['status']) { + $this->error = '当前店员状态已被禁用'; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/model/store/shop/ClerkRel.php b/source/application/api/model/store/shop/ClerkRel.php new file mode 100644 index 0000000..c8539a3 --- /dev/null +++ b/source/application/api/model/store/shop/ClerkRel.php @@ -0,0 +1,15 @@ +where('user_id', '=', $userId) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/model/user/Grade.php b/source/application/api/model/user/Grade.php new file mode 100644 index 0000000..bbe247f --- /dev/null +++ b/source/application/api/model/user/Grade.php @@ -0,0 +1,15 @@ +where('user_id', '=', $userId) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/api/model/wow/Order.php b/source/application/api/model/wow/Order.php new file mode 100644 index 0000000..987b8c6 --- /dev/null +++ b/source/application/api/model/wow/Order.php @@ -0,0 +1,15 @@ +save([ + 'user_id' => $user_id, + 'form_id' => $form_id, + 'expiry_time' => time() + (7 * 86400) - 10, + 'wxapp_id' => self::$wxapp_id + ]); + } +} \ No newline at end of file diff --git a/source/application/api/service/Basics.php b/source/application/api/service/Basics.php new file mode 100644 index 0000000..e4691cc --- /dev/null +++ b/source/application/api/service/Basics.php @@ -0,0 +1,8 @@ +unifiedorder($orderNo, $user['open_id'], $payPrice, $orderType); + // 记录prepay_id + $model = new WxappPrepayIdModel; + $model->add($payment['prepay_id'], $orderId, $user['user_id'], $orderType); + return $payment; + } + +} \ No newline at end of file diff --git a/source/application/api/service/User.php b/source/application/api/service/User.php new file mode 100644 index 0000000..ec6b0f5 --- /dev/null +++ b/source/application/api/service/User.php @@ -0,0 +1,36 @@ + '', 'phone' => '']; + } + +} \ No newline at end of file diff --git a/source/application/api/service/bargain/Amount.php b/source/application/api/service/bargain/Amount.php new file mode 100644 index 0000000..ad9f25f --- /dev/null +++ b/source/application/api/service/bargain/Amount.php @@ -0,0 +1,147 @@ +amount = $amount; + $this->num = $num; + $this->coupon_min = $coupon_min; + } + + /** + * 处理返回 + * @return array + * @throws \Exception + */ + public function handle() + { + // A. 验证 + if ($this->amount < $validAmount = $this->coupon_min * $this->num) { + throw new \Exception('砍价总金额必须≥' . $validAmount . '元'); + } + // B. 分配砍价 + $this->apportion(); + return [ + 'items' => $this->items, + ]; + } + + /** + * 分配砍价 + */ + protected function apportion() + { + $num = $this->num; // 剩余可分配的砍价个数 + $amount = $this->amount; //剩余可领取的砍价金额 + while ($num >= 1) { + // 剩余一个的时候,直接取剩余砍价 + if ($num == 1) { + $coupon_amount = $this->decimal_number($amount); + } else { + $avg_amount = $this->decimal_number($amount / $num); // 剩余的砍价的平均金额 + $coupon_amount = $this->decimal_number( + $this->calcCouponAmount($avg_amount, $amount, $num) + ); + } + $this->items[] = $coupon_amount; // 追加分配 + $amount -= $coupon_amount; + --$num; + } + shuffle($this->items); // 随机打乱 + } + + /** + * 计算分配的砍价金额 + * @param float $avg_amount 每次计算的平均金额 + * @param float $amount 剩余可领取金额 + * @param int $num 剩余可领取的砍价个数 + * + * @return float + */ + protected function calcCouponAmount($avg_amount, $amount, $num) + { + // 如果平均金额小于等于最低金额,则直接返回最低金额 + if ($avg_amount <= $this->coupon_min) { + return $this->coupon_min; + } + // 浮动计算 + $coupon_amount = $this->decimal_number($avg_amount * (1 + $this->apportionRandRatio())); + // 如果低于最低金额或超过可领取的最大金额,则重新获取 + if ($coupon_amount < $this->coupon_min + || $coupon_amount > $this->calcCouponAmountMax($amount, $num) + ) { + return $this->calcCouponAmount($avg_amount, $amount, $num); + } + return $coupon_amount; + } + + /** + * 计算分配的砍价金额-可领取的最大金额 + * @param $amount + * @param $num + * @return float|int + */ + protected function calcCouponAmountMax($amount, $num) + { + return $this->coupon_min + $amount - $num * $this->coupon_min; + } + + /** + * 砍价金额浮动比例 + */ + protected function apportionRandRatio() + { + // 60%机率获取剩余平均值的大幅度砍价(可能正数、可能负数) + if (rand(1, 100) <= 60) { + return rand(-70, 70) / 100; // 上下幅度70% + } + return rand(-30, 30) / 100; // 其他情况,上下浮动30%; + } + + /** + * 格式化金额,保留2位 + * @param float $amount + * @return float + */ + protected function decimal_number($amount) + { + return sprintf('%01.2f', round($amount, 2)); + } +} \ No newline at end of file diff --git a/source/application/api/service/bargain/order/PaySuccess.php b/source/application/api/service/bargain/order/PaySuccess.php new file mode 100644 index 0000000..262bf32 --- /dev/null +++ b/source/application/api/service/bargain/order/PaySuccess.php @@ -0,0 +1,45 @@ +error = '未找到砍价任务信息'; + return false; + } + // 标记为已购买 + $task->setIsBuy(); + // 砍价活动详情 + $active = ActiveModel::detail($task['active_id']); + if (empty($active)) { + $this->error = '未找到砍价活动信息'; + return false; + } + // 累计活动销量 + $active->setIncSales(); + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/service/coupon/GoodsDeduct.php b/source/application/api/service/coupon/GoodsDeduct.php new file mode 100644 index 0000000..f1f36b0 --- /dev/null +++ b/source/application/api/service/coupon/GoodsDeduct.php @@ -0,0 +1,90 @@ +setActualReducedMoney($reducedMoney, $orderTotalPrice); + // 实际抵扣金额为0, + if ($this->actualReducedMoney > 0) { + // 计算商品的价格权重 + $goodsList = $this->getGoodsListWeight($goodsList, $orderTotalPrice); + // 计算商品优惠券抵扣金额 + $this->setGoodsListCouponMoney($goodsList); + // 总抵扣金额 + $totalCouponMoney = helper::getArrayColumnSum($goodsList, 'coupon_money'); + $this->setGoodsListCouponMoneyFill($goodsList, $totalCouponMoney); + $this->setGoodsListCouponMoneyDiff($goodsList, $totalCouponMoney); + } + return $goodsList; + } + + public function getActualReducedMoney() + { + return $this->actualReducedMoney; + } + + private function setActualReducedMoney($reducedMoney, $orderTotalPrice) + { + $reducedMoney *= 100; + $this->actualReducedMoney = ($reducedMoney >= $orderTotalPrice) ? $orderTotalPrice - 1 : $reducedMoney; + } + + private function arraySortByWeight($goodsList) + { + return array_sort($goodsList, 'weight', true); + } + + private function getGoodsListWeight($goodsList, $orderTotalPrice) + { + foreach ($goodsList as &$goods) { + $goods['weight'] = $goods['total_price'] / $orderTotalPrice; + } + return $this->arraySortByWeight($goodsList); + } + + private function setGoodsListCouponMoney(&$goodsList) + { + foreach ($goodsList as &$goods) { + $goods['coupon_money'] = bcmul($this->actualReducedMoney, $goods['weight']); + } + return true; + } + + private function setGoodsListCouponMoneyFill(&$goodsList, $totalCouponMoney) + { + if ($totalCouponMoney === 0) { + $temReducedMoney = $this->actualReducedMoney; + foreach ($goodsList as &$goods) { + if ($temReducedMoney === 0) break; + $goods['coupon_money'] = 1; + $temReducedMoney--; + } + } + return true; + } + + private function setGoodsListCouponMoneyDiff(&$goodsList, $totalCouponMoney) + { + $tempDiff = $this->actualReducedMoney - $totalCouponMoney; + foreach ($goodsList as &$goods) { + if ($tempDiff < 1) break; + $goods['coupon_money']++ && $tempDiff--; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/service/master/order/PaySuccess.php b/source/application/api/service/master/order/PaySuccess.php new file mode 100644 index 0000000..5db35ac --- /dev/null +++ b/source/application/api/service/master/order/PaySuccess.php @@ -0,0 +1,43 @@ +becomeDealerUser($order); + return true; + } + + /** + * 购买指定商品成为分销商 + * @param $order + * @return bool + * @throws \think\exception\DbException + */ + private function becomeDealerUser($order) + { + // 整理商品id集 + $goodsIds = helper::getArrayColumn($order['goods'], 'goods_id'); + $model = new DealerApplyModel; + return $model->becomeDealerUser($order['user_id'], $goodsIds, $order['wxapp_id']); + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/Checkout.php b/source/application/api/service/order/Checkout.php new file mode 100644 index 0000000..16180e7 --- /dev/null +++ b/source/application/api/service/order/Checkout.php @@ -0,0 +1,856 @@ + null, // 配送方式 + 'shop_id' => 0, // 自提门店id + 'linkman' => '', // 自提联系人 + 'phone' => '', // 自提联系电话 + 'coupon_id' => 0, // 优惠券id + 'is_use_points' => 0, // 是否使用积分抵扣 + 'remark' => '', // 买家留言 + 'pay_type' => PayTypeEnum::WECHAT, // 支付方式 + ]; + + /** + * 订单结算的规则 + * @var array + */ + private $checkoutRule = [ + 'is_user_grade' => true, // 会员等级折扣 + 'is_coupon' => true, // 优惠券抵扣 + 'is_use_points' => true, // 是否使用积分抵扣 + 'is_dealer' => true, // 是否开启分销 + ]; + + /** + * 订单来源 + * @var array + */ + private $orderSource = [ + 'source' => OrderSourceEnum::MASTER, + 'source_id' => 0, + ]; + + /** + * 订单结算数据 + * @var array + */ + private $orderData = []; + + /** + * 构造函数 + * Checkout constructor. + */ + public function __construct() + { + $this->model = new OrderModel; + $this->wxapp_id = OrderModel::$wxapp_id; + } + + /** + * 设置结算台请求的参数 + * @param $param + * @return array + */ + public function setParam($param) + { + $this->param = array_merge($this->param, $param); + return $this->getParam(); + } + + /** + * 获取结算台请求的参数 + * @return array + */ + public function getParam() + { + return $this->param; + } + + /** + * 订单结算的规则 + * @param $data + * @return $this + */ + public function setCheckoutRule($data) + { + $this->checkoutRule = array_merge($this->checkoutRule, $data); + return $this; + } + + /** + * 设置订单来源(普通订单、砍价订单、秒杀订单) + * @param $data + * @return $this + */ + public function setOrderSource($data) + { + $this->orderSource = array_merge($this->orderSource, $data); + return $this; + } + + /** + * 订单确认-结算台 + * @param $user + * @param $goodsList + * @return array + * @throws BaseException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function onCheckout($user, $goodsList) + { + $this->user = $user; + $this->goodsList = $goodsList; + // 订单确认-立即购买 + return $this->checkout(); + } + + /** + * 订单结算台 + * @return array + * @throws BaseException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function checkout() + { + // 整理订单数据 + $this->orderData = $this->getOrderData(); + // 验证商品状态, 是否允许购买 + $this->validateGoodsList(); + // 订单商品总数量 + $orderTotalNum = helper::getArrayColumnSum($this->goodsList, 'total_num'); + // 设置订单商品会员折扣价 + $this->setOrderGoodsGradeMoney(); + // 设置订单商品总金额(不含优惠折扣) + $this->setOrderTotalPrice(); + // 计算可用积分抵扣 + $this->setOrderPoints(); + // 当前用户可用的优惠券列表 + $couponList = $this->getUserCouponList($this->orderData['order_total_price']); + // 计算优惠券抵扣 + $this->setOrderCouponMoney($couponList, $this->param['coupon_id']); + // 计算订单商品的实际付款金额 + $this->setOrderGoodsPayPrice(); + // 设置默认配送方式 + !$this->param['delivery'] && $this->param['delivery'] = current(SettingModel::getItem('store')['delivery_type']); + // 处理配送方式 + if ($this->param['delivery'] == DeliveryTypeEnum::EXPRESS) { + $this->setOrderExpress(); + } elseif ($this->param['delivery'] == DeliveryTypeEnum::EXTRACT) { + $this->param['shop_id'] > 0 && $this->orderData['extract_shop'] = ShopModel::detail($this->param['shop_id']); + } + // 计算订单最终金额 + $this->setOrderPayPrice(); + // 计算订单积分赠送数量 + $this->setOrderPointsBonus(); + // 返回订单数据 + return array_merge([ + 'goods_list' => array_values($this->goodsList), // 商品信息 + 'order_total_num' => $orderTotalNum, // 商品总数量 + 'coupon_list' => array_values($couponList), // 优惠券列表 + 'has_error' => $this->hasError(), + 'error_msg' => $this->getError(), + ], $this->orderData); + } + + /** + * 计算订单可用积分抵扣 + * @return bool + */ + private function setOrderPoints() + { + // 设置默认的商品积分抵扣信息 + $this->setDefaultGoodsPoints(); + // 积分设置 + $setting = SettingModel::getItem('points'); + // 条件:后台开启下单使用积分抵扣 + if (!$setting['is_shopping_discount'] || !$this->checkoutRule['is_use_points']) { + return false; + } + // 条件:订单金额满足[?]元 + if (helper::bccomp($setting['discount']['full_order_price'], $this->orderData['order_total_price']) === 1) { + return false; + } + // 计算订单商品最多可抵扣的积分数量 + $this->setOrderGoodsMaxPointsNum(); + // 订单最多可抵扣的积分总数量 + $maxPointsNumCount = helper::getArrayColumnSum($this->goodsList, 'max_points_num'); + // 实际可抵扣的积分数量 + $actualPointsNum = min($maxPointsNumCount, $this->user['points']); + if ($actualPointsNum < 1) { + return false; + } + // 计算订单商品实际抵扣的积分数量和金额 + $GoodsDeduct = new PointsDeductService($this->goodsList); + $GoodsDeduct->setGoodsPoints($maxPointsNumCount, $actualPointsNum); + // 积分抵扣总金额 + $orderPointsMoney = helper::getArrayColumnSum($this->goodsList, 'points_money'); + $this->orderData['points_money'] = helper::number2($orderPointsMoney); + // 积分抵扣总数量 + $this->orderData['points_num'] = $actualPointsNum; + // 允许积分抵扣 + $this->orderData['is_allow_points'] = true; + return true; + } + + /** + * 计算订单商品最多可抵扣的积分数量 + * @return bool + */ + private function setOrderGoodsMaxPointsNum() + { + // 积分设置 + $setting = SettingModel::getItem('points'); + foreach ($this->goodsList as &$goods) { + // 商品不允许积分抵扣 + if (!$goods['is_points_discount']) continue; + // 积分抵扣比例 + $deductionRatio = helper::bcdiv($setting['discount']['max_money_ratio'], 100); + // 最多可抵扣的金额 + $maxPointsMoney = helper::bcmul($goods['total_price'], $deductionRatio); + // 最多可抵扣的积分数量 + $goods['max_points_num'] = helper::bcdiv($maxPointsMoney, $setting['discount']['discount_ratio'], 0); + } + return true; + } + + /** + * 设置默认的商品积分抵扣信息 + * @return bool + */ + private function setDefaultGoodsPoints() + { + foreach ($this->goodsList as &$goods) { + // 最多可抵扣的积分数量 + $goods['max_points_num'] = 0; + // 实际抵扣的积分数量 + $goods['points_num'] = 0; + // 实际抵扣的金额 + $goods['points_money'] = 0.00; + } + return true; + } + + /** + * 整理订单数据(结算台初始化) + * @return array + */ + private function getOrderData() + { + // 系统支持的配送方式 (后台设置) + $deliveryType = SettingModel::getItem('store')['delivery_type']; + // 积分设置 + $pointsSetting = SettingModel::getItem('points'); + return [ + // 配送类型 + 'delivery' => $this->param['delivery'] > 0 ? $this->param['delivery'] : $deliveryType[0], + // 默认地址 + 'address' => $this->user['address_default'], + // 是否存在收货地址 + 'exist_address' => $this->user['address_id'] > 0, + // 配送费用 + 'express_price' => 0.00, + // 当前用户收货城市是否存在配送规则中 + 'intra_region' => true, + // 自提门店信息 + 'extract_shop' => [], + // 是否允许使用积分抵扣 + 'is_allow_points' => false, + // 是否使用积分抵扣 + 'is_use_points' => $this->param['is_use_points'], + // 积分抵扣金额 + 'points_money' => 0.00, + // 赠送的积分数量 + 'points_bonus' => 0, + // 支付方式 + 'pay_type' => $this->param['pay_type'], + // 系统设置 + 'setting' => [ + 'delivery' => $deliveryType, // 支持的配送方式 + 'points_name' => $pointsSetting['points_name'], // 积分名称 + 'points_describe' => $pointsSetting['describe'], // 积分说明 + ], + // 记忆的自提联系方式 + 'last_extract' => UserService::getLastExtract($this->user['user_id']), + // todo: delete - 兼容处理 + 'deliverySetting' => $deliveryType, + ]; + } + + /** + * 当前用户可用的优惠券列表 + * @param $orderTotalPrice + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getUserCouponList($orderTotalPrice) + { + // 是否开启优惠券折扣 + if (!$this->checkoutRule['is_coupon']) { + return []; + } + return UserCouponModel::getUserCouponList($this->user['user_id'], $orderTotalPrice); + } + + /** + * 验证订单商品的状态 + * @return bool + */ + private function validateGoodsList() + { + $Checkout = CheckoutFactory::getFactory( + $this->user, + $this->goodsList, + $this->orderSource['source'] + ); + $status = $Checkout->validateGoodsList(); + $status == false && $this->setError($Checkout->getError()); + return $status; + } + + /** + * 设置订单的商品总金额(不含优惠折扣) + */ + private function setOrderTotalPrice() + { + // 订单商品的总金额(不含优惠券折扣) + $this->orderData['order_total_price'] = helper::number2(helper::getArrayColumnSum($this->goodsList, 'total_price')); + } + + /** + * 设置订单的实际支付金额(含配送费) + */ + private function setOrderPayPrice() + { + // 订单金额(含优惠折扣) + $this->orderData['order_price'] = helper::number2(helper::getArrayColumnSum($this->goodsList, 'total_pay_price')); + // 订单实付款金额(订单金额 + 运费) + $this->orderData['order_pay_price'] = helper::number2(helper::bcadd($this->orderData['order_price'], $this->orderData['express_price'])); + } + + /** + * 计算订单积分赠送数量 + * @return bool + */ + private function setOrderPointsBonus() + { + // 初始化商品积分赠送数量 + foreach ($this->goodsList as &$goods) { + $goods['points_bonus'] = 0; + } + // 积分设置 + $setting = SettingModel::getItem('points'); + // 条件:后台开启开启购物送积分 + if (!$setting['is_shopping_gift']) { + return false; + } + // 设置商品积分赠送数量 + foreach ($this->goodsList as &$goods) { + // 积分赠送比例 + $ratio = $setting['gift_ratio'] / 100; + // 计算抵扣积分数量 + $goods['points_bonus'] = !$goods['is_points_gift'] ? 0 : helper::bcmul($goods['total_pay_price'], $ratio, 0); + } + // 订单积分赠送数量 + $this->orderData['points_bonus'] = helper::getArrayColumnSum($this->goodsList, 'points_bonus'); + return true; + } + + /** + * 计算订单商品的实际付款金额 + * @return bool + */ + private function setOrderGoodsPayPrice() + { + // 商品总价 - 优惠抵扣 + foreach ($this->goodsList as &$goods) { + // 减去优惠券抵扣金额 + $value = helper::bcsub($goods['total_price'], $goods['coupon_money']); + // 减去积分抵扣金额 + if ($this->orderData['is_allow_points'] && $this->orderData['is_use_points']) { + $value = helper::bcsub($value, $goods['points_money']); + } + $goods['total_pay_price'] = helper::number2($value); + } + return true; + } + + /** + * 设置订单商品会员折扣价 + * @return bool + */ + private function setOrderGoodsGradeMoney() + { + // 设置默认数据 + helper::setDataAttribute($this->goodsList, [ + // 标记参与会员折扣 + 'is_user_grade' => false, + // 会员等级抵扣的金额 + 'grade_ratio' => 0, + // 会员折扣的商品单价 + 'grade_goods_price' => 0.00, + // 会员折扣的总额差 + 'grade_total_money' => 0.00, + ], true); + + // 是否开启会员等级折扣 + if (!$this->checkoutRule['is_user_grade']) { + return false; + } + // 会员等级状态 + if (!( + $this->user['grade_id'] > 0 && !empty($this->user['grade']) + && !$this->user['grade']['is_delete'] && $this->user['grade']['status'] + )) { + return false; + } + // 计算抵扣金额 + foreach ($this->goodsList as &$goods) { + // 判断商品是否参与会员折扣 + if (!$goods['is_enable_grade']) { + continue; + } + // 商品单独设置了会员折扣 + if ($goods['is_alone_grade'] && isset($goods['alone_grade_equity'][$this->user['grade_id']])) { + // 折扣比例 + $discountRatio = helper::bcdiv($goods['alone_grade_equity'][$this->user['grade_id']], 10); + } else { + // 折扣比例 + $discountRatio = helper::bcdiv($this->user['grade']['equity']['discount'], 10); + } + if ($discountRatio > 0) { + // 会员折扣后的商品总金额 + $gradeTotalPrice = max(0.01, helper::bcmul($goods['total_price'], $discountRatio)); + helper::setDataAttribute($goods, [ + 'is_user_grade' => true, + 'grade_ratio' => $discountRatio, + 'grade_goods_price' => helper::number2(helper::bcmul($goods['goods_price'], $discountRatio), true), + 'grade_total_money' => helper::number2(helper::bcsub($goods['total_price'], $gradeTotalPrice)), + 'total_price' => $gradeTotalPrice, + ], false); + } + } + return true; + } + + /** + * 设置订单优惠券抵扣信息 + * @param array $couponList 当前用户可用的优惠券列表 + * @param int $couponId 当前选择的优惠券id + * @return bool + * @throws BaseException + */ + private function setOrderCouponMoney($couponList, $couponId) + { + // 设置默认数据:订单信息 + helper::setDataAttribute($this->orderData, [ + 'coupon_id' => 0, // 用户优惠券id + 'coupon_money' => 0, // 优惠券抵扣金额 + ], false); + // 设置默认数据:订单商品列表 + helper::setDataAttribute($this->goodsList, [ + 'coupon_money' => 0, // 优惠券抵扣金额 + ], true); + // 是否开启优惠券折扣 + if (!$this->checkoutRule['is_coupon']) { + return false; + } + // 如果没有可用的优惠券,直接返回 + if ($couponId <= 0 || empty($couponList)) { + return true; + } + // 获取优惠券信息 + $couponInfo = helper::getArrayItemByColumn($couponList, 'user_coupon_id', $couponId); + if ($couponInfo == false) { + throw new BaseException(['msg' => '未找到优惠券信息']); + } + // 计算订单商品优惠券抵扣金额 + $goodsListTemp = helper::getArrayColumns($this->goodsList, ['total_price']); + $CouponMoney = new GoodsDeductService; + $completed = $CouponMoney->setGoodsCouponMoney($goodsListTemp, $couponInfo['reduced_price']); + // 分配订单商品优惠券抵扣金额 + foreach ($this->goodsList as $key => &$goods) { + $goods['coupon_money'] = $completed[$key]['coupon_money'] / 100; + } + // 记录订单优惠券信息 + $this->orderData['coupon_id'] = $couponId; + $this->orderData['coupon_money'] = helper::number2($CouponMoney->getActualReducedMoney() / 100); + return true; + } + + /** + * 订单配送-快递配送 + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function setOrderExpress() + { + // 设置默认数据:配送费用 + helper::setDataAttribute($this->goodsList, [ + 'express_price' => 0, + ], true); + // 当前用户收货城市id + $cityId = $this->user['address_default'] ? $this->user['address_default']['city_id'] : null; + // 初始化配送服务类 + $ExpressService = new ExpressService($cityId, $this->goodsList, OrderTypeEnum::MASTER); + // 验证商品是否在配送范围 + $isIntraRegion = $ExpressService->isIntraRegion(); + if ($cityId > 0 && $isIntraRegion == false) { + $notInRuleGoodsName = $ExpressService->getNotInRuleGoodsName(); + $this->setError("很抱歉,您的收货地址不在商品 [{$notInRuleGoodsName}] 的配送范围内"); + } + // 订单总运费金额 + $this->orderData['intra_region'] = $isIntraRegion; + $this->orderData['express_price'] = $ExpressService->getDeliveryFee(); + return true; + } + + /** + * 创建新订单 + * @param array $order 订单信息 + * @return bool + * @throws \Exception + */ + public function createOrder($order) + { + // 表单验证 + if (!$this->validateOrderForm($order, $this->param['linkman'], $this->param['phone'])) { + return false; + } + // 创建新的订单 + $status = $this->model->transaction(function () use ($order) { + // 创建订单事件 + return $this->createOrderEvent($order); + }); + // 余额支付标记订单已支付 + if ($status && $order['pay_type'] == PayTypeEnum::BALANCE) { + return $this->model->onPaymentByBalance($this->model['order_no']); + } + return $status; + } + + /** + * 创建订单事件 + * @param $order + * @return bool + * @throws BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + private function createOrderEvent($order) + { + // 新增订单记录 + $status = $this->add($order, $this->param['remark']); + if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) { + // 记录收货地址 + $this->saveOrderAddress($order['address']); + } elseif ($order['delivery'] == DeliveryTypeEnum::EXTRACT) { + // 记录自提信息 + $this->saveOrderExtract($this->param['linkman'], $this->param['phone']); + } + // 保存订单商品信息 + $this->saveOrderGoods($order); + // 更新商品库存 (针对下单减库存的商品) + $this->updateGoodsStockNum($order); + // 设置优惠券使用状态 + UserCouponModel::setIsUse($this->param['coupon_id']); + // 积分抵扣情况下扣除用户积分 + if ($order['is_allow_points'] && $order['is_use_points'] && $order['points_num'] > 0) { + $describe = "用户消费:{$this->model['order_no']}"; + $this->user->setIncPoints(-$order['points_num'], $describe); + } + // 获取订单详情 + $detail = OrderModel::getUserOrderDetail($this->model['order_id'], $this->user['user_id']); + // 记录分销商订单 + $this->checkoutRule['is_dealer'] && DealerOrderModel::createOrder($detail); + return $status; + } + + /** + * 构建支付请求的参数 + * @return array + * @throws BaseException + * @throws \think\exception\DbException + */ + public function onOrderPayment() + { + return PaymentService::orderPayment($this->user, $this->model, $this->param['pay_type']); + } + + /** + * 表单验证 (订单提交) + * @param array $order 订单信息 + * @param string $linkman 联系人 + * @param string $phone 联系电话 + * @return bool + */ + private function validateOrderForm(&$order, $linkman, $phone) + { + if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) { + if (empty($order['address'])) { + $this->error = '您还没有选择配送地址'; + return false; + } + } + if ($order['delivery'] == DeliveryTypeEnum::EXTRACT) { + if (empty($order['extract_shop'])) { + $this->error = '您还没有选择自提门店'; + return false; + } + if (empty($linkman) || empty($phone)) { + $this->error = '您还没有填写联系人和电话'; + return false; + } + } + // 余额支付时判断用户余额是否足够 + if ($order['pay_type'] == PayTypeEnum::BALANCE) { + if ($this->user['balance'] < $order['order_pay_price']) { + $this->error = '您的余额不足,无法使用余额支付'; + return false; + } + } + return true; + } + + /** + * 当前订单是否存在和使用积分抵扣 + * @param $order + * @return bool + */ + private function isExistPointsDeduction($order) + { + return $order['is_allow_points'] && $order['is_use_points']; + } + + /** + * 新增订单记录 + * @param $order + * @param string $remark + * @return false|int + */ + private function add($order, $remark = '') + { + // 当前订单是否存在和使用积分抵扣 + $isExistPointsDeduction = $this->isExistPointsDeduction($order); + // 订单数据 + $data = [ + 'user_id' => $this->user['user_id'], + 'order_no' => $this->model->orderNo(), + 'total_price' => $order['order_total_price'], + 'order_price' => $order['order_price'], + 'coupon_id' => $order['coupon_id'], + 'coupon_money' => $order['coupon_money'], + 'points_money' => $isExistPointsDeduction ? $order['points_money'] : 0.00, + 'points_num' => $isExistPointsDeduction ? $order['points_num'] : 0, + 'pay_price' => $order['order_pay_price'], + 'delivery_type' => $order['delivery'], + 'pay_type' => $order['pay_type'], + 'buyer_remark' => trim($remark), + 'order_source' => $this->orderSource['source'], + 'order_source_id' => $this->orderSource['source_id'], + 'points_bonus' => $order['points_bonus'], + 'wxapp_id' => $this->wxapp_id, + ]; + if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) { + $data['express_price'] = $order['express_price']; + } elseif ($order['delivery'] == DeliveryTypeEnum::EXTRACT) { + $data['extract_shop_id'] = $order['extract_shop']['shop_id']; + } + // 保存订单记录 + return $this->model->save($data); + } + + /** + * 保存订单商品信息 + * @param $order + * @return int + */ + private function saveOrderGoods($order) + { + // 当前订单是否存在和使用积分抵扣 + $isExistPointsDeduction = $this->isExistPointsDeduction($order); + // 订单商品列表 + $goodsList = []; + foreach ($order['goods_list'] as $goods) { + /* @var GoodsModel $goods */ + $item = [ + 'user_id' => $this->user['user_id'], + 'wxapp_id' => $this->wxapp_id, + 'goods_id' => $goods['goods_id'], + 'goods_name' => $goods['goods_name'], + 'image_id' => $goods['image'][0]['image_id'], + 'deduct_stock_type' => $goods['deduct_stock_type'], + 'spec_type' => $goods['spec_type'], + 'spec_sku_id' => $goods['goods_sku']['spec_sku_id'], + 'goods_sku_id' => $goods['goods_sku']['goods_sku_id'], + 'goods_attr' => $goods['goods_sku']['goods_attr'], + 'content' => $goods['content'], + 'goods_no' => $goods['goods_sku']['goods_no'], + 'goods_price' => $goods['goods_sku']['goods_price'], + 'line_price' => $goods['goods_sku']['line_price'], + 'goods_weight' => $goods['goods_sku']['goods_weight'], + 'is_user_grade' => (int)$goods['is_user_grade'], + 'grade_ratio' => $goods['grade_ratio'], + 'grade_goods_price' => $goods['grade_goods_price'], + 'grade_total_money' => $goods['grade_total_money'], + 'coupon_money' => $goods['coupon_money'], + 'points_money' => $isExistPointsDeduction ? $goods['points_money'] : 0.00, + 'points_num' => $isExistPointsDeduction ? $goods['points_num'] : 0, + 'points_bonus' => $goods['points_bonus'], + 'total_num' => $goods['total_num'], + 'total_price' => $goods['total_price'], + 'total_pay_price' => $goods['total_pay_price'], + 'is_ind_dealer' => $goods['is_ind_dealer'], + 'dealer_money_type' => $goods['dealer_money_type'], + 'first_money' => $goods['first_money'], + 'second_money' => $goods['second_money'], + 'third_money' => $goods['third_money'], + ]; + // 记录订单商品来源id + $item['goods_source_id'] = isset($goods['goods_source_id']) ? $goods['goods_source_id'] : 0; + $goodsList[] = $item; + } + return $this->model->goods()->saveAll($goodsList); + } + + /** + * 更新商品库存 (针对下单减库存的商品) + * @param $order + * @return mixed + */ + private function updateGoodsStockNum($order) + { + return StockFactory::getFactory($this->model['order_source'])->updateGoodsStock($order['goods_list']); + } + + /** + * 记录收货地址 + * @param $address + * @return false|\think\Model + */ + private function saveOrderAddress($address) + { + if ($address['region_id'] == 0 && !empty($address['district'])) { + $address['detail'] = $address['district'] . ' ' . $address['detail']; + } + return $this->model->address()->save([ + 'user_id' => $this->user['user_id'], + 'wxapp_id' => $this->wxapp_id, + 'name' => $address['name'], + 'phone' => $address['phone'], + 'province_id' => $address['province_id'], + 'city_id' => $address['city_id'], + 'region_id' => $address['region_id'], + 'detail' => $address['detail'], + ]); + } + + /** + * 保存上门自提联系人 + * @param $linkman + * @param $phone + * @return false|\think\Model + */ + private function saveOrderExtract($linkman, $phone) + { + // 记忆上门自提联系人(缓存),用于下次自动填写 + UserService::setLastExtract($this->model['user_id'], trim($linkman), trim($phone)); + // 保存上门自提联系人(数据库) + return $this->model->extract()->save([ + 'linkman' => trim($linkman), + 'phone' => trim($phone), + 'user_id' => $this->model['user_id'], + 'wxapp_id' => $this->wxapp_id, + ]); + } + + /** + * 设置错误信息 + * @param $error + */ + protected function setError($error) + { + empty($this->error) && $this->error = $error; + } + + /** + * 获取错误信息 + * @return mixed + */ + public function getError() + { + return $this->error ?: ''; + } + + /** + * 是否存在错误 + * @return bool + */ + public function hasError() + { + return !empty($this->error); + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/PaySuccess.php b/source/application/api/service/order/PaySuccess.php new file mode 100644 index 0000000..2a4443e --- /dev/null +++ b/source/application/api/service/order/PaySuccess.php @@ -0,0 +1,150 @@ +model = OrderModel::getPayDetail($orderNo); + if (!empty($this->model)) { + $this->wxappId = $this->model['wxapp_id']; + } + // 获取用户信息 + $this->user = UserModel::detail($this->model['user_id']); + } + + /** + * 获取订单详情 + * @return OrderModel|null + */ + public function getOrderInfo() + { + return $this->model; + } + + /** + * 订单支付成功业务处理 + * @param $payType + * @param array $payData + * @return bool + */ + public function onPaySuccess($payType, $payData = []) + { + if (empty($this->model)) { + $this->error = '未找到该订单信息'; + return false; + } + // 更新付款状态 + $status = $this->updatePayStatus($payType, $payData); + // 订单支付成功行为 + if ($status == true) { + Hook::listen('order_pay_success', $this->model, OrderTypeEnum::MASTER); + } + return $status; + } + + /** + * 更新付款状态 + * @param $payType + * @param array $payData + * @return bool + */ + private function updatePayStatus($payType, $payData = []) + { + // 验证余额支付时用户余额是否满足 + if ($payType == PayTypeEnum::BALANCE) { + if ($this->user['balance'] < $this->model['pay_price']) { + $this->error = '用户余额不足,无法使用余额支付'; + return false; + } + } + // 事务处理 + $this->model->transaction(function () use ($payType, $payData) { + // 更新订单状态 + $this->updateOrderInfo($payType, $payData); + // 累积用户总消费金额 + $this->user->setIncPayMoney($this->model['pay_price']); + // 记录订单支付信息 + $this->updatePayInfo($payType); + }); + return true; + } + + /** + * 更新订单记录 + * @param $payType + * @param $payData + * @return false|int + * @throws \Exception + */ + private function updateOrderInfo($payType, $payData) + { + // 更新商品库存、销量 + StockFactory::getFactory($this->model['order_source'])->updateStockSales($this->model['goods']); + // 整理订单信息 + $order = [ + 'pay_type' => $payType, + 'pay_status' => 20, + 'pay_time' => time() + ]; + if ($payType == PayTypeEnum::WECHAT) { + $order['transaction_id'] = $payData['transaction_id']; + } + // 更新订单状态 + return $this->model->save($order); + } + + /** + * 记录订单支付信息 + * @param $payType + * @throws \think\Exception + */ + private function updatePayInfo($payType) + { + // 余额支付 + if ($payType == PayTypeEnum::BALANCE) { + // 更新用户余额 + $this->user->setDec('balance', $this->model['pay_price']); + BalanceLogModel::add(SceneEnum::CONSUME, [ + 'user_id' => $this->user['user_id'], + 'money' => -$this->model['pay_price'], + ], ['order_no' => $this->model['order_no']]); + } + // 微信支付 + if ($payType == PayTypeEnum::WECHAT) { + // 更新prepay_id记录 + WxappPrepayIdModel::updatePayStatus($this->model['order_id'], OrderTypeEnum::MASTER); + } + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/Bargain.php b/source/application/api/service/order/source/Bargain.php new file mode 100644 index 0000000..5ef9d16 --- /dev/null +++ b/source/application/api/service/order/source/Bargain.php @@ -0,0 +1,70 @@ +checkOrderStatusOnPayCommon($order)) { + return false; + } + // 判断商品状态、库存 + if (!$this->checkGoodsStatusOnPay($order['goods'])) { + return false; + } + return true; + } + + /** + * 判断商品状态、库存 (未付款订单) + * @param $goodsList + * @return bool + * @throws \think\exception\DbException + */ + protected function checkGoodsStatusOnPay($goodsList) + { + foreach ($goodsList as $goods) { + // 获取商品的sku信息 + $goodsSku = $this->getOrderGoodsSku($goods['goods_id'], $goods['spec_sku_id']); + // sku已不存在 + if (empty($goodsSku)) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] sku已不存在,请重新下单"; + return false; + } + // 付款减库存 + if ($goods['deduct_stock_type'] == 20 && $goods['total_num'] > $goodsSku['stock_num']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 库存不足"; + return false; + } + } + return true; + } + + /** + * 获取指定的商品sku信息 + * @param $goodsId + * @param $specSkuId + * @return \app\common\model\GoodsSku|null + * @throws \think\exception\DbException + */ + private function getOrderGoodsSku($goodsId, $specSkuId) + { + return GoodsSkuModel::detail($goodsId, $specSkuId); + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/Basics.php b/source/application/api/service/order/source/Basics.php new file mode 100644 index 0000000..3d3fdf3 --- /dev/null +++ b/source/application/api/service/order/source/Basics.php @@ -0,0 +1,43 @@ +error = '很抱歉,当前订单不合法,无法支付'; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/Factory.php b/source/application/api/service/order/source/Factory.php new file mode 100644 index 0000000..3f29fd6 --- /dev/null +++ b/source/application/api/service/order/source/Factory.php @@ -0,0 +1,36 @@ + 'Master', + OrderSourceEnum::BARGAIN => 'Bargain', + OrderSourceEnum::SHARP => 'Sharp', + ]; + + /** + * 根据订单来源获取商品库存类 + * @param int $orderSource + * @return mixed + */ + public static function getFactory($orderSource = OrderSourceEnum::MASTER) + { + static $classObj = []; + if (!isset($classObj[$orderSource])) { + $className = __NAMESPACE__ . '\\' . static::$class[$orderSource]; + $classObj[$orderSource] = new $className(); + } + return $classObj[$orderSource]; + } +} \ No newline at end of file diff --git a/source/application/api/service/order/source/Master.php b/source/application/api/service/order/source/Master.php new file mode 100644 index 0000000..8d7e6ea --- /dev/null +++ b/source/application/api/service/order/source/Master.php @@ -0,0 +1,78 @@ +checkOrderStatusOnPayCommon($order)) { + return false; + } + // 判断商品状态、库存 + if (!$this->checkGoodsStatusOnPay($order['goods'])) { + return false; + } + return true; + } + + /** + * 判断商品状态、库存 (未付款订单) + * @param $goodsList + * @return bool + * @throws \think\exception\DbException + */ + protected function checkGoodsStatusOnPay($goodsList) + { + foreach ($goodsList as $goods) { + // 判断商品是否下架 + if ( + empty($goods['goods']) + || $goods['goods']['goods_status']['value'] != 10 + ) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 已下架"; + return false; + } + // 获取商品的sku信息 + $goodsSku = $this->getOrderGoodsSku($goods['goods_id'], $goods['spec_sku_id']); + // sku已不存在 + if (empty($goodsSku)) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] sku已不存在,请重新下单"; + return false; + } + // 付款减库存 + if ($goods['deduct_stock_type'] == 20 && $goods['total_num'] > $goodsSku['stock_num']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 库存不足"; + return false; + } + } + return true; + } + + /** + * 获取指定的商品sku信息 + * @param $goodsId + * @param $specSkuId + * @return \app\common\model\GoodsSku|null + * @throws \think\exception\DbException + */ + private function getOrderGoodsSku($goodsId, $specSkuId) + { + return GoodsSkuModel::detail($goodsId, $specSkuId); + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/Sharp.php b/source/application/api/service/order/source/Sharp.php new file mode 100644 index 0000000..eae5dee --- /dev/null +++ b/source/application/api/service/order/source/Sharp.php @@ -0,0 +1,76 @@ +checkOrderStatusOnPayCommon($order)) { + return false; + } + // 判断商品状态、库存 + if (!$this->checkGoodsStatusOnPay($order['goods'])) { + return false; + } + return true; + } + + /** + * 判断商品状态、库存 (未付款订单) + * @param $goodsList + * @return bool + * @throws \think\exception\DbException + */ + protected function checkGoodsStatusOnPay($goodsList) + { + foreach ($goodsList as $goods) { + // 秒杀商品信息 + $sharpGoods = SharpGoodsModel::detail($goods['goods_source_id'], ['sku']); + // 判断商品是否下架 + if (empty($sharpGoods) || $sharpGoods['is_delete'] || !$sharpGoods['status']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 不存在或已下架"; + return false; + } + // 获取秒杀商品的sku信息 + $goodsSku = $this->getOrderGoodsSku($sharpGoods, $goods['spec_sku_id']); + if (empty($goodsSku)) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] sku已不存在,请重新下单"; + return false; + } + // 付款减库存 + if ($goods['deduct_stock_type'] == 20 && $goods['total_num'] > $goodsSku['seckill_stock']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 库存不足"; + return false; + } + } + return true; + } + + /** + * 获取指定的商品sku信息 + * @param $sharpGoods + * @param $specSkuId + * @return bool + */ + private function getOrderGoodsSku($sharpGoods, $specSkuId) + { + return helper::getArrayItemByColumn($sharpGoods['sku'], 'spec_sku_id', $specSkuId); + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/checkout/Bargain.php b/source/application/api/service/order/source/checkout/Bargain.php new file mode 100644 index 0000000..41d1c84 --- /dev/null +++ b/source/application/api/service/order/source/checkout/Bargain.php @@ -0,0 +1,28 @@ +goodsList as $goods) { + // 判断商品库存 + if ($goods['total_num'] > $goods['goods_sku']['stock_num']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 库存不足"; + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/checkout/Basics.php b/source/application/api/service/order/source/checkout/Basics.php new file mode 100644 index 0000000..107c5e3 --- /dev/null +++ b/source/application/api/service/order/source/checkout/Basics.php @@ -0,0 +1,38 @@ +user = $user; + $this->goodsList = $goodsList; + } + + /** + * 验证商品列表 + * @return mixed + */ + abstract public function validateGoodsList(); + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/checkout/Factory.php b/source/application/api/service/order/source/checkout/Factory.php new file mode 100644 index 0000000..02dd8af --- /dev/null +++ b/source/application/api/service/order/source/checkout/Factory.php @@ -0,0 +1,35 @@ + 'Master', + OrderSourceEnum::BARGAIN => 'Bargain', + OrderSourceEnum::SHARP => 'Sharp', + ]; + + /** + * 根据订单来源获取商品库存类 + * @param $user + * @param $goodsList + * @param int $orderSource + * @return mixed + */ + public static function getFactory($user, $goodsList, $orderSource = OrderSourceEnum::MASTER) + { + $className = __NAMESPACE__ . '\\' . static::$class[$orderSource]; + return new $className($user, $goodsList); + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/checkout/Master.php b/source/application/api/service/order/source/checkout/Master.php new file mode 100644 index 0000000..4a134c4 --- /dev/null +++ b/source/application/api/service/order/source/checkout/Master.php @@ -0,0 +1,33 @@ +goodsList as $goods) { + // 判断商品是否下架 + if ($goods['goods_status']['value'] != 10) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 已下架"; + return false; + } + // 判断商品库存 + if ($goods['total_num'] > $goods['goods_sku']['stock_num']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 库存不足"; + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/service/order/source/checkout/Sharp.php b/source/application/api/service/order/source/checkout/Sharp.php new file mode 100644 index 0000000..da8f573 --- /dev/null +++ b/source/application/api/service/order/source/checkout/Sharp.php @@ -0,0 +1,96 @@ +validateGoodsStatus()) { + return false; + } + // 验证商品限购 + if (!$this->validateLimitNum()) { + return false; + } + // 判断商品库存 + if (!$this->validateGoodsSeckillStock()) { + return false; + } + return true; + } + + /** + * 判断商品是否下架 + * @return bool + */ + private function validateGoodsStatus() + { + foreach ($this->goodsList as $goods) { + if ($goods['is_delete'] || !$goods['status']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 已下架"; + return false; + } + } + return true; + } + + /** + * 判断商品是否下架 + * @return bool + */ + private function validateGoodsSeckillStock() + { + foreach ($this->goodsList as $goods) { + if ($goods['total_num'] > $goods['goods_sku']['seckill_stock']) { + $this->error = "很抱歉,商品 [{$goods['goods_name']}] 库存不足"; + return false; + } + } + return true; + } + + /** + * 验证商品限购 + * @return bool + */ + public function validateLimitNum() + { + foreach ($this->goodsList as $goods) { + // 不限购 + if ($goods['limit_num'] <= 0) return true; + // 获取用户已下单的件数(未取消 订单来源) + $alreadyBuyNum = SharpOrderService::getAlreadyBuyNum($this->user['user_id'], $goods['goods_id']); + // 情况1: 已购买0件, 实际想购买5件 + if ($alreadyBuyNum == 0 && $goods['total_num'] > $goods['limit_num']) { + $this->error = "很抱歉,该商品限购{$goods['limit_num']}件,请修改购买数量"; + return false; + } + // 情况2: 已购买3件, 实际想购买1件 + if ($alreadyBuyNum >= $goods['limit_num']) { + $this->error = "很抱歉,该商品限购{$goods['limit_num']}件,您当前已下单{$alreadyBuyNum}件,无法购买"; + return false; + } + // 情况3: 已购买2件, 实际想购买2件 + if (($alreadyBuyNum + $goods['total_num']) > $goods['limit_num']) { + $diffNum = ($alreadyBuyNum + $goods['total_num']) - $goods['limit_num']; + $this->error = "很抱歉,该商品限购{$goods['limit_num']}件,您最多能再购买{$diffNum}件"; + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/service/points/GoodsDeduct.php b/source/application/api/service/points/GoodsDeduct.php new file mode 100644 index 0000000..7577127 --- /dev/null +++ b/source/application/api/service/points/GoodsDeduct.php @@ -0,0 +1,82 @@ +goodsList = $goodsList; + } + + public function setGoodsPoints($maxPointsNumCount, $actualPointsNum) + { + // 计算实际积分抵扣数量 + $this->setGoodsListPointsNum($maxPointsNumCount, $actualPointsNum); + // 总抵扣数量 + $totalPointsNum = helper::getArrayColumnSum($this->goodsList, 'points_num'); + // 填充余数 + $this->setGoodsListPointsNumFill($actualPointsNum, $totalPointsNum); + $this->setGoodsListPointsNumDiff($actualPointsNum, $totalPointsNum); + // 计算实际积分抵扣金额 + $this->setGoodsListPointsMoney(); + return true; + } + + /** + * 计算实际积分抵扣数量 + * @param $maxPointsNumCount + * @param $actualPointsNum + */ + private function setGoodsListPointsNum($maxPointsNumCount, $actualPointsNum) + { + foreach ($this->goodsList as &$goods) { + if (!$goods['is_points_discount']) continue; + $goods['points_num'] = floor($goods['max_points_num'] / $maxPointsNumCount * $actualPointsNum); + } + } + + /** + * 计算实际积分抵扣金额 + */ + private function setGoodsListPointsMoney() + { + $setting = SettingModel::getItem('points'); + foreach ($this->goodsList as &$goods) { + if (!$goods['is_points_discount']) continue; + $goods['points_money'] = helper::bcmul($goods['points_num'], $setting['discount']['discount_ratio']); + } + } + + private function setGoodsListPointsNumFill($actualPointsNum, $totalPointsNum) + { + if ($totalPointsNum === 0) { + $temReducedMoney = $actualPointsNum; + foreach ($this->goodsList as &$goods) { + if (!$goods['is_points_discount']) continue; + if ($temReducedMoney === 0) break; + $goods['points_num'] = 1; + $temReducedMoney--; + } + } + return true; + } + + private function setGoodsListPointsNumDiff($actualPointsNum, $totalPointsNum) + { + $tempDiff = $actualPointsNum - $totalPointsNum; + foreach ($this->goodsList as &$goods) { + if (!$goods['is_points_discount']) continue; + if ($tempDiff < 1) break; + $goods['points_num'] = $goods['points_num'] + 1; + $tempDiff--; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/service/recharge/PaySuccess.php b/source/application/api/service/recharge/PaySuccess.php new file mode 100644 index 0000000..1efd758 --- /dev/null +++ b/source/application/api/service/recharge/PaySuccess.php @@ -0,0 +1,79 @@ +model = OrderModel::getPayDetail($orderNo); + $this->wxappId = $this->model['wxapp_id']; + // 获取用户信息 + $this->user = UserModel::detail($this->model['user_id']); + } + + /** + * 获取订单详情 + * @return OrderModel|null + */ + public function getOrderInfo() + { + return $this->model; + } + + /** + * 订单支付成功业务处理 + * @param int $payType 支付类型 + * @param array $payData 支付回调数据 + * @return bool + */ + public function onPaySuccess($payType, $payData) + { + return $this->model->transaction(function () use ($payType, $payData) { + // 更新订单状态 + $this->model->save([ + 'pay_status' => PayStatusEnum::SUCCESS, + 'pay_time' => time(), + 'transaction_id' => $payData['transaction_id'] + ]); + // 累积用户余额 + $this->user->setInc('balance', $this->model['actual_money']); + // 用户余额变动明细 + BalanceLogModel::add(SceneEnum::RECHARGE, [ + 'user_id' => $this->user['user_id'], + 'money' => $this->model['actual_money'], + 'wxapp_id' => $this->wxappId, + ], ['order_no' => $this->model['order_no']]); + // 更新prepay_id记录 + if ($payType == PayTypeEnum::WECHAT) { + WxappPrepayIdModel::updatePayStatus($this->model['order_id'], OrderTypeEnum::RECHARGE); + } + return true; + }); + } + +} \ No newline at end of file diff --git a/source/application/api/service/sharing/order/Checkout.php b/source/application/api/service/sharing/order/Checkout.php new file mode 100644 index 0000000..c5234a0 --- /dev/null +++ b/source/application/api/service/sharing/order/Checkout.php @@ -0,0 +1,887 @@ + 0, // 参与的拼单id + 'delivery' => null, // 配送方式 + 'shop_id' => 0, // 自提门店id + 'linkman' => '', // 自提联系人 + 'phone' => '', // 自提联系电话 + 'coupon_id' => 0, // 优惠券id + 'is_use_points' => 0, // 是否使用积分抵扣 + 'remark' => '', // 买家留言 + 'pay_type' => PayTypeEnum::WECHAT, // 支付方式 + 'order_type' => 20, // 下单类型 10 => 单独购买,20 => 拼团 + ]; + + /** + * 订单结算的规则 + * @var array + */ + private $checkoutRule = [ + 'is_user_grade' => true, // 会员等级折扣 + 'is_coupon' => true, // 优惠券抵扣 + 'is_use_points' => true, // 是否使用积分抵扣 + 'is_dealer' => true, // 是否开启分销 + ]; + + /** + * 订单来源 + * @var array + */ + private $orderSource = [ + 'source' => OrderSourceEnum::MASTER, + 'source_id' => 0, + ]; + + /** + * 订单结算数据 + * @var array + */ + private $orderData = []; + + /** + * 构造函数 + * Checkout constructor. + */ + public function __construct() + { + $this->model = new OrderModel; + $this->wxapp_id = OrderModel::$wxapp_id; + } + + /** + * 设置结算台请求的参数 + * @param $param + * @return array + */ + public function setParam($param) + { + $this->param = array_merge($this->param, $param); + return $this->getParam(); + } + + /** + * 获取结算台请求的参数 + * @return array + */ + public function getParam() + { + return $this->param; + } + + /** + * 订单结算的规则 + * @param $data + */ + public function setCheckoutRule($data) + { + $this->checkoutRule = array_merge($this->checkoutRule, $data); + } + + /** + * 设置订单来源(普通订单、砍价订单) + * @param $data + */ + public function setOrderSource($data) + { + $this->orderSource = array_merge($this->orderSource, $data); + } + + /** + * 订单确认-砍价活动 + * @param $user + * @param $goodsList + * @return array + * @throws BaseException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function onCheckout($user, $goodsList) + { + $this->user = $user; + $this->goodsList = $goodsList; + // 订单确认-立即购买 + return $this->checkout(); + } + + /** + * 订单结算台 + * @return array + * @throws BaseException + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function checkout() + { + // 整理订单数据 + $this->orderData = $this->getOrderData(); + // 验证商品状态, 是否允许购买 + $this->validateGoodsList(); + // 订单商品总数量 + $orderTotalNum = helper::getArrayColumnSum($this->goodsList, 'total_num'); + // 设置订单商品会员折扣价 + $this->setOrderGoodsGradeMoney(); + // 设置订单商品总金额(不含优惠折扣) + $this->setOrderTotalPrice(); + // 计算可用积分抵扣 + $this->setOrderPoints(); + // 当前用户可用的优惠券列表 + $couponList = $this->getUserCouponList($this->orderData['order_total_price']); + // 计算优惠券抵扣 + $this->setOrderCouponMoney($couponList, $this->param['coupon_id']); + // 计算订单商品的实际付款金额 + $this->setOrderGoodsPayPrice(); + // 设置默认配送方式 + !$this->param['delivery'] && $this->param['delivery'] = current(SettingModel::getItem('store')['delivery_type']); + // 处理配送方式 + if ($this->param['delivery'] == DeliveryTypeEnum::EXPRESS) { + $this->setOrderExpress(); + } elseif ($this->param['delivery'] == DeliveryTypeEnum::EXTRACT) { + $this->param['shop_id'] > 0 && $this->orderData['extract_shop'] = ShopModel::detail($this->param['shop_id']); + } + // 计算订单最终金额 + $this->setOrderPayPrice(); + // 计算订单积分赠送数量 + $this->setOrderPointsBonus(); + // 返回订单数据 + return array_merge([ + 'goods_list' => array_values($this->goodsList), // 商品信息 + 'order_total_num' => $orderTotalNum, // 商品总数量 + 'coupon_list' => array_values($couponList), // 优惠券列表 + 'has_error' => $this->hasError(), + 'error_msg' => $this->getError(), + ], $this->orderData); + } + + /** + * 计算订单可用积分抵扣 + * @return bool + */ + private function setOrderPoints() + { + // 设置默认的商品积分抵扣信息 + $this->setDefaultGoodsPoints(); + // 积分设置 + $setting = SettingModel::getItem('points'); + // 条件:后台开启下单使用积分抵扣 + if (!$setting['is_shopping_discount'] || !$this->checkoutRule['is_use_points']) { + return false; + } + // 条件:订单金额满足[?]元 + if (helper::bccomp($setting['discount']['full_order_price'], $this->orderData['order_total_price']) === 1) { + return false; + } + // 计算订单商品最多可抵扣的积分数量 + $this->setOrderGoodsMaxPointsNum(); + // 订单最多可抵扣的积分总数量 + $maxPointsNumCount = helper::getArrayColumnSum($this->goodsList, 'max_points_num'); + // 实际可抵扣的积分数量 + $actualPointsNum = min($maxPointsNumCount, $this->user['points']); + if ($actualPointsNum < 1) { + return false; + } + // 计算订单商品实际抵扣的积分数量和金额 + $GoodsDeduct = new PointsDeductService($this->goodsList); + $GoodsDeduct->setGoodsPoints($maxPointsNumCount, $actualPointsNum); + // 积分抵扣总金额 + $orderPointsMoney = helper::getArrayColumnSum($this->goodsList, 'points_money'); + $this->orderData['points_money'] = helper::number2($orderPointsMoney); + // 积分抵扣总数量 + $this->orderData['points_num'] = $actualPointsNum; + // 允许积分抵扣 + $this->orderData['is_allow_points'] = true; + return true; + } + + /** + * 计算订单商品最多可抵扣的积分数量 + * @return bool + */ + private function setOrderGoodsMaxPointsNum() + { + // 积分设置 + $setting = SettingModel::getItem('points'); + foreach ($this->goodsList as &$goods) { + // 商品不允许积分抵扣 + if (!$goods['is_points_discount']) continue; + // 积分抵扣比例 + $deductionRatio = helper::bcdiv($setting['discount']['max_money_ratio'], 100); + // 最多可抵扣的金额 + $maxPointsMoney = helper::bcmul($goods['total_price'], $deductionRatio); + // 最多可抵扣的积分数量 + $goods['max_points_num'] = helper::bcdiv($maxPointsMoney, $setting['discount']['discount_ratio'], 0); + } + return true; + } + + /** + * 设置默认的商品积分抵扣信息 + * @return bool + */ + private function setDefaultGoodsPoints() + { + foreach ($this->goodsList as &$goods) { + // 最多可抵扣的积分数量 + $goods['max_points_num'] = 0; + // 实际抵扣的积分数量 + $goods['points_num'] = 0; + // 实际抵扣的金额 + $goods['points_money'] = 0.00; + } + return true; + } + + /** + * 整理订单数据(结算台初始化) + * @return array + */ + private function getOrderData() + { + // 系统支持的配送方式 (后台设置) + $deliveryType = SettingModel::getItem('store')['delivery_type']; + // 积分设置 + $pointsSetting = SettingModel::getItem('points'); + return [ + // 订单类型 + 'order_type' => $this->param['order_type'], + // 配送类型 + 'delivery' => $this->param['delivery'] > 0 ? $this->param['delivery'] : $deliveryType[0], + // 默认地址 + 'address' => $this->user['address_default'], + // 是否存在收货地址 + 'exist_address' => $this->user['address_id'] > 0, + // 配送费用 + 'express_price' => 0.00, + // 当前用户收货城市是否存在配送规则中 + 'intra_region' => true, + // 自提门店信息 + 'extract_shop' => [], + // 是否允许使用积分抵扣 + 'is_allow_points' => false, + // 是否使用积分抵扣 + 'is_use_points' => $this->param['is_use_points'], + // 积分抵扣金额 + 'points_money' => 0.00, + // 赠送的积分数量 + 'points_bonus' => 0, + // 支付方式 + 'pay_type' => $this->param['pay_type'], + // 系统设置 + 'setting' => [ + 'delivery' => $deliveryType, // 支持的配送方式 + 'points_name' => $pointsSetting['points_name'], // 积分名称 + 'points_describe' => $pointsSetting['describe'], // 积分说明 + ], + // 记忆的自提联系方式 + 'last_extract' => UserService::getLastExtract($this->user['user_id']), + // todo: 兼容处理 + 'deliverySetting' => $deliveryType, + ]; + } + + /** + * 当前用户可用的优惠券列表 + * @param $orderTotalPrice + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getUserCouponList($orderTotalPrice) + { + // 是否开启优惠券折扣 + if (!$this->checkoutRule['is_coupon'] || !SharingSettingModel::getItem('basic')['is_coupon']) { + return []; + } + return UserCouponModel::getUserCouponList($this->user['user_id'], $orderTotalPrice); + } + + /** + * 验证订单商品的状态 + */ + private function validateGoodsList() + { + foreach ($this->goodsList as $goods) { + // 判断商品是否下架 + if ($goods['goods_status']['value'] != 10) { + $this->setError("很抱歉,商品 [{$goods['goods_name']}] 已下架"); + } + // 判断商品库存 + if ($goods['total_num'] > $goods['goods_sku']['stock_num']) { + $this->setError("很抱歉,商品 [{$goods['goods_name']}] 库存不足"); + } + } + } + + /** + * 设置订单的商品总金额(不含优惠折扣) + */ + private function setOrderTotalPrice() + { + // 订单商品的总金额(不含优惠券折扣) + $this->orderData['order_total_price'] = helper::number2(helper::getArrayColumnSum($this->goodsList, 'total_price')); + } + + /** + * 设置订单的实际支付金额(含配送费) + */ + private function setOrderPayPrice() + { + // 订单金额(含优惠折扣) + $this->orderData['order_price'] = helper::number2(helper::getArrayColumnSum($this->goodsList, 'total_pay_price')); + // 订单实付款金额(订单金额 + 运费) + $this->orderData['order_pay_price'] = helper::number2(helper::bcadd($this->orderData['order_price'], $this->orderData['express_price'])); + } + + /** + * 计算订单积分赠送数量 + * @return bool + */ + private function setOrderPointsBonus() + { + // 初始化商品积分赠送数量 + foreach ($this->goodsList as &$goods) { + $goods['points_bonus'] = 0; + } + // 积分设置 + $setting = SettingModel::getItem('points'); + // 条件:后台开启开启购物送积分 + if (!$setting['is_shopping_gift']) { + return false; + } + // 设置商品积分赠送数量 + foreach ($this->goodsList as &$goods) { + // 积分赠送比例 + $ratio = $setting['gift_ratio'] / 100; + // 计算抵扣积分数量 + $goods['points_bonus'] = $goods['is_points_gift'] ? helper::bcmul($goods['total_pay_price'], $ratio, 0) : 0; + } + // 订单积分赠送数量 + $this->orderData['points_bonus'] = helper::getArrayColumnSum($this->goodsList, 'points_bonus'); + return true; + } + + /** + * 计算订单商品的实际付款金额 + * @return bool + */ + private function setOrderGoodsPayPrice() + { + // 商品总价 - 优惠抵扣 + foreach ($this->goodsList as &$goods) { + // 减去优惠券抵扣金额 + $value = helper::bcsub($goods['total_price'], $goods['coupon_money']); + // 减去积分抵扣金额 + if ($this->orderData['is_allow_points'] && $this->orderData['is_use_points']) { + $value = helper::bcsub($value, $goods['points_money']); + } + $goods['total_pay_price'] = helper::number2($value); + } + return true; + } + + /** + * 设置订单商品会员折扣价 + * @return bool + */ + private function setOrderGoodsGradeMoney() + { + // 设置默认数据 + helper::setDataAttribute($this->goodsList, [ + // 标记参与会员折扣 + 'is_user_grade' => false, + // 会员等级抵扣的金额 + 'grade_ratio' => 0, + // 会员折扣的商品单价 + 'grade_goods_price' => 0.00, + // 会员折扣的总额差 + 'grade_total_money' => 0.00, + ], true); + + // 是否开启会员等级折扣 + if (!$this->checkoutRule['is_user_grade']) { + return false; + } + // 会员等级状态 + if (!( + $this->user['grade_id'] > 0 && !empty($this->user['grade']) + && !$this->user['grade']['is_delete'] && $this->user['grade']['status'] + )) { + return false; + } + // 计算抵扣金额 + foreach ($this->goodsList as &$goods) { + // 判断商品是否参与会员折扣 + if (!$goods['is_enable_grade']) { + continue; + } + // 商品单独设置了会员折扣 + if ($goods['is_alone_grade'] && isset($goods['alone_grade_equity'][$this->user['grade_id']])) { + // 折扣比例 + $discountRatio = helper::bcdiv($goods['alone_grade_equity'][$this->user['grade_id']], 10); + } else { + // 折扣比例 + $discountRatio = helper::bcdiv($this->user['grade']['equity']['discount'], 10); + } + if ($discountRatio > 0) { + // 会员折扣后的商品总金额 + $gradeTotalPrice = max(0.01, helper::bcmul($goods['total_price'], $discountRatio)); + helper::setDataAttribute($goods, [ + 'is_user_grade' => true, + 'grade_ratio' => $discountRatio, + 'grade_goods_price' => helper::number2(helper::bcmul($goods['goods_price'], $discountRatio), true), + 'grade_total_money' => helper::number2(helper::bcsub($goods['total_price'], $gradeTotalPrice)), + 'total_price' => $gradeTotalPrice, + ], false); + } + } + return true; + } + + /** + * 设置订单优惠券抵扣信息 + * @param array $couponList 当前用户可用的优惠券列表 + * @param int $couponId 当前选择的优惠券id + * @return bool + * @throws BaseException + */ + private function setOrderCouponMoney($couponList, $couponId) + { + // 设置默认数据:订单信息 + helper::setDataAttribute($this->orderData, [ + 'coupon_id' => 0, // 用户优惠券id + 'coupon_money' => 0, // 优惠券抵扣金额 + ], false); + // 设置默认数据:订单商品列表 + helper::setDataAttribute($this->goodsList, [ + 'coupon_money' => 0, // 优惠券抵扣金额 + ], true); + // 是否开启优惠券折扣 + if (!$this->checkoutRule['is_coupon']) { + return false; + } + // 如果没有可用的优惠券,直接返回 + if ($couponId <= 0 || empty($couponList)) { + return true; + } + // 获取优惠券信息 + $couponInfo = helper::getArrayItemByColumn($couponList, 'user_coupon_id', $couponId); + if ($couponInfo == false) { + throw new BaseException(['msg' => '未找到优惠券信息']); + } + // 计算订单商品优惠券抵扣金额 + $goodsListTemp = helper::getArrayColumns($this->goodsList, ['total_price']); + $CouponMoney = new GoodsDeductService; + $completed = $CouponMoney->setGoodsCouponMoney($goodsListTemp, $couponInfo['reduced_price']); + // 分配订单商品优惠券抵扣金额 + foreach ($this->goodsList as $key => &$goods) { + $goods['coupon_money'] = $completed[$key]['coupon_money'] / 100; + } + // 记录订单优惠券信息 + $this->orderData['coupon_id'] = $couponId; + $this->orderData['coupon_money'] = helper::number2($CouponMoney->getActualReducedMoney() / 100); + return true; + } + + /** + * 订单配送-快递配送 + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function setOrderExpress() + { + // 设置默认数据:配送费用 + helper::setDataAttribute($this->goodsList, [ + 'express_price' => 0, + ], true); + // 当前用户收货城市id + $cityId = $this->user['address_default'] ? $this->user['address_default']['city_id'] : null; + // 初始化配送服务类 + $ExpressService = new ExpressService($cityId, $this->goodsList, OrderTypeEnum::SHARING); + // 验证商品是否在配送范围 + $isIntraRegion = $ExpressService->isIntraRegion(); + if ($cityId > 0 && $isIntraRegion == false) { + $notInRuleGoodsName = $ExpressService->getNotInRuleGoodsName(); + $this->setError("很抱歉,您的收货地址不在商品 [{$notInRuleGoodsName}] 的配送范围内"); + } + // 订单总运费金额 + $this->orderData['intra_region'] = $isIntraRegion; + $this->orderData['express_price'] = $ExpressService->getDeliveryFee(); + return true; + } + + /** + * 创建新订单 + * @param array $order 订单信息 + * @return bool + * @throws \Exception + */ + public function createOrder($order) + { + // 如果是参与拼单,则记录拼单id + $order['active_id'] = $this->param['active_id']; + // 表单验证 + if (!$this->validateOrderForm($order, $this->param['linkman'], $this->param['phone'])) { + return false; + } + // 创建新的订单 + $status = $this->model->transaction(function () use ($order) { + // 创建订单事件 + return $this->createOrderEvent($order); + }); + // 余额支付标记订单已支付 + if ($status && $order['pay_type'] == PayTypeEnum::BALANCE) { + return $this->model->onPaymentByBalance($this->model['order_no']); + } + return $status; + } + + /** + * 创建订单事件 + * @param $order + * @return bool + * @throws BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + private function createOrderEvent($order) + { + // 新增订单记录 + $status = $this->add($order, $this->param['remark']); + if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) { + // 记录收货地址 + $this->saveOrderAddress($order['address']); + } elseif ($order['delivery'] == DeliveryTypeEnum::EXTRACT) { + // 记录自提信息 + $this->saveOrderExtract($this->param['linkman'], $this->param['phone']); + } + // 保存订单商品信息 + $this->saveOrderGoods($order); + // 更新商品库存 (针对下单减库存的商品) + $this->updateGoodsStockNum($order['goods_list']); + // 设置优惠券使用状态 + UserCouponModel::setIsUse($this->param['coupon_id']); + // 积分抵扣情况下扣除用户积分 + if ($order['is_allow_points'] && $order['is_use_points'] && $order['points_num'] > 0) { + $describe = "用户消费:{$this->model['order_no']}"; + $this->user->setIncPoints(-$order['points_num'], $describe); + } + // 获取订单详情 + $detail = OrderModel::getUserOrderDetail($this->model['order_id'], $this->user['user_id']); + // 记录分销商订单 + if ($this->checkoutRule['is_dealer'] && SharingSettingModel::getItem('basic')['is_dealer']) { + DealerOrderModel::createOrder($detail, OrderTypeEnum::SHARING); + } + return $status; + } + + /** + * 构建支付请求的参数 + * @return array + * @throws BaseException + * @throws \think\exception\DbException + */ + public function onOrderPayment() + { + return PaymentService::orderPayment($this->user, $this->model, $this->param['pay_type']); + } + + /** + * 表单验证 (订单提交) + * @param array $order 订单信息 + * @param string $linkman 联系人 + * @param string $phone 联系电话 + * @return bool + * @throws \think\exception\DbException + */ + private function validateOrderForm(&$order, $linkman, $phone) + { + if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) { + if (empty($order['address'])) { + $this->error = '您还没有选择配送地址'; + return false; + } + } + if ($order['delivery'] == DeliveryTypeEnum::EXTRACT) { + if (empty($order['extract_shop'])) { + $this->error = '您还没有选择自提门店'; + return false; + } + if (empty($linkman) || empty($phone)) { + $this->error = '您还没有填写联系人和电话'; + return false; + } + } + // 余额支付时判断用户余额是否足够 + if ($order['pay_type'] == PayTypeEnum::BALANCE) { + if ($this->user['balance'] < $order['order_pay_price']) { + $this->error = '您的余额不足,无法使用余额支付'; + return false; + } + } + // 验证拼单id是否合法 + if ($order['active_id'] > 0) { + // 拼单详情 + $detail = ActiveModel::detail($order['active_id']); + if (empty($detail)) { + $this->error = '很抱歉,拼单不存在'; + return false; + } + // 验证当前拼单是否允许加入新成员 + if (!$detail->checkAllowJoin()) { + $this->error = $detail->getError(); + return false; + } + } + return true; + } + + /** + * 当前订单是否存在和使用积分抵扣 + * @param $order + * @return bool + */ + private function isExistPointsDeduction($order) + { + return $order['is_allow_points'] && $order['is_use_points']; + } + + /** + * 新增订单记录 + * @param $order + * @param string $remark + * @return false|int + */ + private function add(&$order, $remark = '') + { + // 当前订单是否存在和使用积分抵扣 + $isExistPointsDeduction = $this->isExistPointsDeduction($order); + // 订单数据 + $data = [ + 'user_id' => $this->user['user_id'], + 'order_type' => $order['order_type'], + 'active_id' => $order['active_id'], + 'order_no' => $this->model->orderNo(), + 'total_price' => $order['order_total_price'], + 'order_price' => $order['order_price'], + 'coupon_id' => $order['coupon_id'], + 'coupon_money' => $order['coupon_money'], + 'points_money' => $isExistPointsDeduction ? $order['points_money'] : 0.00, + 'points_num' => $isExistPointsDeduction ? $order['points_num'] : 0, + 'pay_price' => $order['order_pay_price'], + 'delivery_type' => $order['delivery'], + 'pay_type' => $order['pay_type'], + 'buyer_remark' => trim($remark), +// 'order_source' => $this->orderSource['source'], +// 'order_source_id' => $this->orderSource['source_id'], + 'points_bonus' => $order['points_bonus'], + 'wxapp_id' => $this->wxapp_id, + ]; + if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) { + $data['express_price'] = $order['express_price']; + } elseif ($order['delivery'] == DeliveryTypeEnum::EXTRACT) { + $data['extract_shop_id'] = $order['extract_shop']['shop_id']; + } + // 保存订单记录 + return $this->model->save($data); + } + + /** + * 保存订单商品信息 + * @param $order + * @return int + */ + private function saveOrderGoods(&$order) + { + // 当前订单是否存在和使用积分抵扣 + $isExistPointsDeduction = $this->isExistPointsDeduction($order); + // 订单商品列表 + $goodsList = []; + foreach ($order['goods_list'] as $goods) { + /* @var GoodsModel $goods */ + $goodsList[] = [ + 'user_id' => $this->user['user_id'], + 'wxapp_id' => $this->wxapp_id, + 'goods_id' => $goods['goods_id'], + 'goods_name' => $goods['goods_name'], + 'image_id' => $goods['image'][0]['image_id'], + 'people' => $goods['people'], + 'group_time' => $goods['group_time'], + 'is_alone' => $goods['is_alone'], + 'deduct_stock_type' => $goods['deduct_stock_type'], + 'spec_type' => $goods['spec_type'], + 'spec_sku_id' => $goods['goods_sku']['spec_sku_id'], + 'goods_sku_id' => $goods['goods_sku']['goods_sku_id'], + 'goods_attr' => $goods['goods_sku']['goods_attr'], + 'content' => $goods['content'], + 'goods_no' => $goods['goods_sku']['goods_no'], + 'goods_price' => $goods['goods_sku']['goods_price'], + 'line_price' => $goods['goods_sku']['line_price'], + 'goods_weight' => $goods['goods_sku']['goods_weight'], + 'is_user_grade' => (int)$goods['is_user_grade'], + 'grade_ratio' => $goods['grade_ratio'], + 'grade_goods_price' => $goods['grade_goods_price'], + 'grade_total_money' => $goods['grade_total_money'], + 'coupon_money' => $goods['coupon_money'], + 'points_money' => $isExistPointsDeduction ? $goods['points_money'] : 0.00, + 'points_num' => $isExistPointsDeduction ? $goods['points_num'] : 0, + 'points_bonus' => $goods['points_bonus'], + 'total_num' => $goods['total_num'], + 'total_price' => $goods['total_price'], + 'total_pay_price' => $goods['total_pay_price'], + 'is_ind_dealer' => $goods['is_ind_dealer'], + 'dealer_money_type' => $goods['dealer_money_type'], + 'first_money' => $goods['first_money'], + 'second_money' => $goods['second_money'], + 'third_money' => $goods['third_money'], + ]; + } + return $this->model->goods()->saveAll($goodsList); + } + + /** + * 更新商品库存 (针对下单减库存的商品) + * @param $goods_list + * @throws \Exception + */ + private function updateGoodsStockNum($goods_list) + { + $deductStockData = []; + foreach ($goods_list as $goods) { + // 下单减库存 + $goods['deduct_stock_type'] == 10 && $deductStockData[] = [ + 'goods_sku_id' => $goods['goods_sku']['goods_sku_id'], + 'stock_num' => ['dec', $goods['total_num']] + ]; + } + !empty($deductStockData) && (new GoodsSkuModel)->isUpdate()->saveAll($deductStockData); + } + + /** + * 记录收货地址 + * @param $address + * @return false|\think\Model + */ + private function saveOrderAddress($address) + { + if ($address['region_id'] == 0 && !empty($address['district'])) { + $address['detail'] = $address['district'] . ' ' . $address['detail']; + } + return $this->model->address()->save([ + 'user_id' => $this->user['user_id'], + 'wxapp_id' => $this->wxapp_id, + 'name' => $address['name'], + 'phone' => $address['phone'], + 'province_id' => $address['province_id'], + 'city_id' => $address['city_id'], + 'region_id' => $address['region_id'], + 'detail' => $address['detail'], + ]); + } + + /** + * 保存上门自提联系人 + * @param $linkman + * @param $phone + * @return false|\think\Model + */ + private function saveOrderExtract($linkman, $phone) + { + // 记忆上门自提联系人(缓存),用于下次自动填写 + UserService::setLastExtract($this->model['user_id'], trim($linkman), trim($phone)); + // 保存上门自提联系人(数据库) + return $this->model->extract()->save([ + 'linkman' => trim($linkman), + 'phone' => trim($phone), + 'user_id' => $this->model['user_id'], + 'wxapp_id' => $this->wxapp_id, + ]); + } + + /** + * 设置错误信息 + * @param $error + */ + protected function setError($error) + { + empty($this->error) && $this->error = $error; + } + + /** + * 获取错误信息 + * @return mixed + */ + public function getError() + { + return $this->error; + } + + /** + * 是否存在错误 + * @return bool + */ + public function hasError() + { + return !empty($this->error); + } + +} \ No newline at end of file diff --git a/source/application/api/service/sharing/order/PaySuccess.php b/source/application/api/service/sharing/order/PaySuccess.php new file mode 100644 index 0000000..6c8017d --- /dev/null +++ b/source/application/api/service/sharing/order/PaySuccess.php @@ -0,0 +1,152 @@ +model = OrderModel::getPayDetail($orderNo); + if (!empty($this->model)) { + $this->wxappId = $this->model['wxapp_id']; + } + // 获取用户信息 + $this->user = UserModel::detail($this->model['user_id']); + } + + /** + * 获取订单详情 + * @return OrderModel|null + */ + public function getOrderInfo() + { + return $this->model; + } + + /** + * 订单支付成功业务处理 + * @param $payType + * @param array $payData + * @return bool + */ + public function onPaySuccess($payType, $payData = []) + { + if (empty($this->model)) { + $this->error = '未找到该订单信息'; + return false; + } + // 更新付款状态 + $status = $this->updatePayStatus($payType, $payData); + // 订单支付成功行为 + if ($status == true) { + Hook::listen('order_pay_success', $this->model, OrderTypeEnum::SHARING); + } + return $status; + } + + /** + * 更新付款状态 + * @param $payType + * @param $payData + * @return bool + */ + private function updatePayStatus($payType, $payData) + { + // 验证余额支付时用户余额是否满足 + if ($payType == PayTypeEnum::BALANCE) { + if ($this->user['balance'] < $this->model['pay_price']) { + $this->error = '用户余额不足,无法使用余额支付'; + return false; + } + } + $this->model->transaction(function () use ($payType, $payData) { + // 更新商品库存、销量 + (new GoodsModel)->updateStockSales($this->model['goods']); + // 更新拼单记录 + $this->saveSharingActive($this->model['goods'][0]); + // 整理订单信息 + $order = ['pay_type' => $payType, 'pay_status' => 20, 'pay_time' => time()]; + if ($payType == PayTypeEnum::WECHAT) { + $order['transaction_id'] = $payData['transaction_id']; + } + // 更新订单状态 + $this->model->save($order); + // 累积用户总消费金额 + $this->user->setIncPayMoney($this->model['pay_price']); + // 余额支付 + if ($payType == PayTypeEnum::BALANCE) { + // 更新用户余额 + $this->user->setDec('balance', $this->model['pay_price']); + BalanceLogModel::add(SceneEnum::CONSUME, [ + 'user_id' => $this->user['user_id'], + 'money' => -$this->model['pay_price'], + ], ['order_no' => $this->model['order_no']]); + } + // 微信支付 + if ($payType == PayTypeEnum::WECHAT) { + // 更新prepay_id记录 + WxappPrepayIdModel::updatePayStatus($this->model['order_id'], OrderTypeEnum::SHARING); + } + }); + return true; + } + + /** + * 更新拼单记录 + * @param $goods + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + private function saveSharingActive($goods) + { + // 新增/更新拼单记录 + if ($this->model['order_type']['value'] != 20) { + return false; + } + // 拼单模型 + $ActiveModel = new ActiveModel; + // 参与他人的拼单, 更新拼单记录 + if ($this->model['active_id'] > 0) { + $ActiveModel = $ActiveModel::detail($this->model['active_id']); + return $ActiveModel->onUpdate($this->model['user_id'], $this->model['order_id']); + } + // 自己发起的拼单, 新增拼单记录 + $ActiveModel->onCreate($this->model['user_id'], $this->model['order_id'], $goods); + // 记录拼单id + $this->model['active_id'] = $ActiveModel['active_id']; + return true; + } + +} \ No newline at end of file diff --git a/source/application/api/service/sharp/Active.php b/source/application/api/service/sharp/Active.php new file mode 100644 index 0000000..2210bbe --- /dev/null +++ b/source/application/api/service/sharp/Active.php @@ -0,0 +1,357 @@ +ActiveModel = new ActiveModel; + $this->ActiveTimeModel = new ActiveTimeModel; + } + + /** + * 获取秒杀活动会场首页数据 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getHallIndex() + { + // 获取秒杀首页顶部菜单 + $tabbar = $this->getActiveTabbar(); + if (empty($tabbar)) return ['tabbar' => [], 'goodsList' => []]; + // 获取活动商品 + $goodsList = $this->getGoodsListByActiveTimeId($tabbar[0]['active_time_id']); + return compact('tabbar', 'goodsList'); + } + + /** + * 获取秒杀活动组件数据 + * @param array $goodsParm + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getSharpModular($goodsParm = []) + { + // 获取秒杀活动列表 + $tabbar = $this->getActiveTabbar(); + if (empty($tabbar)) return ['active' => null, 'goodsList' => []]; + return [ + // 秒杀活动 + 'active' => $tabbar[0], + // 活动商品列表 + 'goodsList' => $this->getGoodsListByActiveTimeId($tabbar[0]['active_time_id'], $goodsParm), + ]; + } + + /** + * 根据活动场次ID获取商品列表 + * @param int $activeTimeId + * @param array $goodsParm + * @return false|\PDOStatement|string|\think\Collection + */ + public function getGoodsListByActiveTimeId($activeTimeId, $goodsParm = []) + { + return ActiveGoodsModel::getGoodsListByActiveTimeId($activeTimeId, $goodsParm); + } + + /** + * 获取活动商品详情 + * @param $activeTimeId + * @param $sharpGoodsId + * @return array|bool + * @throws \think\exception\DbException + */ + public function getyActiveGoodsDetail($activeTimeId, $sharpGoodsId) + { + // 活动详情 + $active = $this->getGoodsActive($activeTimeId, $sharpGoodsId); + if (empty($active)) return false; + // 商品详情 + $model = new ActiveGoodsModel; + $goods = $model->getGoodsActiveDetail($active, $sharpGoodsId, true); + if (empty($goods)) { + $this->error = $model->getError(); + return false; + } + // 商品多规格信息 + $goods['goods_multi_spec'] = (new SharpGoodsModel)->getSpecData($goods, $goods['sku']); + return compact('active', 'goods'); + } + + /** + * 获取订单提交的商品列表 + * @param $activeTimeId + * @param $sharpGoodsId + * @param $goodsSkuId + * @param $goodsNum + * @return array|bool + * @throws \think\exception\DbException + */ + public function getCheckoutGoodsList($activeTimeId, $sharpGoodsId, $goodsSkuId, $goodsNum) + { + // 活动详情 + $active = $this->getGoodsActive($activeTimeId, $sharpGoodsId); + if (empty($active)) return false; + // 商品详情 + $model = new ActiveGoodsModel; + $goods = $model->getGoodsActiveDetail($active, $sharpGoodsId, false); + if (empty($goods)) return false; + // 商品sku信息 + $goods['goods_sku'] = GoodsModel::getGoodsSku($goods, $goodsSkuId); + // 商品列表 + $goodsList = [$goods->hidden(['category', 'content', 'image', 'sku'])]; + foreach ($goodsList as &$item) { + // 商品价格 + $item['goods_price'] = $item['goods_sku']['seckill_price']; + $item['line_price'] = $item['goods_sku']['original_price']; + // 商品id + $item['spec_sku_id'] = $item['goods_sku']['spec_sku_id']; + $item['goods_source_id'] = $item['sharp_goods_id']; + // 商品购买数量 + $item['total_num'] = $goodsNum; + // 商品购买总金额 + $item['total_price'] = helper::bcmul($item['goods_price'], $goodsNum); + } + return $goodsList; + } + + /** + * 活动详情 + * @param $activeTimeId + * @param $sharpGoodsId + * @return array|bool + */ + private function getGoodsActive($activeTimeId, $sharpGoodsId) + { + // 获取活动商品的关联信息 + $model = $this->getActiveGoods($activeTimeId, $sharpGoodsId); + if (empty($model) || !($model['active']['status'] && $model['active_time']['status'])) { + $this->error = '很抱歉,该活动不存在或已结束'; + return false; + } + // 整理数据 + $startTime = $model['active']['active_date'] + ($model->active_time->getData('active_time') * 60 * 60); + $endTime = $startTime + (1 * 60 * 60); + $activeStatus = $this->getActivcGoodsStatus($startTime, $endTime); + $data = [ + 'active_id' => $model['active_id'], + 'active_time_id' => $model['active_time_id'], + 'active_time' => $model['active_time']['active_time'], + 'sales_actual' => $model['sales_actual'], + 'start_time' => $this->onFormatTime($startTime), + 'end_time' => $this->onFormatTime($endTime), + 'active_status' => $activeStatus, + 'count_down_time' => $this->getGoodsActiveCountDownTime($activeStatus, $startTime, $endTime), + 'wxapp_id' => $model['wxapp_id'], + ]; + return $data; + } + + /** + * 获取活动商品的关联信息 + * @param $activeTimeId + * @param $sharpGoodsId + * @return mixed + */ + public function getActiveGoods($activeTimeId, $sharpGoodsId) + { + static $data = []; + if (!isset($data["{$activeTimeId}_{$sharpGoodsId}"])) { + $model = ActiveGoodsModel::getGoodsActive($activeTimeId, $sharpGoodsId); + !empty($model) && $data["{$activeTimeId}_{$sharpGoodsId}"] = $model; + } + return $data["{$activeTimeId}_{$sharpGoodsId}"]; + } + + /** + * 活动商品倒计时 + * @param $activeStatus + * @param $startTime + * @param $endTime + * @return bool|false|string + */ + private function getGoodsActiveCountDownTime($activeStatus, $startTime, $endTime) + { + if ($activeStatus == GoodsStatusEnum::STATE_BEGIN) { + return $this->onFormatTime($startTime); + } + if ($activeStatus == GoodsStatusEnum::STATE_SOON) { + return $this->onFormatTime($endTime); + } + return false; + } + + /** + * 活动商品状态 + * @param $startTime + * @param $endTime + * @return int + */ + private function getActivcGoodsStatus($startTime, $endTime) + { + $nowTime = time(); + if ($nowTime < $startTime) { + return GoodsStatusEnum::STATE_SOON; + } + if ($nowTime >= $startTime && $nowTime < $endTime) { + return GoodsStatusEnum::STATE_BEGIN; + } + return GoodsStatusEnum::STATE_END; + } + + /** + * 获取秒杀首页顶部菜单 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getActiveTabbar() + { + // 当天的活动 + $todyActive = $this->ActiveModel->getNowActive(); + $data = []; + if (!empty($todyActive)) { + // 当前进行中的活动 + $data[] = $this->getBeginActive($todyActive); + // 获取即将开始的活动 + $data = array_merge($data, $this->getSoonActive($todyActive)); + } + // 获取预告的活动 + $data[] = $this->getNoticeActive(); + return array_values(array_filter($data)); + } + + /** + * 获取当前进行中的活动 + * @param $todyActive + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getBeginActive($todyActive) + { + // 当前的时间点 + $model = $this->ActiveTimeModel->getNowActiveTime($todyActive['active_id']); + if (empty($model)) return []; + // 整理数据 + $startTime = $todyActive['active_date'] + ($model->getData('active_time') * 60 * 60); + $endTime = $startTime + (1 * 60 * 60); + return [ + 'active_id' => $todyActive['active_id'], + 'active_time_id' => $model['active_time_id'], + 'active_time' => $model['active_time'], + 'start_time' => $this->onFormatTime($startTime), + 'end_time' => $this->onFormatTime($endTime), + 'count_down_time' => $this->onFormatTime($endTime), + 'status' => ActiveStatusEnum::ACTIVE_STATE_BEGIN, + 'status_text' => '已开抢', + 'status_text2' => '正在疯抢', + 'sharp_modular_text' => '正在疯抢', + ]; + } + + /** + * 获取即将开始的活动 + * @param $todyActive + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getSoonActive($todyActive) + { + // 当前的时间点 + $list = $this->ActiveTimeModel->getNextActiveTimes($todyActive['active_id']); + if (empty($list) || $list->isEmpty()) return []; + // 整理数据 + $data = []; + foreach ($list as $item) { + $startTime = $todyActive['active_date'] + ($item->getData('active_time') * 60 * 60); + $endTime = $startTime + (1 * 60 * 60); + $data[] = [ + 'active_id' => $todyActive['active_id'], + 'active_time_id' => $item['active_time_id'], + 'active_time' => $item['active_time'], + 'start_time' => $this->onFormatTime($startTime), + 'end_time' => $this->onFormatTime($endTime), + 'count_down_time' => $this->onFormatTime($startTime), + 'status' => ActiveStatusEnum::ACTIVE_STATE_SOON, + 'status_text' => '即将开抢', + 'status_text2' => '即将开抢', + 'sharp_modular_text' => "{$item['active_time']} 场预告", + ]; + } + return $data; + } + + /** + * 获取预告的活动 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getNoticeActive() + { + // 下一场活动 + $nextActive = $this->ActiveModel->getNextActive(); + if (empty($nextActive)) return []; + // 第一个时间点 + $model = $this->ActiveTimeModel->getRecentActiveTime($nextActive['active_id']); + if (empty($model)) return []; + // 整理数据 + $startTime = $nextActive['active_date'] + ($model->getData('active_time') * 60 * 60); + $endTime = $startTime + (1 * 60 * 60); + return [ + 'active_id' => $nextActive['active_id'], + 'active_time_id' => $model['active_time_id'], + 'active_time' => $model['active_time'], + 'start_time' => $this->onFormatTime($startTime), + 'end_time' => $this->onFormatTime($endTime), + 'count_down_time' => $this->onFormatTime($startTime), + 'status' => ActiveStatusEnum::ACTIVE_STATE_NOTICE, + 'status_text' => '预告', + 'status_text2' => $this->onFormatTime($startTime) . ' 开始', + 'sharp_modular_text' => $this->onFormatTime($startTime) . ' 开始', + ]; + } + + /** + * 将时间戳格式化为日期时间 + * @param $timeStamp + * @return false|string + */ + private function onFormatTime($timeStamp) + { + return date('Y-m-d H:i', $timeStamp); + } + +} \ No newline at end of file diff --git a/source/application/api/service/sharp/Order.php b/source/application/api/service/sharp/Order.php new file mode 100644 index 0000000..1f3def8 --- /dev/null +++ b/source/application/api/service/sharp/Order.php @@ -0,0 +1,38 @@ +setBaseQuery('order', [ + ['order_goods', 'order_id'], + ]) + ->where('order_goods.user_id', '=', $userId) + ->where('order_goods.goods_id', '=', $goodsId) + ->where('order.order_source', '=', OrderSourceEnum::SHARP) + ->where('order.order_status', '<>', OrderStatusEnum::CANCELLED) + ->where('order.is_delete', '=', 0) + ->sum('order_goods.total_num'); + return $totalNum; + } +} \ No newline at end of file diff --git a/source/application/api/service/sharp/order/PaySuccess.php b/source/application/api/service/sharp/order/PaySuccess.php new file mode 100644 index 0000000..3f22029 --- /dev/null +++ b/source/application/api/service/sharp/order/PaySuccess.php @@ -0,0 +1,47 @@ +updateActiveGoodsAales($activeTimeId, $order['goods']); + } + + /** + * 更新活动会场的商品实际销量 + * @param $activeTimeId + * @param $goodsList + * @return bool + */ + private function updateActiveGoodsAales($activeTimeId, $goodsList) + { + $data = []; + foreach ($goodsList as $goods) { + $data[] = [ + 'data' => ['sales_actual' => ['inc', $goods['total_num']]], + 'where' => [ + 'active_time_id' => $activeTimeId, + 'sharp_goods_id' => $goods['goods_source_id'], + ], + ]; + } + return !empty($data) && (new ActiveGoodsModel)->updateAll($data); + } +} \ No newline at end of file diff --git a/source/application/api/tags.php b/source/application/api/tags.php new file mode 100644 index 0000000..8c80ff4 --- /dev/null +++ b/source/application/api/tags.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- + +// 应用行为扩展定义文件 +return [ + + // 订单支付成功行为管理 + 'order_pay_success' => [ + 'app\\api\\behavior\\order\\PaySuccess' + ], + +]; diff --git a/source/application/api/validate/order/Checkout.php b/source/application/api/validate/order/Checkout.php new file mode 100644 index 0000000..a94e2ab --- /dev/null +++ b/source/application/api/validate/order/Checkout.php @@ -0,0 +1,50 @@ + [ + 'require', + 'number', + 'gt' => 0 + ], + + // 购买数量 + 'goods_num' => [ + 'require', + 'number', + 'gt' => 0 + ], + + // 商品sku_id + 'goods_sku_id' => [ + 'require', + ], + +// // 购物车id集 +// 'cart_ids' => [ +// 'require', +// ], + + ]; + + /** + * 验证场景 + * @var array + */ + protected $scene = [ + 'buyNow' => ['goods_id', 'goods_num', 'goods_sku_id'], +// 'cart' => ['cart_ids'], + ]; + +} diff --git a/source/application/api/validate/sharing/order/Checkout.php b/source/application/api/validate/sharing/order/Checkout.php new file mode 100644 index 0000000..58fe2fa --- /dev/null +++ b/source/application/api/validate/sharing/order/Checkout.php @@ -0,0 +1,44 @@ + [ + 'require', + 'number', + 'gt' => 0 + ], + + // 购买数量 + 'goods_num' => [ + 'require', + 'number', + 'gt' => 0 + ], + + // 商品sku_id + 'goods_sku_id' => [ + 'require', + ], + + ]; + + /** + * 验证场景 + * @var array + */ + protected $scene = [ + 'buyNow' => ['goods_id', 'goods_num', 'goods_sku_id'], + ]; + +} diff --git a/source/application/common.php b/source/application/common.php new file mode 100644 index 0000000..26a8e61 --- /dev/null +++ b/source/application/common.php @@ -0,0 +1,348 @@ +' . print_r($content, true); + $is_die && die(); +} + +/** + * 驼峰命名转下划线命名 + * @param $str + * @return string + */ +function toUnderScore($str) +{ + $dstr = preg_replace_callback('/([A-Z]+)/', function ($matchs) { + return '_' . strtolower($matchs[0]); + }, $str); + return trim(preg_replace('/_{2,}/', '_', $dstr), '_'); +} + +/** + * 生成密码hash值 + * @param $password + * @return string + */ +function yoshop_hash($password) +{ + return md5(md5($password) . 'yoshop_salt_SmTRx'); +} + +/** + * 获取当前域名及根路径 + * @return string + */ +function base_url() +{ + static $baseUrl = ''; + if (empty($baseUrl)) { + $request = Request::instance(); + $subDir = str_replace('\\', '/', dirname($request->server('PHP_SELF'))); + $baseUrl = $request->scheme() . '://' . $request->host() . $subDir . ($subDir === '/' ? '' : '/'); + } + return $baseUrl; +} + +/** + * 写入日志 (废弃) + * @param string|array $values + * @param string $dir + * @return bool|int + */ +//function write_log($values, $dir) +//{ +// if (is_array($values)) +// $values = print_r($values, true); +// // 日志内容 +// $content = '[' . date('Y-m-d H:i:s') . ']' . PHP_EOL . $values . PHP_EOL . PHP_EOL; +// try { +// // 文件路径 +// $filePath = $dir . '/logs/'; +// // 路径不存在则创建 +// !is_dir($filePath) && mkdir($filePath, 0755, true); +// // 写入文件 +// return file_put_contents($filePath . date('Ymd') . '.log', $content, FILE_APPEND); +// } catch (\Exception $e) { +// return false; +// } +//} + +/** + * 写入日志 (使用tp自带驱动记录到runtime目录中) + * @param $value + * @param string $type + */ +function log_write($value, $type = 'yoshop-info') +{ + $msg = is_string($value) ? $value : var_export($value, true); + Log::record($msg, $type); +} + +/** + * curl请求指定url (get) + * @param $url + * @param array $data + * @return mixed + */ +function curl($url, $data = []) +{ + // 处理get数据 + if (!empty($data)) { + $url = $url . '?' . http_build_query($data); + } + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);//这个是重点。 + $result = curl_exec($curl); + curl_close($curl); + return $result; +} + +/** + * curl请求指定url (post) + * @param $url + * @param array $data + * @return mixed + */ +function curlPost($url, $data = []) +{ + $ch = curl_init(); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $result = curl_exec($ch); + curl_close($ch); + return $result; +} + +if (!function_exists('array_column')) { + /** + * array_column 兼容低版本php + * (PHP < 5.5.0) + * @param $array + * @param $columnKey + * @param null $indexKey + * @return array + */ + function array_column($array, $columnKey, $indexKey = null) + { + $result = array(); + foreach ($array as $subArray) { + if (is_null($indexKey) && array_key_exists($columnKey, $subArray)) { + $result[] = is_object($subArray) ? $subArray->$columnKey : $subArray[$columnKey]; + } elseif (array_key_exists($indexKey, $subArray)) { + if (is_null($columnKey)) { + $index = is_object($subArray) ? $subArray->$indexKey : $subArray[$indexKey]; + $result[$index] = $subArray; + } elseif (array_key_exists($columnKey, $subArray)) { + $index = is_object($subArray) ? $subArray->$indexKey : $subArray[$indexKey]; + $result[$index] = is_object($subArray) ? $subArray->$columnKey : $subArray[$columnKey]; + } + } + } + return $result; + } +} + +/** + * 多维数组合并 + * @param $array1 + * @param $array2 + * @return array + */ +function array_merge_multiple($array1, $array2) +{ + $merge = $array1 + $array2; + $data = []; + foreach ($merge as $key => $val) { + if ( + isset($array1[$key]) + && is_array($array1[$key]) + && isset($array2[$key]) + && is_array($array2[$key]) + ) { + $data[$key] = array_merge_multiple($array1[$key], $array2[$key]); + } else { + $data[$key] = isset($array2[$key]) ? $array2[$key] : $array1[$key]; + } + } + return $data; +} + +/** + * 二维数组排序 + * @param $arr + * @param $keys + * @param bool $desc + * @return mixed + */ +function array_sort($arr, $keys, $desc = false) +{ + $key_value = $new_array = array(); + foreach ($arr as $k => $v) { + $key_value[$k] = $v[$keys]; + } + if ($desc) { + arsort($key_value); + } else { + asort($key_value); + } + reset($key_value); + foreach ($key_value as $k => $v) { + $new_array[$k] = $arr[$k]; + } + return $new_array; +} + +/** + * 数据导出到excel(csv文件) + * @param $fileName + * @param array $tileArray + * @param array $dataArray + */ +function export_excel($fileName, $tileArray = [], $dataArray = []) +{ + ini_set('memory_limit', '512M'); + ini_set('max_execution_time', 0); + ob_end_clean(); + ob_start(); + header("Content-Type: text/csv"); + header("Content-Disposition:filename=" . $fileName); + $fp = fopen('php://output', 'w'); + fwrite($fp, chr(0xEF) . chr(0xBB) . chr(0xBF));// 转码 防止乱码(比如微信昵称) + fputcsv($fp, $tileArray); + $index = 0; + foreach ($dataArray as $item) { + if ($index == 1000) { + $index = 0; + ob_flush(); + flush(); + } + $index++; + fputcsv($fp, $item); + } + ob_flush(); + flush(); + ob_end_clean(); +} + +/** + * 隐藏敏感字符 + * @param $value + * @return string + */ +function substr_cut($value) +{ + $strlen = mb_strlen($value, 'utf-8'); + if ($strlen <= 1) return $value; + $firstStr = mb_substr($value, 0, 1, 'utf-8'); + $lastStr = mb_substr($value, -1, 1, 'utf-8'); + return $strlen == 2 ? $firstStr . str_repeat('*', $strlen - 1) : $firstStr . str_repeat("*", $strlen - 2) . $lastStr; +} + +/** + * 获取当前系统版本号 + * @return mixed|null + * @throws Exception + */ +function get_version() +{ + static $version = null; + if ($version) { + return $version; + } + $file = dirname(ROOT_PATH) . '/version.json'; + if (!file_exists($file)) { + throw new Exception('version.json not found'); + } + $version = json_decode(file_get_contents($file), true); + if (!is_array($version)) { + throw new Exception('version cannot be decoded'); + } + return $version['version']; +} + +/** + * 获取全局唯一标识符 + * @param bool $trim + * @return string + */ +function getGuidV4($trim = true) +{ + // Windows + if (function_exists('com_create_guid') === true) { + $charid = com_create_guid(); + return $trim == true ? trim($charid, '{}') : $charid; + } + // OSX/Linux + if (function_exists('openssl_random_pseudo_bytes') === true) { + $data = openssl_random_pseudo_bytes(16); + $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100 + $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 + return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); + } + // Fallback (PHP 4.2+) + mt_srand((double)microtime() * 10000); + $charid = strtolower(md5(uniqid(rand(), true))); + $hyphen = chr(45); // "-" + $lbrace = $trim ? "" : chr(123); // "{" + $rbrace = $trim ? "" : chr(125); // "}" + $guidv4 = $lbrace . + substr($charid, 0, 8) . $hyphen . + substr($charid, 8, 4) . $hyphen . + substr($charid, 12, 4) . $hyphen . + substr($charid, 16, 4) . $hyphen . + substr($charid, 20, 12) . + $rbrace; + return $guidv4; +} + +/** + * 时间戳转换日期 + * @param $timeStamp + * @return false|string + */ +function format_time($timeStamp) +{ + return date('Y-m-d H:i:s', $timeStamp); +} + +/** + * 左侧填充0 + * @param $value + * @param int $padLength + * @return string + */ +function pad_left($value, $padLength = 2) +{ + return \str_pad($value, $padLength, "0", STR_PAD_LEFT); +} + +/** + * 过滤emoji表情 + * @param $text + * @return null|string|string[] + */ +function filter_emoji($text) +{ + // 此处的preg_replace用于过滤emoji表情 + // 如需支持emoji表情, 需将mysql的编码改为utf8mb4 + return preg_replace('/[\xf0-\xf7].{3}/', '', $text); +} diff --git a/source/application/common/behavior/App.php b/source/application/common/behavior/App.php new file mode 100644 index 0000000..2f6e12d --- /dev/null +++ b/source/application/common/behavior/App.php @@ -0,0 +1,29 @@ +header(), true), 'begin'); + Log::record('[ PARAM ] ' . var_export($request->param(), true), 'begin'); + } + } +} \ No newline at end of file diff --git a/source/application/common/enum/DeliveryType.php b/source/application/common/enum/DeliveryType.php new file mode 100644 index 0000000..53b5d53 --- /dev/null +++ b/source/application/common/enum/DeliveryType.php @@ -0,0 +1,36 @@ + [ + 'name' => '快递配送', + 'value' => self::EXPRESS, + ], + self::EXTRACT => [ + 'name' => '上门自提', + 'value' => self::EXTRACT, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/EnumBasics.php b/source/application/common/enum/EnumBasics.php new file mode 100644 index 0000000..0a22e23 --- /dev/null +++ b/source/application/common/enum/EnumBasics.php @@ -0,0 +1,15 @@ + [ + 'name' => '商城订单', + 'value' => self::MASTER, + ], + self::SHARING => [ + 'name' => '拼团订单', + 'value' => self::SHARING, + ], + self::RECHARGE => [ + 'name' => '余额充值', + 'value' => self::RECHARGE, + ], + ]; + } + + /** + * 获取订单类型名称 + * @return array + */ + public static function getTypeName() + { + static $names = []; + if (empty($names)) { + foreach (self::data() as $item) + $names[$item['value']] = $item['name']; + } + return $names; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/PrinterType.php b/source/application/common/enum/PrinterType.php new file mode 100644 index 0000000..27f512e --- /dev/null +++ b/source/application/common/enum/PrinterType.php @@ -0,0 +1,30 @@ + '飞鹅打印机', + self::PRINT_CENTER => '365云打印', + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/Setting.php b/source/application/common/enum/Setting.php new file mode 100644 index 0000000..61da965 --- /dev/null +++ b/source/application/common/enum/Setting.php @@ -0,0 +1,85 @@ + [ + 'value' => self::STORE, + 'describe' => '商城设置', + ], + self::TRADE => [ + 'value' => self::TRADE, + 'describe' => '交易设置', + ], + self::SMS => [ + 'value' => self::SMS, + 'describe' => '短信通知', + ], + self::TPL_MSG => [ + 'value' => self::TPL_MSG, + 'describe' => '模板消息', + ], + self::STORAGE => [ + 'value' => self::STORAGE, + 'describe' => '上传设置', + ], + self::PRINTER => [ + 'value' => self::PRINTER, + 'describe' => '小票打印', + ], + self::FULL_FREE => [ + 'value' => self::FULL_FREE, + 'describe' => '满额包邮设置', + ], + self::RECHARGE => [ + 'value' => self::RECHARGE, + 'describe' => '充值设置', + ], + self::POINTS => [ + 'value' => self::POINTS, + 'describe' => '积分设置', + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/goods/DeductStockType.php b/source/application/common/enum/goods/DeductStockType.php new file mode 100644 index 0000000..44c1db8 --- /dev/null +++ b/source/application/common/enum/goods/DeductStockType.php @@ -0,0 +1,39 @@ + [ + 'name' => '下单减库存', + 'value' => self::CREATE, + ], + self::PAYMENT => [ + 'name' => '付款减库存', + 'value' => self::PAYMENT, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/order/OrderSource.php b/source/application/common/enum/order/OrderSource.php new file mode 100644 index 0000000..9796ad9 --- /dev/null +++ b/source/application/common/enum/order/OrderSource.php @@ -0,0 +1,45 @@ + [ + 'name' => '普通订单', + 'value' => self::MASTER, + ], + self::BARGAIN => [ + 'name' => '砍价订单', + 'value' => self::BARGAIN, + ], + self::SHARP => [ + 'name' => '秒杀订单', + 'value' => self::SHARP, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/order/PayStatus.php b/source/application/common/enum/order/PayStatus.php new file mode 100644 index 0000000..ca321c2 --- /dev/null +++ b/source/application/common/enum/order/PayStatus.php @@ -0,0 +1,38 @@ + [ + 'name' => '待付款', + 'value' => self::PENDING, + ], + self::SUCCESS => [ + 'name' => '已付款', + 'value' => self::SUCCESS, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/order/PayType.php b/source/application/common/enum/order/PayType.php new file mode 100644 index 0000000..7711d26 --- /dev/null +++ b/source/application/common/enum/order/PayType.php @@ -0,0 +1,38 @@ + [ + 'name' => '余额支付', + 'value' => self::BALANCE, + ], + self::WECHAT => [ + 'name' => '微信支付', + 'value' => self::WECHAT, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/order/Status.php b/source/application/common/enum/order/Status.php new file mode 100644 index 0000000..6190219 --- /dev/null +++ b/source/application/common/enum/order/Status.php @@ -0,0 +1,47 @@ + [ + 'name' => '进行中', + 'value' => self::NORMAL, + ], + self::CANCELLED => [ + 'name' => '已取消', + 'value' => self::CANCELLED, + ], + self::APPLY_CANCEL => [ + 'name' => '待取消', + 'value' => self::APPLY_CANCEL, + ], + self::COMPLETED => [ + 'name' => '已完成', + 'value' => self::COMPLETED, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/recharge/order/PayStatus.php b/source/application/common/enum/recharge/order/PayStatus.php new file mode 100644 index 0000000..1d20bfc --- /dev/null +++ b/source/application/common/enum/recharge/order/PayStatus.php @@ -0,0 +1,38 @@ + [ + 'name' => '未支付', + 'value' => self::PENDING, + ], + self::SUCCESS => [ + 'name' => '已支付', + 'value' => self::SUCCESS, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/recharge/order/RechargeType.php b/source/application/common/enum/recharge/order/RechargeType.php new file mode 100644 index 0000000..b599e32 --- /dev/null +++ b/source/application/common/enum/recharge/order/RechargeType.php @@ -0,0 +1,38 @@ + [ + 'name' => '自定义金额', + 'value' => self::CUSTOM, + ], + self::PLAN => [ + 'name' => '套餐充值', + 'value' => self::PLAN, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/sharp/ActiveStatus.php b/source/application/common/enum/sharp/ActiveStatus.php new file mode 100644 index 0000000..2120103 --- /dev/null +++ b/source/application/common/enum/sharp/ActiveStatus.php @@ -0,0 +1,45 @@ + [ + 'name' => '已开始', + 'value' => self::ACTIVE_STATE_BEGIN, + ], + self::ACTIVE_STATE_SOON => [ + 'name' => '即将开始', + 'value' => self::ACTIVE_STATE_SOON, + ], + self::ACTIVE_STATE_NOTICE => [ + 'name' => '预告', + 'value' => self::ACTIVE_STATE_SOON, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/sharp/GoodsStatus.php b/source/application/common/enum/sharp/GoodsStatus.php new file mode 100644 index 0000000..ea35347 --- /dev/null +++ b/source/application/common/enum/sharp/GoodsStatus.php @@ -0,0 +1,45 @@ + [ + 'name' => '已开始', + 'value' => self::STATE_BEGIN, + ], + self::STATE_SOON => [ + 'name' => '未开始', + 'value' => self::STATE_SOON, + ], + self::STATE_END => [ + 'name' => '已结束', + 'value' => self::STATE_END, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/user/balanceLog/Scene.php b/source/application/common/enum/user/balanceLog/Scene.php new file mode 100644 index 0000000..4e9eba6 --- /dev/null +++ b/source/application/common/enum/user/balanceLog/Scene.php @@ -0,0 +1,56 @@ + [ + 'name' => '用户充值', + 'value' => self::RECHARGE, + 'describe' => '用户充值:%s', + ], + self::CONSUME => [ + 'name' => '用户消费', + 'value' => self::CONSUME, + 'describe' => '用户消费:%s', + ], + self::ADMIN => [ + 'name' => '管理员操作', + 'value' => self::ADMIN, + 'describe' => '后台管理员 [%s] 操作', + ], + self::REFUND => [ + 'name' => '订单退款', + 'value' => self::REFUND, + 'describe' => '订单退款:%s', + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/enum/user/grade/log/ChangeType.php b/source/application/common/enum/user/grade/log/ChangeType.php new file mode 100644 index 0000000..e3ec4d9 --- /dev/null +++ b/source/application/common/enum/user/grade/log/ChangeType.php @@ -0,0 +1,20 @@ + 变更类型 + * Class ChangeType + * @package app\common\enum\user\grade\log + */ +class ChangeType extends EnumBasics +{ + // 后台管理员设置 + const ADMIN_USER = 10; + + // 自动升级 + const AUTO_UPGRADE = 20; + +} \ No newline at end of file diff --git a/source/application/common/exception/BaseException.php b/source/application/common/exception/BaseException.php new file mode 100644 index 0000000..2cb378e --- /dev/null +++ b/source/application/common/exception/BaseException.php @@ -0,0 +1,33 @@ +code = $params['code']; + } + if (array_key_exists('msg', $params)) { + $this->message = $params['msg']; + } + } +} + diff --git a/source/application/common/exception/ExceptionHandler.php b/source/application/common/exception/ExceptionHandler.php new file mode 100644 index 0000000..ebf4b3d --- /dev/null +++ b/source/application/common/exception/ExceptionHandler.php @@ -0,0 +1,49 @@ +code = $e->code; + $this->message = $e->message; + } else { + if (config('app_debug')) { + return parent::render($e); + } + $this->code = 0; + $this->message = $e->getMessage() ?: '很抱歉,服务器内部错误'; + $this->recordErrorLog($e); + } + return json(['msg' => $this->message, 'code' => $this->code]); + } + + /** + * 将异常写入日志 + * @param Exception $e + */ + private function recordErrorLog(Exception $e) + { + Log::record($e->getMessage(), 'error'); + Log::record($e->getTraceAsString(), 'error'); + } +} diff --git a/source/application/common/library/Lock.php b/source/application/common/library/Lock.php new file mode 100644 index 0000000..2e6b49b --- /dev/null +++ b/source/application/common/library/Lock.php @@ -0,0 +1,61 @@ +config = $config; + } + + /** + * 执行查询 + * @param $express_code + * @param $express_no + * @return bool + */ + public function query($express_code, $express_no) + { + // 缓存索引 + $cacheIndex = 'express_' . $express_code . '_' . $express_no; + if ($data = Cache::get($cacheIndex)) { + return $data; + } + // 参数设置 + $postData = [ + 'customer' => $this->config['customer'], + 'param' => json_encode([ + 'resultv2' => '1', + 'com' => $express_code, + 'num' => $express_no + ]) + ]; + $postData['sign'] = strtoupper(md5($postData['param'] . $this->config['key'] . $postData['customer'])); + // 请求快递100 api + $url = 'http://poll.kuaidi100.com/poll/query.do'; + $result = curlPost($url, http_build_query($postData)); + $express = json_decode($result, true); + // 记录错误信息 + if (isset($express['returnCode']) || !isset($express['data'])) { + $this->error = isset($express['message']) ? $express['message'] : '查询失败'; + return false; + } + // 记录缓存, 时效5分钟 + Cache::set($cacheIndex, $express['data'], 300); + return $express['data']; + } + + /** + * 返回错误信息 + * @return string + */ + public function getError() + { + return $this->error; + } + +} \ No newline at end of file diff --git a/source/application/common/library/helper.php b/source/application/common/library/helper.php new file mode 100644 index 0000000..89901ac --- /dev/null +++ b/source/application/common/library/helper.php @@ -0,0 +1,147 @@ + $value) { + $item[$key] = $value; + } + } + return $source; + } + + public static function bcsub($leftOperand, $rightOperand, $scale = 2) + { + return \bcsub($leftOperand, $rightOperand, $scale); + } + + public static function bcadd($leftOperand, $rightOperand, $scale = 2) + { + return \bcadd($leftOperand, $rightOperand, $scale); + } + + public static function bcmul($leftOperand, $rightOperand, $scale = 2) + { + return \bcmul($leftOperand, $rightOperand, $scale); + } + + public static function bcdiv($leftOperand, $rightOperand, $scale = 2) + { + return \bcdiv($leftOperand, $rightOperand, $scale); + } + + public static function bccomp($leftOperand, $rightOperand, $scale = 2) + { + return \bccomp($leftOperand, $rightOperand, $scale); + } + + public static function bcequal($leftOperand, $rightOperand, $scale = 2) + { + return self::bccomp($leftOperand, $rightOperand, $scale) === 0; + } + + /** + * 数组转义为json + * @param array|\think\Collection $data + * @return string + */ + public static function jsonEncode($data) + { + return json_encode($data, JSON_UNESCAPED_UNICODE); + } + + /** + * json转义为数组 + * @param $json + * @return array + */ + public static function jsonDecode($json) + { + return json_decode($json, true); + } + +} \ No newline at end of file diff --git a/source/application/common/library/printer/Driver.php b/source/application/common/library/printer/Driver.php new file mode 100644 index 0000000..2e26eb3 --- /dev/null +++ b/source/application/common/library/printer/Driver.php @@ -0,0 +1,72 @@ + 'Feie', + PrinterTypeEnum::PRINT_CENTER => 'PrintCenter', + ]; + + /** + * 构造方法 + * Driver constructor. + * @param $printer + * @throws BaseException + */ + public function __construct($printer) + { + // 当前打印机 + $this->printer = $printer; + // 实例化当前打印机引擎 + $this->engine = $this->getEngineClass(); + } + + /** + * 执行打印请求 + * @param $content + * @return bool + */ + public function printTicket($content) + { + return $this->engine->printTicket($content); + } + + /** + * 获取错误信息 + * @return mixed + */ + public function getError() + { + return $this->engine->getError(); + } + + /** + * 获取当前的打印机引擎类 + * @return mixed + * @throws BaseException + */ + private function getEngineClass() + { + $engineName = self::$engineList[$this->printer['printer_type']['value']]; + $classSpace = __NAMESPACE__ . "\\engine\\{$engineName}"; + if (!class_exists($classSpace)) { + throw new BaseException("未找到打印机引擎类: {$engineName}"); + } + return new $classSpace($this->printer['printer_config'], $this->printer['print_times']); + } + +} diff --git a/source/application/common/library/printer/engine/Basics.php b/source/application/common/library/printer/engine/Basics.php new file mode 100644 index 0000000..64c3c75 --- /dev/null +++ b/source/application/common/library/printer/engine/Basics.php @@ -0,0 +1,55 @@ +config = $config; + $this->times = $times; + } + + /** + * 执行打印请求 + * @param $content + * @return mixed + */ + abstract protected function printTicket($content); + + /** + * 返回错误信息 + * @return mixed + */ + public function getError() + { + return $this->error; + } + + /** + * 创建打印的内容 + * @return string + */ + private function setContentText() + { + return ''; + } + + +} \ No newline at end of file diff --git a/source/application/common/library/printer/engine/Feie.php b/source/application/common/library/printer/engine/Feie.php new file mode 100644 index 0000000..23affd3 --- /dev/null +++ b/source/application/common/library/printer/engine/Feie.php @@ -0,0 +1,68 @@ +getParams($content); + // API请求:开始打印 + $client = new FeieHttpClient(self::IP, self::PORT); + if (!$client->post(self::PATH, $params)) { + $this->error = $client->getError(); + return false; + } + // 处理返回结果 + $result = json_decode($client->getContent()); + log_write($result); + // 返回状态 + if ($result->ret != 0) { + $this->error = $result->msg; + return false; + } + return true; + } + + /** + * 构建Api请求参数 + * @param $content + * @return array + */ + private function getParams(&$content) + { + $time = time(); + return [ + 'user' => $this->config['USER'], + 'stime' => $time, + 'sig' => sha1("{$this->config['USER']}{$this->config['UKEY']}{$time}"), + 'apiname' => 'Open_printMsg', + 'sn' => $this->config['SN'], + 'content' => $content, + 'times' => $this->times // 打印次数 + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/library/printer/engine/PrintCenter.php b/source/application/common/library/printer/engine/PrintCenter.php new file mode 100644 index 0000000..5551713 --- /dev/null +++ b/source/application/common/library/printer/engine/PrintCenter.php @@ -0,0 +1,44 @@ + [ + 'header' => "Content-type: application/x-www-form-urlencoded ", + 'method' => 'POST', + 'content' => http_build_query([ + 'deviceNo' => $this->config['deviceNo'], + 'key' => $this->config['key'], + 'printContent' => $content, + 'times' => $this->times + ]), + ] + ]); + // API请求:开始打印 + $result = file_get_contents(self::API, false, $context); + // 处理返回结果 + $result = json_decode($result); + log_write($result); + // 返回状态 + if ($result->responseCode != 0) { + $this->error = $result->msg; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/common/library/printer/party/FeieHttpClient.php b/source/application/common/library/printer/party/FeieHttpClient.php new file mode 100644 index 0000000..7af16b6 --- /dev/null +++ b/source/application/common/library/printer/party/FeieHttpClient.php @@ -0,0 +1,368 @@ +host = $host; + $this->port = $port; + } + + function get($path, $data = false) + { + $this->path = $path; + $this->method = 'GET'; + if ($data) { + $this->path .= '?' . $this->buildQueryString($data); + } + return $this->doRequest(); + } + + function post($path, $data) + { + $this->path = $path; + $this->method = 'POST'; + $this->postdata = $this->buildQueryString($data); + return $this->doRequest(); + } + + function buildQueryString($data) + { + $querystring = ''; + if (is_array($data)) { + foreach ($data as $key => $val) { + if (is_array($val)) { + foreach ($val as $val2) { + $querystring .= urlencode($key) . '=' . urlencode($val2) . '&'; + } + } else { + $querystring .= urlencode($key) . '=' . urlencode($val) . '&'; + } + } + $querystring = substr($querystring, 0, -1); // Eliminate unnecessary & + } else { + $querystring = $data; + } + return $querystring; + } + + function doRequest() + { + if (!$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout)) { + switch ($errno) { + case -3: + $this->errormsg = 'Socket creation failed (-3)'; + break; + case -4: + $this->errormsg = 'DNS lookup failure (-4)'; + break; + case -5: + $this->errormsg = 'Connection refused or timed out (-5)'; + break; + default: + $this->errormsg = 'Connection failed (' . $errno . ')'; + $this->errormsg .= ' ' . $errstr; + $this->debug($this->errormsg); + } + return false; + } + socket_set_timeout($fp, $this->timeout); + $request = $this->buildRequest(); + $this->debug('Request', $request); + fwrite($fp, $request); + $this->headers = []; + $this->content = ''; + $this->errormsg = ''; + $inHeaders = true; + $atStart = true; + while (!feof($fp)) { + $line = fgets($fp, 4096); + if ($atStart) { + $atStart = false; + if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) { + $this->errormsg = "Status code line invalid: " . htmlentities($line); + $this->debug($this->errormsg); + return false; + } +// $http_version = $m[1]; + $this->status = $m[2]; +// $status_string = $m[3]; + $this->debug(trim($line)); + continue; + } + if ($inHeaders) { + if (trim($line) == '') { + $inHeaders = false; + $this->debug('Received Headers', $this->headers); + if ($this->headers_only) { + break; + } + continue; + } + if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) { + continue; + } + $key = strtolower(trim($m[1])); + $val = trim($m[2]); + if (isset($this->headers[$key])) { + if (is_array($this->headers[$key])) { + $this->headers[$key][] = $val; + } else { + $this->headers[$key] = [$this->headers[$key], $val]; + } + } else { + $this->headers[$key] = $val; + } + continue; + } + $this->content .= $line; + } + fclose($fp); + if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] == 'gzip') { + $this->debug('Content is gzip encoded, unzipping it'); + $this->content = substr($this->content, 10); + $this->content = gzinflate($this->content); + } + if ($this->persist_cookies && isset($this->headers['set-cookie']) && $this->host == $this->cookie_host) { + $cookies = $this->headers['set-cookie']; + if (!is_array($cookies)) { + $cookies = [$cookies]; + } + foreach ($cookies as $cookie) { + if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m)) { + $this->cookies[$m[1]] = $m[2]; + } + } + $this->cookie_host = $this->host; + } + if ($this->persist_referers) { + $this->debug('Persisting referer: ' . $this->getRequestURL()); + $this->referer = $this->getRequestURL(); + } + if ($this->handle_redirects) { + if (++$this->redirect_count >= $this->max_redirects) { + $this->errormsg = 'Number of redirects exceeded maximum (' . $this->max_redirects . ')'; + $this->debug($this->errormsg); + $this->redirect_count = 0; + return false; + } + $location = isset($this->headers['location']) ? $this->headers['location'] : ''; + $uri = isset($this->headers['uri']) ? $this->headers['uri'] : ''; + if ($location || $uri) { + $url = parse_url($location . $uri); + return $this->get($url['path']); + } + } + return true; + } + + function buildRequest() + { + $headers = []; + $headers[] = "{$this->method} {$this->path} HTTP/1.0"; + $headers[] = "Host: {$this->host}"; + $headers[] = "User-Agent: {$this->user_agent}"; + $headers[] = "Accept: {$this->accept}"; + if ($this->use_gzip) { + $headers[] = "Accept-encoding: {$this->accept_encoding}"; + } + $headers[] = "Accept-language: {$this->accept_language}"; + if ($this->referer) { + $headers[] = "Referer: {$this->referer}"; + } + if ($this->cookies) { + $cookie = 'Cookie: '; + foreach ($this->cookies as $key => $value) { + $cookie .= "$key=$value; "; + } + $headers[] = $cookie; + } + if ($this->username && $this->password) { + $headers[] = 'Authorization: BASIC ' . base64_encode($this->username . ':' . $this->password); + } + if ($this->postdata) { + $headers[] = 'Content-Type: application/x-www-form-urlencoded'; + $headers[] = 'Content-Length: ' . strlen($this->postdata); + } + $request = implode("\r\n", $headers) . "\r\n\r\n" . $this->postdata; + return $request; + } + + function getStatus() + { + return $this->status; + } + + function getContent() + { + return $this->content; + } + + function getHeaders() + { + return $this->headers; + } + + function getHeader($header) + { + $header = strtolower($header); + if (isset($this->headers[$header])) { + return $this->headers[$header]; + } else { + return false; + } + } + + function getError() + { + return $this->errormsg; + } + + function getCookies() + { + return $this->cookies; + } + + function getRequestURL() + { + $url = 'http://' . $this->host; + if ($this->port != 80) { + $url .= ':' . $this->port; + } + $url .= $this->path; + return $url; + } + + function setUserAgent($string) + { + $this->user_agent = $string; + } + + function setAuthorization($username, $password) + { + $this->username = $username; + $this->password = $password; + } + + function setCookies($array) + { + $this->cookies = $array; + } + + function useGzip($boolean) + { + $this->use_gzip = $boolean; + } + + function setPersistCookies($boolean) + { + $this->persist_cookies = $boolean; + } + + function setPersistReferers($boolean) + { + $this->persist_referers = $boolean; + } + + function setHandleRedirects($boolean) + { + $this->handle_redirects = $boolean; + } + + function setMaxRedirects($num) + { + $this->max_redirects = $num; + } + + function setHeadersOnly($boolean) + { + $this->headers_only = $boolean; + } + + function setDebug($boolean) + { + $this->debug = $boolean; + } + + function quickGet($url) + { + $bits = parse_url($url); + $host = $bits['host']; + $port = isset($bits['port']) ? $bits['port'] : 80; + $path = isset($bits['path']) ? $bits['path'] : '/'; + if (isset($bits['query'])) { + $path .= '?' . $bits['query']; + } + $client = new self($host, $port); + if (!$client->get($path)) { + return false; + } else { + return $client->getContent(); + } + } + + function quickPost($url, $data) + { + $bits = parse_url($url); + $host = $bits['host']; + $port = isset($bits['port']) ? $bits['port'] : 80; + $path = isset($bits['path']) ? $bits['path'] : '/'; + $client = new self($host, $port); + if (!$client->post($path, $data)) { + return false; + } else { + return $client->getContent(); + } + } + + function debug($msg, $object = false) + { + if ($this->debug) { + print '
HttpClient Debug: ' . $msg; + if ($object) { + ob_start(); + print_r($object); + $content = htmlentities(ob_get_contents()); + ob_end_clean(); + print '
' . $content . '
'; + } + print '
'; + } + } +} diff --git a/source/application/common/library/sms/Driver.php b/source/application/common/library/sms/Driver.php new file mode 100644 index 0000000..ea5743d --- /dev/null +++ b/source/application/common/library/sms/Driver.php @@ -0,0 +1,73 @@ +config = $config; + // 当前引擎名称 + $this->engineName = $this->config['default']; + // 实例化当前存储引擎 + $this->engine = $this->getEngineClass(); + } + + /** + * 发送短信通知 + * @param $msgType + * @param $templateParams + * @param bool $isTest + * @return bool + */ + public function sendSms($msgType, $templateParams, $isTest = false) + { + if ($isTest === false + && $this->config['engine'][$this->engineName][$msgType]['is_enable'] == '0') { + return false; + } + return $this->engine->sendSms($msgType, $templateParams); + } + + /** + * 获取错误信息 + * @return mixed + */ + public function getError() + { + return $this->engine->getError(); + } + + /** + * 获取当前的存储引擎 + * @return mixed + * @throws Exception + */ + private function getEngineClass() + { + $classSpace = __NAMESPACE__ . '\\engine\\' . ucfirst($this->engineName); + if (!class_exists($classSpace)) { + throw new Exception('未找到存储引擎类: ' . $this->engineName); + } + return new $classSpace($this->config['engine'][$this->engineName]); + } + +} diff --git a/source/application/common/library/sms/engine/Aliyun.php b/source/application/common/library/sms/engine/Aliyun.php new file mode 100644 index 0000000..3cfdf2d --- /dev/null +++ b/source/application/common/library/sms/engine/Aliyun.php @@ -0,0 +1,86 @@ +config = $config; + } + + /** + * 发送短信通知 + * @param $msgType + * @param $templateParams + * @return bool|\stdClass + */ + public function sendSms($msgType, $templateParams) + { + $params = []; + // *** 需用户填写部分 *** + + // 必填: 短信接收号码 + $params["PhoneNumbers"] = $this->config[$msgType]['accept_phone']; + + // 必填: 短信签名,应严格按"签名名称"填写,请参考: https://dysms.console.aliyun.com/dysms.htm#/develop/sign + $params["SignName"] = $this->config['sign']; + + // 必填: 短信模板Code,应严格按"模板CODE"填写, 请参考: https://dysms.console.aliyun.com/dysms.htm#/develop/template + $params["TemplateCode"] = $this->config[$msgType]['template_code']; + + // 可选: 设置模板参数, 假如模板中存在变量需要替换则为必填项 + $params['TemplateParam'] = $templateParams; + + // 可选: 设置发送短信流水号 + // $params['OutId'] = "12345"; + + // 可选: 上行短信扩展码, 扩展码字段控制在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 + $response = $helper->request( + $this->config['AccessKeyId'] + , $this->config['AccessKeySecret'] + , "dysmsapi.aliyuncs.com" + , array_merge($params, [ + "RegionId" => "cn-hangzhou", + "Action" => "SendSms", + "Version" => "2017-05-25", + ]) + // 选填: 启用https + , true + ); + // 记录日志 + log_write([ + 'config' => $this->config, + 'params' => $params + ]); + log_write($response); + $this->error = $response->Message; + return $response->Code === 'OK'; + } + +} diff --git a/source/application/common/library/sms/engine/Server.php b/source/application/common/library/sms/engine/Server.php new file mode 100644 index 0000000..041a1dc --- /dev/null +++ b/source/application/common/library/sms/engine/Server.php @@ -0,0 +1,19 @@ +error; + } + +} diff --git a/source/application/common/library/sms/package/aliyun/SignatureHelper.php b/source/application/common/library/sms/package/aliyun/SignatureHelper.php new file mode 100644 index 0000000..ec81131 --- /dev/null +++ b/source/application/common/library/sms/package/aliyun/SignatureHelper.php @@ -0,0 +1,88 @@ + "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 = "GET&%2F&" . $this->encode(substr($sortedQueryStringTmp, 1)); + + $sign = base64_encode(hash_hmac("sha1", $stringToSign, $accessKeySecret . "&", true)); + + $signature = $this->encode($sign); + + $url = ($security ? 'https' : 'http') . "://{$domain}/?Signature={$signature}{$sortedQueryStringTmp}"; + + try { + $content = $this->fetchContent($url); + 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) + { + $ch = curl_init(); + 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) { + trigger_error("[CURL_" . curl_errno($ch) . "]: " . curl_error($ch), E_USER_ERROR); + } + curl_close($ch); + + return $rtn; + } +} diff --git a/source/application/common/library/storage/Driver.php b/source/application/common/library/storage/Driver.php new file mode 100644 index 0000000..94c8da6 --- /dev/null +++ b/source/application/common/library/storage/Driver.php @@ -0,0 +1,112 @@ +config = $config; + // 实例化当前存储引擎 + $this->engine = $this->getEngineClass($storage); + } + + /** + * 设置上传的文件信息 + * @param string $name + * @return mixed + */ + public function setUploadFile($name = 'iFile') + { + return $this->engine->setUploadFile($name); + } + + /** + * 设置上传的文件信息 + * @param string $filePath + * @return mixed + */ + public function setUploadFileByReal($filePath) + { + return $this->engine->setUploadFileByReal($filePath); + } + + /** + * 执行文件上传 + */ + public function upload() + { + return $this->engine->upload(); + } + + /** + * 执行文件删除 + * @param $fileName + * @return mixed + */ + public function delete($fileName) + { + return $this->engine->delete($fileName); + } + + /** + * 获取错误信息 + * @return mixed + */ + public function getError() + { + return $this->engine->getError(); + } + + /** + * 获取文件路径 + * @return mixed + */ + public function getFileName() + { + return $this->engine->getFileName(); + } + + /** + * 返回文件信息 + * @return mixed + */ + public function getFileInfo() + { + return $this->engine->getFileInfo(); + } + + /** + * 获取当前的存储引擎 + * @param null|string $storage 指定存储方式,如不指定则为系统默认 + * @return mixed + * @throws Exception + */ + private function getEngineClass($storage = null) + { + $engineName = is_null($storage) ? $this->config['default'] : $storage; + $classSpace = __NAMESPACE__ . '\\engine\\' . ucfirst($engineName); + if (!class_exists($classSpace)) { + throw new Exception('未找到存储引擎类: ' . $engineName); + } + return new $classSpace($this->config['engine'][$engineName]); + } + +} diff --git a/source/application/common/library/storage/engine/Aliyun.php b/source/application/common/library/storage/engine/Aliyun.php new file mode 100644 index 0000000..3dec707 --- /dev/null +++ b/source/application/common/library/storage/engine/Aliyun.php @@ -0,0 +1,84 @@ +config = $config; + } + + /** + * 执行上传 + * @return bool|mixed + */ + public function upload() + { + try { + $ossClient = new OssClient( + $this->config['access_key_id'], + $this->config['access_key_secret'], + $this->config['domain'], + true + ); + $result = $ossClient->uploadFile( + $this->config['bucket'], + $this->fileName, + $this->getRealPath() + ); + } catch (OssException $e) { + $this->error = $e->getMessage(); + return false; + } + return true; + } + + /** + * 删除文件 + * @param $fileName + * @return bool|mixed + */ + public function delete($fileName) + { + try { + $ossClient = new OssClient( + $this->config['access_key_id'], + $this->config['access_key_secret'], + $this->config['domain'], + true + ); + $ossClient->deleteObject($this->config['bucket'], $fileName); + } catch (OssException $e) { + $this->error = $e->getMessage(); + return false; + } + return true; + } + + /** + * 返回文件路径 + * @return mixed + */ + public function getFileName() + { + return $this->fileName; + } + +} diff --git a/source/application/common/library/storage/engine/Local.php b/source/application/common/library/storage/engine/Local.php new file mode 100644 index 0000000..a1d4082 --- /dev/null +++ b/source/application/common/library/storage/engine/Local.php @@ -0,0 +1,86 @@ +isInternal ? $this->uploadByInternal() : $this->uploadByExternal(); + } + + /** + * 外部上传(指用户上传,需验证文件类型、大小) + * @return bool + */ + private function uploadByExternal() + { + // 上传目录 + $uplodDir = WEB_PATH . 'uploads'; + // 验证文件并上传 + $info = $this->file->validate([ + 'size' => 4 * 1024 * 1024, + 'ext' => 'jpg,jpeg,png,gif' + ])->move($uplodDir, $this->fileName); + if (empty($info)) { + $this->error = $this->file->getError(); + return false; + } + return true; + } + + /** + * 内部上传(指系统上传,信任模式) + * @return bool + */ + private function uploadByInternal() + { + // 上传目录 + $uplodDir = WEB_PATH . 'uploads'; + // 要上传图片的本地路径 + $realPath = $this->getRealPath(); + if (!rename($realPath, "{$uplodDir}/$this->fileName")) { + $this->error = 'upload write error'; + return false; + } + return true; + } + + /** + * 删除文件 + * @param $fileName + * @return bool|mixed + */ + public function delete($fileName) + { + // 文件所在目录 + $filePath = WEB_PATH . "uploads/{$fileName}"; + return !file_exists($filePath) ?: unlink($filePath); + } + + /** + * 返回文件路径 + * @return mixed + */ + public function getFileName() + { + return $this->fileName; + } + +} diff --git a/source/application/common/library/storage/engine/Qcloud.php b/source/application/common/library/storage/engine/Qcloud.php new file mode 100644 index 0000000..e0124b1 --- /dev/null +++ b/source/application/common/library/storage/engine/Qcloud.php @@ -0,0 +1,93 @@ +config = $config; + // 创建COS控制类 + $this->createCosClient(); + } + + /** + * 创建COS控制类 + */ + private function createCosClient() + { + $this->cosClient = new Client([ + 'region' => $this->config['region'], + 'credentials' => [ + 'secretId' => $this->config['secret_id'], + 'secretKey' => $this->config['secret_key'], + ], + ]); + } + + /** + * 执行上传 + * @return bool|mixed + */ + public function upload() + { + // 上传文件 + // putObject(上传接口,最大支持上传5G文件) + try { + $result = $this->cosClient->putObject([ + 'Bucket' => $this->config['bucket'], + 'Key' => $this->fileName, + 'Body' => fopen($this->getRealPath(), 'rb') + ]); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + return false; + } + } + + /** + * 删除文件 + * @param $fileName + * @return bool|mixed + */ + public function delete($fileName) + { + try { + $result = $this->cosClient->deleteObject(array( + 'Bucket' => $this->config['bucket'], + 'Key' => $fileName + )); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + return false; + } + } + + /** + * 返回文件路径 + * @return mixed + */ + public function getFileName() + { + return $this->fileName; + } + +} diff --git a/source/application/common/library/storage/engine/Qiniu.php b/source/application/common/library/storage/engine/Qiniu.php new file mode 100644 index 0000000..e01ae84 --- /dev/null +++ b/source/application/common/library/storage/engine/Qiniu.php @@ -0,0 +1,88 @@ +config = $config; + } + + /** + * 执行上传 + * @return bool|mixed + * @throws \Exception + */ + public function upload() + { + // 要上传图片的本地路径 + $realPath = $this->getRealPath(); + + // 构建鉴权对象 + $auth = new Auth($this->config['access_key'], $this->config['secret_key']); + + // 要上传的空间 + $token = $auth->uploadToken($this->config['bucket']); + + // 初始化 UploadManager 对象并进行文件的上传 + $uploadMgr = new UploadManager(); + + // 调用 UploadManager 的 putFile 方法进行文件的上传 + list(, $error) = $uploadMgr->putFile($token, $this->fileName, $realPath); + + /* @var $error \Qiniu\Http\Error */ + if ($error !== null) { + $this->error = $error->message(); + return false; + } + return true; + } + + /** + * 删除文件 + * @param $fileName + * @return bool|mixed + */ + public function delete($fileName) + { + // 构建鉴权对象 + $auth = new Auth($this->config['access_key'], $this->config['secret_key']); + // 初始化 UploadManager 对象并进行文件的上传 + $bucketMgr = new BucketManager($auth); + /* @var $error \Qiniu\Http\Error */ + $error = $bucketMgr->delete($this->config['bucket'], $fileName); + if ($error !== null) { + $this->error = $error->message(); + return false; + } + return true; + } + + /** + * 返回文件路径 + * @return mixed + */ + public function getFileName() + { + return $this->fileName; + } + +} diff --git a/source/application/common/library/storage/engine/Server.php b/source/application/common/library/storage/engine/Server.php new file mode 100644 index 0000000..fb530eb --- /dev/null +++ b/source/application/common/library/storage/engine/Server.php @@ -0,0 +1,125 @@ +file = Request::instance()->file($name); + if (empty($this->file)) { + throw new Exception('未找到上传文件的信息'); + } + // 文件信息 + $this->fileInfo = $this->file->getInfo(); + // 生成保存文件名 + $this->fileName = $this->buildSaveName(); + } + + /** + * 设置上传的文件信息 + * @param string $filePath + */ + public function setUploadFileByReal($filePath) + { + // 设置为系统内部上传 + $this->isInternal = true; + // 文件信息 + $this->fileInfo = [ + 'name' => basename($filePath), + 'size' => filesize($filePath), + 'tmp_name' => $filePath, + 'error' => 0, + ]; + // 生成保存文件名 + $this->fileName = $this->buildSaveName(); + } + + /** + * 文件上传 + * @return mixed + */ + abstract protected function upload(); + + /** + * 文件删除 + * @param $fileName + * @return mixed + */ + abstract protected function delete($fileName); + + /** + * 返回上传后文件路径 + * @return mixed + */ + abstract public function getFileName(); + + /** + * 返回文件信息 + * @return mixed + */ + public function getFileInfo() + { + return $this->fileInfo; + } + + protected function getRealPath() + { + return $this->getFileInfo()['tmp_name']; + } + + /** + * 返回错误信息 + * @return mixed + */ + public function getError() + { + return $this->error; + } + + /** + * 生成保存文件名 + */ + private function buildSaveName() + { + // 要上传图片的本地路径 + $realPath = $this->getRealPath(); + // 扩展名 + $ext = pathinfo($this->getFileInfo()['name'], PATHINFO_EXTENSION); + // 自动生成文件名 + return date('YmdHis') . substr(md5($realPath), 0, 5) + . str_pad(rand(0, 9999), 4, '0', STR_PAD_LEFT) . ".{$ext}"; + } + +} diff --git a/source/application/common/library/wechat/Qrcode.php b/source/application/common/library/wechat/Qrcode.php new file mode 100644 index 0000000..2dd9134 --- /dev/null +++ b/source/application/common/library/wechat/Qrcode.php @@ -0,0 +1,49 @@ +getAccessToken(); + $url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$access_token}"; + // 构建请求 + $data = compact('scene', 'width'); + !is_null($page) && $data['page'] = $page; + // 返回结果 + $result = $this->post($url, json_encode($data, JSON_UNESCAPED_UNICODE)); + // 记录日志 + log_write([ + 'describe' => '获取小程序码', + 'params' => $data, + 'result' => strpos($result, 'errcode') ? $result : 'image' + ]); + if (!strpos($result, 'errcode')) { + return $result; + } + $data = json_decode($result, true); + $error = '小程序码获取失败 ' . $data['errmsg']; + if ($data['errcode'] == 41030) { + $error = '小程序页面不存在,请先发布上线后再生成'; + } + throw new BaseException(['msg' => $error]); + } + +} \ No newline at end of file diff --git a/source/application/common/library/wechat/WxBase.php b/source/application/common/library/wechat/WxBase.php new file mode 100644 index 0000000..d1e18ad --- /dev/null +++ b/source/application/common/library/wechat/WxBase.php @@ -0,0 +1,155 @@ +setConfig($appId, $appSecret); + } + + protected function setConfig($appId = null, $appSecret = null) + { + !empty($appId) && $this->appId = $appId; + !empty($appSecret) && $this->appSecret = $appSecret; + } + + /** + * 写入日志记录 + * @param $values + * @return bool|int + */ + protected function doLogs($values) + { + return log_write($values); + } + + /** + * 获取access_token + * @return mixed + * @throws BaseException + */ + protected function getAccessToken() + { + $cacheKey = $this->appId . '@access_token'; + if (!Cache::get($cacheKey)) { + // 请求API获取 access_token + $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}"; + $result = $this->get($url); + $data = $this->jsonDecode($result); + if (array_key_exists('errcode', $data)) { + throw new BaseException(['msg' => "access_token获取失败,错误信息:{$result}"]); + } + // 记录日志 + $this->doLogs([ + 'describe' => '获取access_token', + 'url' => $url, + 'appId' => $this->appId, + 'result' => $result + ]); + // 写入缓存 + Cache::set($cacheKey, $data['access_token'], 6000); // 7000 + } + return Cache::get($cacheKey); + } + + /** + * 模拟GET请求 HTTPS的页面 + * @param string $url 请求地址 + * @return string $result + */ + protected function get($url) + { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_HEADER, 0); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts + $result = curl_exec($curl); + curl_close($curl); + return $result; + } + + /** + * 模拟POST请求 + * @param $url + * @param array $data + * @param bool $useCert + * @param array $sslCert + * @return mixed + */ + protected function post($url, $data = [], $useCert = false, $sslCert = []) + { + $header = [ + 'Content-type: application/json;' + ]; + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_POST, TRUE); + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + if ($useCert == true) { + // 设置证书:cert 与 key 分别属于两个.pem文件 + curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM'); + curl_setopt($curl, CURLOPT_SSLCERT, $sslCert['certPem']); + curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM'); + curl_setopt($curl, CURLOPT_SSLKEY, $sslCert['keyPem']); + } + $result = curl_exec($curl); + curl_close($curl); + return $result; + } + + /** + * 数组转json + * @param $data + * @return string + */ + protected function jsonEncode($data) + { + return json_encode($data, JSON_UNESCAPED_UNICODE); + } + + /** + * json转数组 + * @param $json + * @return mixed + */ + protected function jsonDecode($json) + { + return json_decode($json, true); + } + + /** + * 返回错误信息 + * @return mixed + */ + public function getError() + { + return $this->error; + } + +} diff --git a/source/application/common/library/wechat/WxPay.php b/source/application/common/library/wechat/WxPay.php new file mode 100644 index 0000000..66c1836 --- /dev/null +++ b/source/application/common/library/wechat/WxPay.php @@ -0,0 +1,400 @@ + 'app\api\service\order\PaySuccess', + OrderTypeEnum::SHARING => 'app\api\service\sharing\order\PaySuccess', + OrderTypeEnum::RECHARGE => 'app\api\service\recharge\PaySuccess', + ]; + + /** + * 构造函数 + * WxPay constructor. + * @param $config + */ + public function __construct($config = false) + { + parent::__construct(); + $this->config = $config; + $this->config !== false && $this->setConfig($this->config['app_id'], $this->config['app_secret']); + } + + /** + * 统一下单API + * @param $order_no + * @param $openid + * @param $totalFee + * @param int $orderType 订单类型 + * @return array + * @throws BaseException + */ + public function unifiedorder($order_no, $openid, $totalFee, $orderType = OrderTypeEnum::MASTER) + { + // 当前时间 + $time = time(); + // 生成随机字符串 + $nonceStr = md5($time . $openid); + // API参数 + $params = [ + 'appid' => $this->appId, + 'attach' => json_encode(['order_type' => $orderType]), + 'body' => $order_no, + 'mch_id' => $this->config['mchid'], + 'nonce_str' => $nonceStr, + 'notify_url' => base_url() . 'notice.php', // 异步通知地址 + 'openid' => $openid, + 'out_trade_no' => $order_no, + 'spbill_create_ip' => \request()->ip(), + 'total_fee' => $totalFee * 100, // 价格:单位分 + 'trade_type' => 'JSAPI', + ]; + // 生成签名 + $params['sign'] = $this->makeSign($params); + // 请求API + $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; + $result = $this->post($url, $this->toXml($params)); + $prepay = $this->fromXml($result); + // 请求失败 + if ($prepay['return_code'] === 'FAIL') { + throw new BaseException(['msg' => "微信支付api:{$prepay['return_msg']}", 'code' => -10]); + } + if ($prepay['result_code'] === 'FAIL') { + throw new BaseException(['msg' => "微信支付api:{$prepay['err_code_des']}", 'code' => -10]); + } + // 生成 nonce_str 供前端使用 + $paySign = $this->makePaySign($params['nonce_str'], $prepay['prepay_id'], $time); + return [ + 'prepay_id' => $prepay['prepay_id'], + 'nonceStr' => $nonceStr, + 'timeStamp' => (string)$time, + 'paySign' => $paySign + ]; + } + + /** + * 支付成功异步通知 + * @throws BaseException + * @throws \Exception + * @throws \think\exception\DbException + */ + public function notify() + { +// $xml = << +// +// +// +// +// +// +// +// +// +// +// +// +// +//1 +// +// +// +//EOF; + if (!$xml = file_get_contents('php://input')) { + $this->returnCode(false, 'Not found DATA'); + } + // 将服务器返回的XML数据转化为数组 + $data = $this->fromXml($xml); + // 记录日志 + $this->doLogs($xml); + $this->doLogs($data); + // 实例化订单模型 + $model = $this->getOrderModel($data['out_trade_no'], $data['attach']); + // 订单信息 + $order = $model->getOrderInfo(); + empty($order) && $this->returnCode(false, '订单不存在'); + // 小程序配置信息 + $wxConfig = WxappModel::getWxappCache($order['wxapp_id']); + // 设置支付秘钥 + $this->config['apikey'] = $wxConfig['apikey']; + // 保存微信服务器返回的签名sign + $dataSign = $data['sign']; + // sign不参与签名算法 + unset($data['sign']); + // 生成签名 + $sign = $this->makeSign($data); + // 判断签名是否正确 判断支付状态 + if ( + ($sign !== $dataSign) + || ($data['return_code'] !== 'SUCCESS') + || ($data['result_code'] !== 'SUCCESS') + ) { + $this->returnCode(false, '签名失败'); + } + // 订单支付成功业务处理 + $status = $model->onPaySuccess(PayTypeEnum::WECHAT, $data); + if ($status == false) { + $this->returnCode(false, $model->getError()); + } + // 返回状态 + $this->returnCode(true, 'OK'); + } + + /** + * 申请退款API + * @param $transaction_id + * @param double $total_fee 订单总金额 + * @param double $refund_fee 退款金额 + * @return bool + * @throws BaseException + */ + public function refund($transaction_id, $total_fee, $refund_fee) + { + // 当前时间 + $time = time(); + // 生成随机字符串 + $nonceStr = md5($time . $transaction_id . $total_fee . $refund_fee); + // API参数 + $params = [ + 'appid' => $this->appId, + 'mch_id' => $this->config['mchid'], + 'nonce_str' => $nonceStr, + 'transaction_id' => $transaction_id, + 'out_refund_no' => $time, + 'total_fee' => $total_fee * 100, + 'refund_fee' => $refund_fee * 100, + ]; + // 生成签名 + $params['sign'] = $this->makeSign($params); + // 请求API + $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund'; + $result = $this->post($url, $this->toXml($params), true, $this->getCertPem()); + // 请求失败 + if (empty($result)) { + throw new BaseException(['msg' => '微信退款api请求失败']); + } + // 格式化返回结果 + $prepay = $this->fromXml($result); + // 请求失败 + if ($prepay['return_code'] === 'FAIL') { + throw new BaseException(['msg' => 'return_msg: ' . $prepay['return_msg']]); + } + if ($prepay['result_code'] === 'FAIL') { + throw new BaseException(['msg' => 'err_code_des: ' . $prepay['err_code_des']]); + } + return true; + } + + /** + * 企业付款到零钱API + * @param $order_no + * @param $openid + * @param $amount + * @param $desc + * @return bool + * @throws BaseException + */ + public function transfers($order_no, $openid, $amount, $desc) + { + // API参数 + $params = [ + 'mch_appid' => $this->appId, + 'mchid' => $this->config['mchid'], + 'nonce_str' => md5(uniqid()), + 'partner_trade_no' => $order_no, + 'openid' => $openid, + 'check_name' => 'NO_CHECK', + 'amount' => $amount * 100, + 'desc' => $desc, + 'spbill_create_ip' => \request()->ip(), + ]; + // 生成签名 + $params['sign'] = $this->makeSign($params); + // 请求API + $url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; + $result = $this->post($url, $this->toXml($params), true, $this->getCertPem()); + // 请求失败 + if (empty($result)) { + throw new BaseException(['msg' => '微信退款api请求失败']); + } + // 格式化返回结果 + $prepay = $this->fromXml($result); + // 请求失败 + if ($prepay['return_code'] === 'FAIL') { + throw new BaseException(['msg' => 'return_msg: ' . $prepay['return_msg']]); + } + if ($prepay['result_code'] === 'FAIL') { + throw new BaseException(['msg' => 'err_code_des: ' . $prepay['err_code_des']]); + } + return true; + } + + /** + * 获取cert证书文件 + * @return array + * @throws BaseException + */ + private function getCertPem() + { + if (empty($this->config['cert_pem']) || empty($this->config['key_pem'])) { + throw new BaseException(['msg' => '请先到后台小程序设置填写微信支付证书文件']); + } + // cert目录 + $filePath = __DIR__ . '/cert/' . $this->config['wxapp_id'] . '/'; + return [ + 'certPem' => $filePath . 'cert.pem', + 'keyPem' => $filePath . 'key.pem' + ]; + } + + /** + * 实例化订单模型 (根据attach判断) + * @param $orderNo + * @param null $attach + * @return mixed + */ + private function getOrderModel($orderNo, $attach = null) + { + $attach = json_decode($attach, true); + // 判断订单类型返回对应的订单模型 + $model = $this->modelClass[$attach['order_type']]; + return new $model($orderNo); + } + + /** + * 返回状态给微信服务器 + * @param boolean $returnCode + * @param string $msg + */ + private function returnCode($returnCode = true, $msg = null) + { + // 返回状态 + $return = [ + 'return_code' => $returnCode ? 'SUCCESS' : 'FAIL', + 'return_msg' => $msg ?: 'OK', + ]; + // 记录日志 + log_write([ + 'describe' => '返回微信支付状态', + 'data' => $return + ]); + die($this->toXml($return)); + } + + /** + * 生成paySign + * @param $nonceStr + * @param $prepay_id + * @param $timeStamp + * @return string + */ + private function makePaySign($nonceStr, $prepay_id, $timeStamp) + { + $data = [ + 'appId' => $this->appId, + 'nonceStr' => $nonceStr, + 'package' => 'prepay_id=' . $prepay_id, + 'signType' => 'MD5', + 'timeStamp' => $timeStamp, + ]; + // 签名步骤一:按字典序排序参数 + ksort($data); + $string = $this->toUrlParams($data); + // 签名步骤二:在string后加入KEY + $string = $string . '&key=' . $this->config['apikey']; + // 签名步骤三:MD5加密 + $string = md5($string); + // 签名步骤四:所有字符转为大写 + $result = strtoupper($string); + return $result; + } + + /** + * 将xml转为array + * @param $xml + * @return mixed + */ + private function fromXml($xml) + { + // 禁止引用外部xml实体 + libxml_disable_entity_loader(true); + return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); + } + + /** + * 生成签名 + * @param $values + * @return string 本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值 + */ + private function makeSign($values) + { + //签名步骤一:按字典序排序参数 + ksort($values); + $string = $this->toUrlParams($values); + //签名步骤二:在string后加入KEY + $string = $string . '&key=' . $this->config['apikey']; + //签名步骤三:MD5加密 + $string = md5($string); + //签名步骤四:所有字符转为大写 + $result = strtoupper($string); + return $result; + } + + /** + * 格式化参数格式化成url参数 + * @param $values + * @return string + */ + private function toUrlParams($values) + { + $buff = ''; + foreach ($values as $k => $v) { + if ($k != 'sign' && $v != '' && !is_array($v)) { + $buff .= $k . '=' . $v . '&'; + } + } + return trim($buff, '&'); + } + + /** + * 输出xml字符 + * @param $values + * @return bool|string + */ + private function toXml($values) + { + if (!is_array($values) + || count($values) <= 0 + ) { + return false; + } + + $xml = ""; + foreach ($values as $key => $val) { + if (is_numeric($val)) { + $xml .= "<" . $key . ">" . $val . ""; + } else { + $xml .= "<" . $key . ">"; + } + } + $xml .= ""; + return $xml; + } + +} diff --git a/source/application/common/library/wechat/WxTplMsg.php b/source/application/common/library/wechat/WxTplMsg.php new file mode 100644 index 0000000..c1f2a5c --- /dev/null +++ b/source/application/common/library/wechat/WxTplMsg.php @@ -0,0 +1,64 @@ +getAccessToken(); + $url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token={$accessToken}"; + // 构建请求 + $params = [ + 'touser' => $param['touser'], + 'template_id' => $param['template_id'], + 'page' => $param['page'], + 'form_id' => $param['form_id'], + 'data' => $this->createData($param['data']) + ]; + $result = $this->post($url, $this->jsonEncode($params)); + // 记录日志 + $this->doLogs(['describe' => '发送模板消息', 'url' => $url, 'params' => $params, 'result' => $result]); + // 返回结果 + $response = $this->jsonDecode($result); + if (!isset($response['errcode'])) { + $this->error = 'not found errcode'; + return false; + } + if ($response['errcode'] != 0) { + $this->error = $response['errmsg']; + return false; + } + return true; + } + + /** + * 生成关键字数据 + * @param $data + * @return array + */ + private function createData($data) + { + $params = []; + foreach ($data as $key => $value) { + $params[$key] = [ + 'value' => $value, + 'color' => '#333333' + ]; + } + return $params; + } + +} \ No newline at end of file diff --git a/source/application/common/library/wechat/WxUser.php b/source/application/common/library/wechat/WxUser.php new file mode 100644 index 0000000..a627916 --- /dev/null +++ b/source/application/common/library/wechat/WxUser.php @@ -0,0 +1,38 @@ + $this->appId, + 'secret' => $this->appSecret, + 'grant_type' => 'authorization_code', + 'js_code' => $code + ]), true); + if (isset($result['errcode'])) { + $this->error = $result['errmsg']; + return false; + } + return $result; + } + +} \ No newline at end of file diff --git a/source/application/common/library/wechat/cert/.gitignore b/source/application/common/library/wechat/cert/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/source/application/common/library/wechat/cert/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/source/application/common/library/wechat/logs/.gitignore b/source/application/common/library/wechat/logs/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/source/application/common/library/wechat/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/source/application/common/library/wechat/wow/Order.php b/source/application/common/library/wechat/wow/Order.php new file mode 100644 index 0000000..8984de8 --- /dev/null +++ b/source/application/common/library/wechat/wow/Order.php @@ -0,0 +1,121 @@ + '导入订单', + 'update' => '更新订单信息', + 'delete' => '删除订单', + ]; + + /** + * 导入订单 + * @param $orderList + * @param int $isHistory + * @return bool + * @throws \app\common\exception\BaseException + */ + public function import($orderList, $isHistory = 0) + { + // 微信接口url + $accessToken = $this->getAccessToken(); + $url = "https://api.weixin.qq.com/mall/importorder?action=add-order&is_history={$isHistory}&access_token={$accessToken}"; + // 请求参数 + $params = $this->jsonEncode(['order_list' => $orderList]); + // 执行请求 + $result = $this->post($url, $params); + // 记录日志 + $this->doLogs(['describe' => $this->describe['import'], 'url' => $url, 'params' => $params, 'result' => $result]); + // 返回结果 + $response = $this->jsonDecode($result); + if (!isset($response['errcode'])) { + $this->error = 'not found errcode'; + return false; + } + if ($response['errcode'] != 0) { + $this->error = $response['errmsg']; + return false; + } + return true; + } + + /** + * 更新订单 + * @param $orderList + * @param int $isHistory + * @return bool + * @throws \app\common\exception\BaseException + */ + public function update($orderList, $isHistory = 0) + { + // 微信接口url + $accessToken = $this->getAccessToken(); + $url = "https://api.weixin.qq.com/mall/importorder?action=update-order&is_history={$isHistory}&access_token={$accessToken}"; + // 请求参数 + $params = $this->jsonEncode(['order_list' => $orderList]); + // 执行请求 + $result = $this->post($url, $params); + // 记录日志 + $this->doLogs(['describe' => $this->describe['update'], 'url' => $url, 'params' => $params, 'result' => $result]); + // 返回结果 + $response = $this->jsonDecode($result); + if (!isset($response['errcode'])) { + $this->error = 'not found errcode'; + return false; + } + if ($response['errcode'] != 0) { + $this->error = $response['errmsg']; + return false; + } + return true; + } + + /** + * 删除商品收藏 + * @param string $openId + * @param string $orderId + * @return bool + * @throws \app\common\exception\BaseException + */ + public function delete($openId, $orderId) + { + // 微信接口url + $accessToken = $this->getAccessToken(); + $url = "https://api.weixin.qq.com/mall/deleteorder?access_token={$accessToken}"; + // 请求参数 + $params = [ + 'user_open_id' => $openId, + 'order_id' => $orderId + ]; + // 执行请求 + $result = $this->post($url, $this->jsonEncode($params)); + // 记录日志 + $this->doLogs(['describe' => $this->describe['delete'], 'url' => $url, 'params' => $params, 'result' => $result]); + // 返回结果 + $response = $this->jsonDecode($result); + if (!isset($response['errcode'])) { + $this->error = 'not found errcode'; + return false; + } + if ($response['errcode'] != 0) { + $this->error = $response['errmsg']; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/common/library/wechat/wow/Shoping.php b/source/application/common/library/wechat/wow/Shoping.php new file mode 100644 index 0000000..40e4629 --- /dev/null +++ b/source/application/common/library/wechat/wow/Shoping.php @@ -0,0 +1,83 @@ +getAccessToken(); + $url = "https://api.weixin.qq.com/mall/addshoppinglist?access_token={$accessToken}"; + // 请求参数 + $params = $this->jsonEncode([ + 'user_open_id' => $openId, + 'sku_product_list' => $productList + ]); + // 执行请求 + $result = $this->post($url, $params); + // 记录日志 + $this->doLogs(['describe' => '新增好物圈商品收藏', 'url' => $url, 'params' => $params, 'result' => $result]); + // 返回结果 + $response = $this->jsonDecode($result); + if (!isset($response['errcode'])) { + $this->error = 'not found errcode'; + return false; + } + if ($response['errcode'] != 0) { + $this->error = $response['errmsg']; + return false; + } + return true; + } + + /** + * 删除商品收藏 + * @param $openId + * @param $productList + * @return bool + * @throws \app\common\exception\BaseException + */ + public function delete($openId, $productList) + { + // 微信接口url + $accessToken = $this->getAccessToken(); + $url = "https://api.weixin.qq.com/mall/deleteshoppinglist?access_token={$accessToken}"; + // 请求参数 + $params = [ + 'user_open_id' => $openId, + 'sku_product_list' => $productList + ]; + // 执行请求 + $result = $this->post($url, $this->jsonEncode($params)); + // 记录日志 + $this->doLogs(['describe' => '删除好物圈商品收藏', 'url' => $url, 'params' => $params, 'result' => $result]); + // 返回结果 + $response = $this->jsonDecode($result); + if (!isset($response['errcode'])) { + $this->error = 'not found errcode'; + return false; + } + if ($response['errcode'] != 0) { + $this->error = $response['errmsg']; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/common/model/Article.php b/source/application/common/model/Article.php new file mode 100644 index 0000000..b97bdf2 --- /dev/null +++ b/source/application/common/model/Article.php @@ -0,0 +1,56 @@ +hasOne('uploadFile', 'file_id', 'image_id'); + } + + /** + * 关联文章分类表 + * @return \think\model\relation\BelongsTo + */ + public function category() + { + $module = self::getCalledModule() ?: 'common'; + return $this->BelongsTo("app\\{$module}\\model\\article\\Category"); + } + + /** + * 展示的浏览次数 + * @param $value + * @param $data + * @return mixed + */ + public function getShowViewsAttr($value, $data) + { + return $data['virtual_views'] + $data['actual_views']; + } + + /** + * 文章详情 + * @param $article_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($article_id) + { + return self::get($article_id, ['image', 'category']); + } + +} diff --git a/source/application/common/model/BaseModel.php b/source/application/common/model/BaseModel.php new file mode 100644 index 0000000..f4b8454 --- /dev/null +++ b/source/application/common/model/BaseModel.php @@ -0,0 +1,143 @@ +param('wxapp_id'); + } + + /** + * 定义全局的查询范围 + * @param \think\db\Query $query + */ + protected function base($query) + { + if (self::$wxapp_id > 0) { + $query->where($query->getTable() . '.wxapp_id', self::$wxapp_id); + } + } + + /** + * 设置默认的检索数据 + * @param $query + * @param array $default + * @return array + */ + protected function setQueryDefaultValue(&$query, $default = []) + { + $data = array_merge($default, $query); + foreach ($query as $key => $val) { + if (empty($val) && isset($default[$key])) { + $data[$key] = $default[$key]; + } + } + return $data; + } + + /** + * 设置基础查询条件(用于简化基础alias和field) + * @test 2019-4-25 + * @param string $alias + * @param array $join + * @return BaseModel + */ + public function setBaseQuery($alias = '', $join = []) + { + // 设置别名 + $aliasValue = $alias ?: $this->alias; + $model = $this->alias($aliasValue)->field("{$aliasValue}.*"); + // join条件 + if (!empty($join)) : foreach ($join as $item): + $model->join($item[0], "{$item[0]}.{$item[1]}={$aliasValue}." + . (isset($item[2]) ? $item[2] : $item[1])); + endforeach; endif; + return $model; + } + + /** + * 批量更新数据(支持带where条件) + * @param array $data [0 => ['data'=>[], 'where'=>[]]] + * @return \think\Collection + */ + public function updateAll($data) + { + return $this->transaction(function () use ($data) { + $result = []; + foreach ($data as $key => $item) { + $result[$key] = self::update($item['data'], $item['where']); + } + return $this->toCollection($result); + }); + } + +} diff --git a/source/application/common/model/Category.php b/source/application/common/model/Category.php new file mode 100644 index 0000000..2aeb791 --- /dev/null +++ b/source/application/common/model/Category.php @@ -0,0 +1,122 @@ +hasOne('uploadFile', 'file_id', 'image_id'); + } + + /** + * 所有分类 + * @return mixed + */ + public static function getALL() + { + $model = new static; + if (!Cache::get('category_' . $model::$wxapp_id)) { + $data = $model->with(['image'])->order(['sort' => 'asc', 'create_time' => 'asc'])->select(); + $all = !empty($data) ? $data->toArray() : []; + $tree = []; + foreach ($all as $first) { + if ($first['parent_id'] != 0) continue; + $twoTree = []; + foreach ($all as $two) { + if ($two['parent_id'] != $first['category_id']) continue; + $threeTree = []; + foreach ($all as $three) + $three['parent_id'] == $two['category_id'] + && $threeTree[$three['category_id']] = $three; + !empty($threeTree) && $two['child'] = $threeTree; + $twoTree[$two['category_id']] = $two; + } + if (!empty($twoTree)) { + array_multisort(array_column($twoTree, 'sort'), SORT_ASC, $twoTree); + $first['child'] = $twoTree; + } + $tree[$first['category_id']] = $first; + } + Cache::tag('cache')->set('category_' . $model::$wxapp_id, compact('all', 'tree')); + } + return Cache::get('category_' . $model::$wxapp_id); + } + + /** + * 获取所有分类 + * @return mixed + */ + public static function getCacheAll() + { + return self::getALL()['all']; + } + + /** + * 获取所有分类(树状结构) + * @return mixed + */ + public static function getCacheTree() + { + return self::getALL()['tree']; + } + + /** + * 获取所有分类(树状结构) + * @return string + */ + public static function getCacheTreeJson() + { + return json_encode(static::getCacheTree()); + } + + /** + * 获取指定分类下的所有子分类id + * @param $parent_id + * @param array $all + * @return array + */ + public static function getSubCategoryId($parent_id, $all = []) + { + $arrIds = [$parent_id]; + empty($all) && $all = self::getCacheAll(); + foreach ($all as $key => $item) { + if ($item['parent_id'] == $parent_id) { + unset($all[$key]); + $subIds = self::getSubCategoryId($item['category_id'], $all); + !empty($subIds) && $arrIds = array_merge($arrIds, $subIds); + } + } + return $arrIds; + } + + /** + * 指定的分类下是否存在子分类 + * @param $parentId + * @return bool + */ + protected static function hasSubCategory($parentId) + { + $all = self::getCacheAll(); + foreach ($all as $item) { + if ($item['parent_id'] == $parentId) { + return true; + } + } + return false; + } + +} diff --git a/source/application/common/model/Comment.php b/source/application/common/model/Comment.php new file mode 100644 index 0000000..4a53cf1 --- /dev/null +++ b/source/application/common/model/Comment.php @@ -0,0 +1,113 @@ +belongsTo('Order'); + } + + /** + * 订单商品 + * @return \think\model\relation\BelongsTo + */ + public function OrderGoods() + { + return $this->belongsTo('OrderGoods'); + } + + /** + * 关联用户表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + return $this->belongsTo('User'); + } + + /** + * 关联评价图片表 + * @return \think\model\relation\HasMany + */ + public function image() + { + return $this->hasMany('CommentImage')->order(['id' => 'asc']); + } + + /** + * 评价详情 + * @param $comment_id + * @return Comment|null + * @throws \think\exception\DbException + */ + public static function detail($comment_id) + { + return self::get($comment_id, ['user', 'orderM', 'OrderGoods', 'image.file']); + } + + /** + * 更新记录 + * @param $data + * @return bool + */ + public function edit($data) + { + return $this->transaction(function () use ($data) { + // 删除评价图片 + $this->image()->delete(); + // 添加评论图片 + isset($data['images']) && $this->addCommentImages($data['images']); + // 是否为图片评价 + $data['is_picture'] = !$this->image()->select()->isEmpty(); + // 更新评论记录 + return $this->allowField(true)->save($data); + }); + } + + /** + * 添加评论图片 + * @param $images + * @return int + */ + private function addCommentImages($images) + { + $data = array_map(function ($image_id) { + return [ + 'image_id' => $image_id, + 'wxapp_id' => self::$wxapp_id + ]; + }, $images); + return $this->image()->saveAll($data); + } + + /** + * 获取评价列表 + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList() + { + return $this->with(['user', 'orderM', 'OrderGoods']) + ->where('is_delete', '=', 0) + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/common/model/CommentImage.php b/source/application/common/model/CommentImage.php new file mode 100644 index 0000000..7147cc7 --- /dev/null +++ b/source/application/common/model/CommentImage.php @@ -0,0 +1,25 @@ +belongsTo('UploadFile', 'image_id', 'file_id') + ->bind(['file_path', 'file_name', 'file_url']); + } + +} diff --git a/source/application/common/model/Coupon.php b/source/application/common/model/Coupon.php new file mode 100644 index 0000000..c63bc26 --- /dev/null +++ b/source/application/common/model/Coupon.php @@ -0,0 +1,117 @@ + '已领取', 'value' => 0]; + } + if ($data['total_num'] > -1 && $data['receive_num'] >= $data['total_num']) { + return ['text' => '已抢光', 'value' => 0]; + } + if ($data['expire_type'] == 20 && ($data['end_time'] + 86400) < time()) { + return ['text' => '已过期', 'value' => 0]; + } + return ['text' => '', 'value' => 1]; + } + + /** + * 优惠券颜色 + * @param $value + * @return mixed + */ + public function getColorAttr($value) + { + $status = [10 => 'blue', 20 => 'red', 30 => 'violet', 40 => 'yellow']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 优惠券类型 + * @param $value + * @return mixed + */ + public function getCouponTypeAttr($value) + { + $status = [10 => '满减券', 20 => '折扣券']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 折扣率 + * @param $value + * @return mixed + */ + public function getDiscountAttr($value) + { + return $value / 10; + } + + /** + * 有效期-开始时间 + * @param $value + * @return mixed + */ + public function getStartTimeAttr($value) + { + return ['text' => date('Y/m/d', $value), 'value' => $value]; + } + + /** + * 有效期-结束时间 + * @param $value + * @return mixed + */ + public function getEndTimeAttr($value) + { + return ['text' => date('Y/m/d', $value), 'value' => $value]; + } + + /** + * 修改器:折扣率 + * @param $value + * @return mixed + */ + public function setDiscountAttr($value) + { + return helper::bcmul($value, 10, 0); + } + + /** + * 优惠券详情 + * @param $coupon_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($coupon_id) + { + return self::get($coupon_id); + } + +} diff --git a/source/application/common/model/Delivery.php b/source/application/common/model/Delivery.php new file mode 100644 index 0000000..60342ed --- /dev/null +++ b/source/application/common/model/Delivery.php @@ -0,0 +1,87 @@ +hasMany('DeliveryRule'); + } + + /** + * 计费方式 + * @param $value + * @return mixed + */ + public function getMethodAttr($value) + { + $method = [10 => '按件数', 20 => '按重量']; + return ['text' => $method[$value], 'value' => $value]; + } + + /** + * 获取全部 + * @return mixed + */ + public static function getAll() + { + $model = new static; + return $model->order(['sort' => 'asc'])->select(); + } + + /** + * 获取列表 + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList() + { + return $this->with(['rule']) + ->order(['sort' => 'asc']) + ->paginate(15, false, [ + 'query' => Request::instance()->request() + ]); + } + + /** + * 运费模板详情 + * @param $delivery_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($delivery_id) + { + return self::get($delivery_id, ['rule']); + } + + /** + * 获取列表(根据模板id集) + * @param $deliveryIds + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($deliveryIds) + { + return $this->with(['rule']) + ->where('delivery_id', 'in', $deliveryIds) + ->order(['sort' => 'asc']) + ->select(); + } + +} diff --git a/source/application/common/model/DeliveryRule.php b/source/application/common/model/DeliveryRule.php new file mode 100644 index 0000000..49b3413 --- /dev/null +++ b/source/application/common/model/DeliveryRule.php @@ -0,0 +1,32 @@ +order(['sort' => 'asc'])->select(); + } + + /** + * 获取列表 + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList() + { + return $this->order(['sort' => 'asc']) + ->paginate(15, false, [ + 'query' => Request::instance()->request() + ]); + } + + /** + * 物流公司详情 + * @param $express_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($express_id) + { + return self::get($express_id); + } + + /** + * 获取物流动态信息 + * @param $express_name + * @param $express_code + * @param $express_no + * @return array|bool + */ + public function dynamic($express_name, $express_code, $express_no) + { + $data = [ + 'express_name' => $express_name, + 'express_no' => $express_no + ]; + // 实例化快递100类 + $config = Setting::getItem('store'); + $Kuaidi100 = new Kuaidi100($config['kuaidi100']); + // 请求查询接口 + $data['list'] = $Kuaidi100->query($express_code, $express_no); + if ($data['list'] === false) { + $this->error = $Kuaidi100->getError(); + return false; + } + return $data; + } + +} diff --git a/source/application/common/model/Goods.php b/source/application/common/model/Goods.php new file mode 100644 index 0000000..0d3f48b --- /dev/null +++ b/source/application/common/model/Goods.php @@ -0,0 +1,350 @@ +belongsTo('Category'); + } + + /** + * 关联商品规格表 + * @return \think\model\relation\HasMany + */ + public function sku() + { + return $this->hasMany('GoodsSku')->order(['goods_sku_id' => 'asc']); + } + + /** + * 关联商品规格关系表 + * @return \think\model\relation\BelongsToMany + */ + public function specRel() + { + return $this->belongsToMany('SpecValue', 'GoodsSpecRel')->order(['id' => 'asc']); + } + + /** + * 关联商品图片表 + * @return \think\model\relation\HasMany + */ + public function image() + { + return $this->hasMany('GoodsImage')->order(['id' => 'asc']); + } + + /** + * 关联运费模板表 + * @return \think\model\relation\BelongsTo + */ + public function delivery() + { + return $this->BelongsTo('Delivery'); + } + + /** + * 关联订单评价表 + * @return \think\model\relation\HasMany + */ + public function commentData() + { + return $this->hasMany('Comment'); + } + + /** + * 计费方式 + * @param $value + * @return mixed + */ + public function getGoodsStatusAttr($value) + { + $status = [10 => '上架', 20 => '下架']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 获取商品列表 + * @param $param + * @return mixed + * @throws \think\exception\DbException + */ + public function getList($param) + { + // 商品列表获取条件 + $params = array_merge([ + 'status' => 10, // 商品状态 + 'category_id' => 0, // 分类id + 'search' => '', // 搜索关键词 + 'sortType' => 'all', // 排序类型 + 'sortPrice' => false, // 价格排序 高低 + 'listRows' => 15, // 每页数量 + ], $param); + // 筛选条件 + $filter = []; + $params['category_id'] > 0 && $filter['category_id'] = ['IN', Category::getSubCategoryId($params['category_id'])]; + $params['status'] > 0 && $filter['goods_status'] = $params['status']; + !empty($params['search']) && $filter['goods_name'] = ['like', '%' . trim($params['search']) . '%']; + // 排序规则 + $sort = []; + if ($params['sortType'] === 'all') { + $sort = ['goods_sort', 'goods_id' => 'desc']; + } elseif ($params['sortType'] === 'sales') { + $sort = ['goods_sales' => 'desc']; + } elseif ($params['sortType'] === 'price') { + $sort = $params['sortPrice'] ? ['goods_max_price' => 'desc'] : ['goods_min_price']; + } + // 商品表名称 + $tableName = $this->getTable(); + // 多规格商品 最高价与最低价 + $GoodsSku = new GoodsSku; + $minPriceSql = $GoodsSku->field(['MIN(goods_price)']) + ->where('goods_id', 'EXP', "= `$tableName`.`goods_id`")->buildSql(); + $maxPriceSql = $GoodsSku->field(['MAX(goods_price)']) + ->where('goods_id', 'EXP', "= `$tableName`.`goods_id`")->buildSql(); + // 执行查询 + $list = $this + ->field(['*', '(sales_initial + sales_actual) as goods_sales', + "$minPriceSql AS goods_min_price", + "$maxPriceSql AS goods_max_price" + ]) + ->with(['category', 'image.file', 'sku']) + ->where('is_delete', '=', 0) + ->where($filter) + ->order($sort) + ->paginate($params['listRows'], false, [ + 'query' => \request()->request() + ]); + // 整理列表数据并返回 + return $this->setGoodsListData($list, true); + } + + /** + * 设置商品展示的数据 + * @param $data + * @param bool $isMultiple + * @param callable $callback + * @return mixed + */ + protected function setGoodsListData($data, $isMultiple = true, callable $callback = null) + { + if (!$isMultiple) $dataSource = [&$data]; else $dataSource = &$data; + // 整理商品列表数据 + foreach ($dataSource as &$goods) { + // 商品主图 + $goods['goods_image'] = $goods['image'][0]['file_path']; + // 商品默认规格 + $goods['goods_sku'] = $goods['sku'][0]; + // 回调函数 + is_callable($callback) && call_user_func($callback, $goods); + } + return $data; + } + + /** + * 根据商品id集获取商品列表 + * @param array $goodsIds + * @param null $status + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($goodsIds, $status = null) + { + // 筛选条件 + $filter = ['goods_id' => ['in', $goodsIds]]; + $status > 0 && $filter['goods_status'] = $status; + if (!empty($goodsIds)) { + $this->orderRaw('field(goods_id, ' . implode(',', $goodsIds) . ')'); + } + // 获取商品列表数据 +// ['category', 'image.file', 'sku', 'spec_rel.spec', 'delivery.rule'] + $data = $this->field(['content'], true) + ->with(['category', 'image.file', 'sku']) + ->where($filter) + ->select(); + // 整理列表数据并返回 + return $this->setGoodsListData($data, true); + } + + /** + * 商品多规格信息 + * @param \think\Collection $specRel + * @param \think\Collection $skuData + * @return array + */ + public function getManySpecData($specRel, $skuData) + { + + // spec_attr + $specAttrData = []; + foreach ($specRel as $item) { + if (!isset($specAttrData[$item['spec_id']])) { + $specAttrData[$item['spec_id']] = [ + 'group_id' => $item['spec']['spec_id'], + 'group_name' => $item['spec']['spec_name'], + 'spec_items' => [], + ]; + } + $specAttrData[$item['spec_id']]['spec_items'][] = [ + 'item_id' => $item['spec_value_id'], + 'spec_value' => $item['spec_value'], + ]; + } + + // spec_list + $specListData = []; + foreach ($skuData as $item) { + $image = (isset($item['image']) && !empty($item['image'])) ? $item['image'] : ['file_id' => 0, 'file_path' => '']; + $specListData[] = [ + 'goods_sku_id' => $item['goods_sku_id'], + 'spec_sku_id' => $item['spec_sku_id'], + 'rows' => [], + 'form' => [ + 'image_id' => $image['file_id'], + 'image_path' => $image['file_path'], + 'goods_no' => $item['goods_no'], + 'goods_price' => $item['goods_price'], + 'goods_weight' => $item['goods_weight'], + 'line_price' => $item['line_price'], + 'stock_num' => $item['stock_num'], + ], + ]; + } + return ['spec_attr' => array_values($specAttrData), 'spec_list' => $specListData]; + } + + /** + * 多规格表格数据 + * @param $goods + * @return array + */ + public function getManySpecTable(&$goods) + { + $specData = $this->getManySpecData($goods['spec_rel'], $goods['sku']); + $totalRow = count($specData['spec_list']); + foreach ($specData['spec_list'] as $i => &$sku) { + $rowData = []; + $rowCount = 1; + foreach ($specData['spec_attr'] as $attr) { + $skuValues = $attr['spec_items']; + $rowCount *= count($skuValues); + $anInterBankNum = ($totalRow / $rowCount); + $point = (($i / $anInterBankNum) % count($skuValues)); + if (0 === ($i % $anInterBankNum)) { + $rowData[] = [ + 'rowspan' => $anInterBankNum, + 'item_id' => $skuValues[$point]['item_id'], + 'spec_value' => $skuValues[$point]['spec_value'] + ]; + } + } + $sku['rows'] = $rowData; + } + return $specData; + } + + /** + * 获取商品详情 + * @param $goodsId + * @return static + */ + public static function detail($goodsId) + { + /* @var $model self */ + $model = (new static)->with([ + 'category', + 'image.file', + 'sku.image', + 'spec_rel.spec', + ])->where('goods_id', '=', $goodsId) + ->find(); + if (empty($model)) { + return $model; + } + // 整理商品数据并返回 + return $model->setGoodsListData($model, false); + } + + /** + * 指定的商品规格信息 + * @param static $goods 商品详情 + * @param int $specSkuId + * @return array|bool + */ + public static function getGoodsSku($goods, $specSkuId) + { + // 获取指定的sku + $goodsSku = []; + foreach ($goods['sku'] as $item) { + if ($item['spec_sku_id'] == $specSkuId) { + $goodsSku = $item; + break; + } + } + if (empty($goodsSku)) { + return false; + } + // 多规格文字内容 + $goodsSku['goods_attr'] = ''; + if ($goods['spec_type'] == 20) { + $specRelData = helper::arrayColumn2Key($goods['spec_rel'], 'spec_value_id'); + $attrs = explode('_', $goodsSku['spec_sku_id']); + foreach ($attrs as $specValueId) { + $goodsSku['goods_attr'] .= $specRelData[$specValueId]['spec']['spec_name'] . ':' + . $specRelData[$specValueId]['spec_value'] . '; '; + } + } + return $goodsSku; + } + +} diff --git a/source/application/common/model/GoodsImage.php b/source/application/common/model/GoodsImage.php new file mode 100644 index 0000000..06b4e33 --- /dev/null +++ b/source/application/common/model/GoodsImage.php @@ -0,0 +1,25 @@ +belongsTo('UploadFile', 'image_id', 'file_id') + ->bind(['file_path', 'file_name', 'file_url']); + } + +} diff --git a/source/application/common/model/GoodsSku.php b/source/application/common/model/GoodsSku.php new file mode 100644 index 0000000..f0e53d5 --- /dev/null +++ b/source/application/common/model/GoodsSku.php @@ -0,0 +1,35 @@ +hasOne('uploadFile', 'file_id', 'image_id'); + } + + /** + * 获取sku信息详情 + * @param $goodsId + * @param $specSkuId + * @return GoodsSku|null + * @throws \think\exception\DbException + */ + public static function detail($goodsId, $specSkuId) + { + return static::get(['goods_id' => $goodsId, 'spec_sku_id' => $specSkuId]); + } + +} diff --git a/source/application/common/model/GoodsSpecRel.php b/source/application/common/model/GoodsSpecRel.php new file mode 100644 index 0000000..410fdb4 --- /dev/null +++ b/source/application/common/model/GoodsSpecRel.php @@ -0,0 +1,24 @@ +belongsTo('Spec'); + } + +} diff --git a/source/application/common/model/Order.php b/source/application/common/model/Order.php new file mode 100644 index 0000000..a3729f2 --- /dev/null +++ b/source/application/common/model/Order.php @@ -0,0 +1,360 @@ +hasMany("app\\{$module}\\model\\OrderGoods"); + } + + /** + * 关联订单收货地址表 + * @return \think\model\relation\HasOne + */ + public function address() + { + $module = self::getCalledModule() ?: 'common'; + return $this->hasOne("app\\{$module}\\model\\OrderAddress"); + } + + /** + * 关联订单收货地址表 + * @return \think\model\relation\HasOne + */ + public function extract() + { + $module = self::getCalledModule() ?: 'common'; + return $this->hasOne("app\\{$module}\\model\\OrderExtract"); + } + + /** + * 关联自提门店表 + * @return \think\model\relation\BelongsTo + */ + public function extractShop() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\store\\Shop", 'extract_shop_id'); + } + + /** + * 关联门店店员表 + * @return \think\model\relation\BelongsTo + */ + public function extractClerk() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\store\\shop\\Clerk", 'extract_clerk_id'); + } + + /** + * 关联用户表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 关联物流公司表 + * @return \think\model\relation\BelongsTo + */ + public function express() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\Express"); + } + + /** + * 订单状态文字描述 + * @param $value + * @param $data + * @return string + */ + public function getStateTextAttr($value, $data) + { + // 订单状态 + if (in_array($data['order_status'], [20, 30])) { + $orderStatus = [20 => '已取消', 30 => '已完成']; + return $orderStatus[$data['order_status']]; + } + // 付款状态 + if ($data['pay_status'] == 10) { + return '待付款'; + } + // 订单类型:单独购买 + if ($data['delivery_status'] == 10) { + return '已付款,待发货'; + } + if ($data['receipt_status'] == 10) { + return '已发货,待收货'; + } + return $value; + } + + /** + * 获取器:订单金额(含优惠折扣) + * @param $value + * @param $data + * @return string + */ + public function getOrderPriceAttr($value, $data) + { + // 兼容旧数据:订单金额 + if ($value == 0) { + return helper::bcadd(helper::bcsub($data['total_price'], $data['coupon_money']), $data['update_price']); + } + return $value; + } + + /** + * 改价金额(差价) + * @param $value + * @return array + */ + public function getUpdatePriceAttr($value) + { + return [ + 'symbol' => $value < 0 ? '-' : '+', + 'value' => sprintf('%.2f', abs($value)) + ]; + } + + /** + * 付款状态 + * @param $value + * @return array + */ + public function getPayTypeAttr($value) + { + return ['text' => PayTypeEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 付款状态 + * @param $value + * @return array + */ + public function getPayStatusAttr($value) + { + return ['text' => PayStatusEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 发货状态 + * @param $value + * @return array + */ + public function getDeliveryStatusAttr($value) + { + $status = [10 => '待发货', 20 => '已发货']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 收货状态 + * @param $value + * @return array + */ + public function getReceiptStatusAttr($value) + { + $status = [10 => '待收货', 20 => '已收货']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 收货状态 + * @param $value + * @return array + */ + public function getOrderStatusAttr($value) + { + $status = [10 => '进行中', 20 => '已取消', 21 => '待取消', 30 => '已完成']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 配送方式 + * @param $value + * @return array + */ + public function getDeliveryTypeAttr($value) + { + return ['text' => DeliveryTypeEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 生成订单号 + * @return string + */ + public function orderNo() + { + return OrderService::createOrderNo(); + } + + /** + * 订单详情 + * @param array|int $where + * @param array $with + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($where, $with = [ + 'user', + 'address', + 'goods' => ['image'], + 'extract', + 'express', + 'extract_shop.logo', + 'extract_clerk' + ]) + { + is_array($where) ? $filter = $where : $filter['order_id'] = (int)$where; + return self::get($filter, $with); + } + + /** + * 批量获取订单列表 + * @param $orderIds + * @param array $with 关联查询 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($orderIds, $with = []) + { + $data = $this->getListByInArray('order_id', $orderIds, $with); + return helper::arrayColumn2Key($data, 'order_id'); + } + + /** + * 批量获取订单列表 + * @param string $field + * @param array $data + * @param array $with + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getListByInArray($field, $data, $with = []) + { + return $this->with($with) + ->where($field, 'in', $data) + ->where('is_delete', '=', 0) + ->select(); + } + + /** + * 根据订单号批量查询 + * @param $orderNos + * @param array $with + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByOrderNos($orderNos, $with = []) + { + return $this->getListByInArray('order_no', $orderNos, $with); + } + + /** + * 批量更新订单 + * @param $orderIds + * @param $data + * @return false|int + */ + public function onBatchUpdate($orderIds, $data) + { + return $this->isUpdate(true)->save($data, ['order_id' => ['in', $orderIds]]); + } + + /** + * 确认核销(自提订单) + * @param int $extractClerkId 核销员id + * @return bool|false|int + */ + public function verificationOrder($extractClerkId) + { + if ( + $this['pay_status']['value'] != 20 + || $this['delivery_type']['value'] != DeliveryTypeEnum::EXTRACT + || $this['delivery_status']['value'] == 20 + || in_array($this['order_status']['value'], [20, 21]) + ) { + $this->error = '该订单不满足核销条件'; + return false; + } + return $this->transaction(function () use ($extractClerkId) { + // 更新订单状态:已发货、已收货 + $status = $this->save([ + 'extract_clerk_id' => $extractClerkId, // 核销员 + 'delivery_status' => 20, + 'delivery_time' => time(), + 'receipt_status' => 20, + 'receipt_time' => time(), + 'order_status' => 30 + ]); + // 新增订单核销记录 + ShopOrder::add( + $this['order_id'], + $this['extract_shop_id'], + $this['extract_clerk_id'], + OrderTypeEnum::MASTER + ); + // 执行订单完成后的操作 + $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::MASTER); + $OrderCompleteService->complete([$this], static::$wxapp_id); + return $status; + }); + } + +} diff --git a/source/application/common/model/OrderAddress.php b/source/application/common/model/OrderAddress.php new file mode 100644 index 0000000..9029704 --- /dev/null +++ b/source/application/common/model/OrderAddress.php @@ -0,0 +1,45 @@ + Region::getNameById($data['province_id']), + 'city' => Region::getNameById($data['city_id']), + 'region' => $data['region_id'] == 0 ? '' : Region::getNameById($data['region_id']), + ]; + } + + /** + * 获取完整地址 + * @return string + */ + public function getFullAddress() + { + return $this['region']['province'] . $this['region']['city'] . $this['region']['region'] . $this['detail']; + } + +} diff --git a/source/application/common/model/OrderExtract.php b/source/application/common/model/OrderExtract.php new file mode 100644 index 0000000..344a077 --- /dev/null +++ b/source/application/common/model/OrderExtract.php @@ -0,0 +1,15 @@ +belongsTo($model, 'image_id', 'file_id'); + } + + /** + * 关联商品表 + * @return \think\model\relation\BelongsTo + */ + public function goods() + { + return $this->belongsTo('Goods'); + } + + /** + * 关联商品sku表 + * @return \think\model\relation\BelongsTo + */ +// public function sku() +// { +// return $this->belongsTo('GoodsSku', 'spec_sku_id', 'spec_sku_id'); +// } + + /** + * 关联订单主表 + * @return \think\model\relation\BelongsTo + */ + public function orderM() + { + return $this->belongsTo('Order'); + } + + /** + * 售后单记录表 + * @return \think\model\relation\HasOne + */ + public function refund() + { + return $this->hasOne('OrderRefund'); + } + + /** + * 订单商品详情 + * @param $where + * @return OrderGoods|null + * @throws \think\exception\DbException + */ + public static function detail($where) + { + return static::get($where, ['image', 'refund']); + } + +} diff --git a/source/application/common/model/OrderRefund.php b/source/application/common/model/OrderRefund.php new file mode 100644 index 0000000..3ee8205 --- /dev/null +++ b/source/application/common/model/OrderRefund.php @@ -0,0 +1,113 @@ +belongsTo('User'); + } + + /** + * 关联订单主表 + * @return \think\model\relation\BelongsTo + */ + public function orderMaster() + { + return $this->belongsTo('Order'); + } + + /** + * 关联订单商品表 + * @return \think\model\relation\BelongsTo + */ + public function orderGoods() + { + return $this->belongsTo('OrderGoods'); + } + + /** + * 关联图片记录表 + * @return \think\model\relation\HasMany + */ + public function image() + { + return $this->hasMany('OrderRefundImage'); + } + + /** + * 关联物流公司表 + * @return \think\model\relation\BelongsTo + */ + public function express() + { + return $this->belongsTo('Express'); + } + + /** + * 关联用户表 + * @return \think\model\relation\HasOne + */ + public function address() + { + return $this->hasOne('OrderRefundAddress'); + } + + /** + * 售后类型 + * @param $value + * @return array + */ + public function getTypeAttr($value) + { + $status = [10 => '退货退款', 20 => '换货']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 商家是否同意售后 + * @param $value + * @return array + */ + public function getIsAgreeAttr($value) + { + $status = [0 => '待审核', 10 => '已同意', 20 => '已拒绝']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 售后单状态 + * @param $value + * @return array + */ + public function getStatusAttr($value) + { + $status = [0 => '进行中', 10 => '已拒绝', 20 => '已完成', 30 => '已取消']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 售后单详情 + * @param $where + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($where) + { + return static::get($where, ['order_master', 'image.file', 'order_goods.image', 'express', 'address']); + } + +} \ No newline at end of file diff --git a/source/application/common/model/OrderRefundAddress.php b/source/application/common/model/OrderRefundAddress.php new file mode 100644 index 0000000..cd23fa4 --- /dev/null +++ b/source/application/common/model/OrderRefundAddress.php @@ -0,0 +1,15 @@ +belongsTo('UploadFile', 'image_id', 'file_id') + ->bind(['file_path', 'file_name', 'file_url']); + } + +} diff --git a/source/application/common/model/Printer.php b/source/application/common/model/Printer.php new file mode 100644 index 0000000..3a59ed6 --- /dev/null +++ b/source/application/common/model/Printer.php @@ -0,0 +1,96 @@ + $value, 'text' => $printerType[$value]]; + } + + /** + * 自动转换printer_config为array格式 + * @param $value + * @return string + */ + public function getPrinterConfigAttr($value) + { + return json_decode($value, true); + } + + /** + * 自动转换printer_config为json格式 + * @param $value + * @return string + */ + public function setPrinterConfigAttr($value) + { + return json_encode($value); + } + + /** + * 获取全部 + * @return mixed + */ + public static function getAll() + { + return (new static)->where('is_delete', '=', 0) + ->order(['sort' => 'asc'])->select(); + } + + /** + * 获取列表 + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList() + { + return $this->where('is_delete', '=', 0) + ->order(['sort' => 'asc']) + ->paginate(15, false, [ + 'query' => Request::instance()->request() + ]); + } + + /** + * 物流公司详情 + * @param $printer_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($printer_id) + { + return self::get($printer_id); + } + +} diff --git a/source/application/common/model/Region.php b/source/application/common/model/Region.php new file mode 100644 index 0000000..32ca8bf --- /dev/null +++ b/source/application/common/model/Region.php @@ -0,0 +1,208 @@ + 'integer', + 'pid' => 'integer', + 'level' => 'integer', + ]; + + // 当前数据版本号 + private static $version = '1.2.3'; + + // 县级市别名 (兼容微信端命名) + private static $county = [ + '省直辖县级行政区划', + '自治区直辖县级行政区划', + ]; + + /** + * 根据id获取地区名称 + * @param $id + * @return string + */ + public static function getNameById($id) + { + return $id > 0 ? self::getCacheAll()[$id]['name'] : '其他'; + } + + /** + * 根据名称获取地区id + * @param $name + * @param int $level + * @param int $pid + * @return mixed + */ + public static function getIdByName($name, $level = 0, $pid = 0) + { + // 兼容:微信端"省直辖县级行政区划" + if (in_array($name, static::$county)) { + $name = '直辖县级'; + } + $data = self::getCacheAll(); + foreach ($data as $item) { + if ($item['name'] == $name && $item['level'] == $level && $item['pid'] == $pid) + return $item['id']; + } + return 0; + } + + /** + * 获取所有地区(树状结构) + * @return mixed + */ + public static function getCacheTree() + { + return static::getCacheData('tree'); + } + + /** + * 获取所有地区列表 + * @return mixed + */ + public static function getCacheAll() + { + return static::getCacheData('all'); + } + + /** + * 获取所有地区的总数 + * @return mixed + */ + public static function getCacheCounts() + { + return static::getCacheData('counts'); + } + + /** + * 获取缓存中的数据(存入静态变量) + * @param null $item + * @return array|mixed + */ + private static function getCacheData($item = null) + { + static $cacheData = []; + if (empty($cacheData)) { + $static = new static; + $cacheData = $static->regionCache(); + } + if (is_null($item)) { + return $cacheData; + } + return $cacheData[$item]; + } + + /** + * 获取地区缓存 + * @return array|mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function regionCache() + { + // 缓存的数据 + $complete = Cache::get('region'); + // 如果存在缓存则返回缓存的数据,否则从数据库中查询 + // 条件1: 获取缓存数据 + // 条件2: 数据版本号要与当前一致 + if ( + !empty($complete) + && isset($complete['version']) + && $complete['version'] == self::$version + ) { + return $complete; + } + // 所有地区 + $allList = $tempList = $this->getAllList(); + // 已完成的数据 + $complete = [ + 'all' => $allList, + 'tree' => $this->getTreeList($allList), + 'counts' => $this->getCount($allList), + 'version' => self::$version, + ]; + // 写入缓存 + Cache::tag('cache')->set('region', $complete); + return $complete; + } + + private static function getCount($allList) + { + $counts = [ + 'total' => count($allList), + 'province' => 0, + 'city' => 0, + 'region' => 0, + ]; + $level = [1 => 'province', 2 => 'city', 3 => 'region']; + foreach ($allList as $item) { + $counts[$level[$item['level']]]++; + } + return $counts; + } + + /** + * 格式化为树状格式 + * @param $allList + * @return array + */ + private function getTreeList($allList) + { + $treeList = []; + foreach ($allList as $pKey => $province) { + if ($province['level'] == 1) { // 省份 + $treeList[$province['id']] = $province; + unset($allList[$pKey]); + foreach ($allList as $cKey => $city) { + if ($city['level'] == 2 && $city['pid'] == $province['id']) { // 城市 + $treeList[$province['id']]['city'][$city['id']] = $city; + unset($allList[$cKey]); + foreach ($allList as $rKey => $region) { + if ($region['level'] == 3 && $region['pid'] == $city['id']) { // 地区 + $treeList[$province['id']]['city'][$city['id']]['region'][$region['id']] = $region; + unset($allList[$rKey]); + } + } + } + } + } + } + return $treeList; + } + + /** + * 从数据库中获取所有地区 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getAllList() + { + $list = self::useGlobalScope(false) + ->field('id, pid, name, level') + ->select() + ->toArray(); + return helper::arrayColumn2Key($list, 'id'); + } +} diff --git a/source/application/common/model/ReturnAddress.php b/source/application/common/model/ReturnAddress.php new file mode 100644 index 0000000..28ac670 --- /dev/null +++ b/source/application/common/model/ReturnAddress.php @@ -0,0 +1,25 @@ +toArray(), null, 'key'); + Cache::tag('cache')->set('setting_' . $wxapp_id, $data); + } + return $static->getMergeData($data); + } + + /** + * 合并用户设置与默认数据 + * @param $userData + * @return array + */ + private function getMergeData($userData) + { + $defaultData = $this->defaultData(); + // 商城设置:配送方式 + if (isset($userData['store']['values']['delivery_type'])) { + unset($defaultData['store']['values']['delivery_type']); + } + return array_merge_multiple($defaultData, $userData); + } + + /** + * 默认配置 + * @param null|string $storeName + * @return array + */ + public function defaultData($storeName = null) + { + return [ + 'store' => [ + 'key' => 'store', + 'describe' => '商城设置', + 'values' => [ + // 商城名称 + 'name' => $storeName ?: '萤火小程序商城', + // 配送方式 + 'delivery_type' => array_keys(DeliveryTypeEnum::data()), + // 快递100 + 'kuaidi100' => [ + 'customer' => '', + 'key' => '', + ] + ], + ], + 'trade' => [ + 'key' => 'trade', + 'describe' => '交易设置', + 'values' => [ + 'order' => [ + 'close_days' => '3', + 'receive_days' => '10', + 'refund_days' => '7' + ], + 'freight_rule' => '10', + ] + ], + 'storage' => [ + 'key' => 'storage', + 'describe' => '上传设置', + 'values' => [ + 'default' => 'local', + 'engine' => [ + 'local' => [], + 'qiniu' => [ + 'bucket' => '', + 'access_key' => '', + 'secret_key' => '', + 'domain' => 'http://' + ], + 'aliyun' => [ + 'bucket' => '', + 'access_key_id' => '', + 'access_key_secret' => '', + 'domain' => 'http://' + ], + 'qcloud' => [ + 'bucket' => '', + 'region' => '', + 'secret_id' => '', + 'secret_key' => '', + 'domain' => 'http://' + ], + ] + ], + ], + 'sms' => [ + 'key' => 'sms', + 'describe' => '短信通知', + 'values' => [ + 'default' => 'aliyun', + 'engine' => [ + 'aliyun' => [ + 'AccessKeyId' => '', + 'AccessKeySecret' => '', + 'sign' => '萤火科技', + 'order_pay' => [ + 'is_enable' => '0', + 'template_code' => '', + 'accept_phone' => '', + ], + ], + ], + ], + ], + 'tplMsg' => [ + 'key' => 'tplMsg', + 'describe' => '模板消息', + 'values' => [ + 'payment' => [ + 'is_enable' => '0', + 'template_id' => '', + ], + 'delivery' => [ + 'is_enable' => '0', + 'template_id' => '', + ], + 'refund' => [ + 'is_enable' => '0', + 'template_id' => '', + ], + ], + ], + 'printer' => [ + 'key' => 'printer', + 'describe' => '小票打印机设置', + 'values' => [ + 'is_open' => '0', // 是否开启打印 + 'printer_id' => '', // 打印机id + 'order_status' => [], // 订单类型 10下单打印 20付款打印 30确认收货打印 + ], + ], + 'full_free' => [ + 'key' => 'full_free', + 'describe' => '满额包邮设置', + 'values' => [ + 'is_open' => '0', // 是否开启满额包邮 + 'money' => '', // 单笔订单额度 + 'notin_region' => [ // 不参与包邮的地区 + 'province' => [], + 'citys' => [], + 'treeData' => [], + ], + 'notin_goods' => [], // 不参与包邮的商品 (商品id集) + ], + ], + 'recharge' => [ + 'key' => 'recharge', + 'describe' => '用户充值设置', + 'values' => [ + 'is_entrance' => '1', // 是否允许用户充值 + 'is_custom' => '1', // 是否允许自定义金额 + 'is_match_plan' => '1', // 自定义金额是否自动匹配合适的套餐 + 'describe' => "1. 账户充值仅限微信在线方式支付,充值金额实时到账;\n" . + "2. 账户充值套餐赠送的金额即时到账;\n" . + "3. 账户余额有效期:自充值日起至用完即止;\n" . + "4. 若有其它疑问,可拨打客服电话400-000-1234", // 充值说明 + ], + ], + 'points' => [ + 'key' => 'points', + 'describe' => '积分设置', + 'values' => [ + 'points_name' => '积分', // 积分名称自定义 + 'is_shopping_gift' => '0', // 是否开启购物送积分 + 'gift_ratio' => '100', // 是否开启购物送积分 + 'is_shopping_discount' => '0', // 是否允许下单使用积分抵扣 + 'discount' => [ // 积分抵扣 + 'discount_ratio' => '0.01', // 积分抵扣比例 + 'full_order_price' => '100.00', // 订单满[?]元 + 'max_money_ratio' => '10', // 最高可抵扣订单额百分比 + ], + // 充值说明 + 'describe' => "a) 积分不可兑现、不可转让,仅可在本平台使用;\n" . + "b) 您在本平台参加特定活动也可使用积分,详细使用规则以具体活动时的规则为准;\n" . + "c) 积分的数值精确到个位(小数点后全部舍弃,不进行四舍五入)\n" . + "d) 买家在完成该笔交易(订单状态为“已签收”)后才能得到此笔交易的相应积分,如购买商品参加店铺其他优惠,则优惠的金额部分不享受积分获取;", + ], + ], + ]; + } + +} diff --git a/source/application/common/model/Spec.php b/source/application/common/model/Spec.php new file mode 100644 index 0000000..ba30cc7 --- /dev/null +++ b/source/application/common/model/Spec.php @@ -0,0 +1,15 @@ +belongsTo('Spec'); + } + +} diff --git a/source/application/common/model/Store.php b/source/application/common/model/Store.php new file mode 100644 index 0000000..1e0b898 --- /dev/null +++ b/source/application/common/model/Store.php @@ -0,0 +1,13 @@ +belongsTo('UploadGroup', 'group_id'); + } + + /** + * 获取图片完整路径 + * @param $value + * @param $data + * @return string + */ + public function getFilePathAttr($value, $data) + { + if ($data['storage'] === 'local') { + return self::$base_url . 'uploads/' . $data['file_name']; + } + return $data['file_url'] . '/' . $data['file_name']; + } + + /** + * 文件详情 + * @param $file_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($file_id) + { + return self::get($file_id); + } + + /** + * 根据文件名查询文件id + * @param $fileName + * @return mixed + */ + public static function getFildIdByName($fileName) + { + return (new static)->where(['file_name' => $fileName])->value('file_id'); + } + + /** + * 查询文件id + * @param $fileId + * @return mixed + */ + public static function getFileName($fileId) + { + return (new static)->where(['file_id' => $fileId])->value('file_name'); + } + + /** + * 添加新记录 + * @param $data + * @return false|int + */ + public function add($data) + { + $data['wxapp_id'] = self::$wxapp_id; + return $this->save($data); + } + +} diff --git a/source/application/common/model/UploadFileUsed.php b/source/application/common/model/UploadFileUsed.php new file mode 100644 index 0000000..30aefbc --- /dev/null +++ b/source/application/common/model/UploadFileUsed.php @@ -0,0 +1,14 @@ +belongsTo("app\\{$module}\\model\\user\\Grade"); + } + + /** + * 关联收货地址表 + * @return \think\model\relation\HasMany + */ + public function address() + { + return $this->hasMany('UserAddress'); + } + + /** + * 关联收货地址表 (默认地址) + * @return \think\model\relation\BelongsTo + */ + public function addressDefault() + { + return $this->belongsTo('UserAddress', 'address_id'); + } + + /** + * 显示性别 + * @param $value + * @return mixed + */ + public function getGenderAttr($value) + { + return $this->gender[$value]; + } + + /** + * 获取用户信息 + * @param $where + * @param $with + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($where, $with = ['address', 'addressDefault']) + { + $filter = ['is_delete' => 0]; + if (is_array($where)) { + $filter = array_merge($filter, $where); + } else { + $filter['user_id'] = (int)$where; + } + return static::get($filter, $with); + } + + /** + * 累积用户的实际消费金额 + * @param $userId + * @param $expendMoney + * @return int|true + * @throws \think\Exception + */ + public function setIncUserExpend($userId, $expendMoney) + { + return $this->where(['user_id' => $userId])->setInc('expend_money', $expendMoney); + } + + /** + * 指定会员等级下是否存在用户 + * @param $gradeId + * @return bool + */ + public static function checkExistByGradeId($gradeId) + { + $model = new static; + return !!$model->where('grade_id', '=', (int)$gradeId) + ->where('is_delete', '=', 0) + ->value('user_id'); + } + + /** + * 累积用户总消费金额 + * @param $money + * @return int|true + * @throws \think\Exception + */ + public function setIncPayMoney($money) + { + return $this->setInc('pay_money', $money); + } + + /** + * 累积用户实际消费的金额 (批量) + * @param $data + * @return array|false + * @throws \Exception + */ + public function onBatchIncExpendMoney($data) + { + foreach ($data as $userId => $expendMoney) { + $this->where(['user_id' => $userId])->setInc('expend_money', $expendMoney); + } + return true; + } + + /** + * 累积用户的可用积分数量 (批量) + * @param $data + * @return array|false + * @throws \Exception + */ + public function onBatchIncPoints($data) + { + foreach ($data as $userId => $expendMoney) { + $this->where(['user_id' => $userId])->setInc('points', $expendMoney); + } + return true; + } + + /** + * 累积用户的可用积分 + * @param $points + * @param $describe + * @return int|true + * @throws \think\Exception + */ + public function setIncPoints($points, $describe) + { + // 新增积分变动明细 + PointsLogModel::add([ + 'user_id' => $this['user_id'], + 'value' => $points, + 'describe' => $describe, + ]); + // 更新用户可用积分 + return $this->setInc('points', $points); + } + +} diff --git a/source/application/common/model/UserAddress.php b/source/application/common/model/UserAddress.php new file mode 100644 index 0000000..dae311f --- /dev/null +++ b/source/application/common/model/UserAddress.php @@ -0,0 +1,36 @@ + Region::getNameById($data['province_id']), + 'city' => Region::getNameById($data['city_id']), + 'region' => $data['region_id'] == 0 ? $data['district'] + : Region::getNameById($data['region_id']), + ]; + } + +} diff --git a/source/application/common/model/UserCoupon.php b/source/application/common/model/UserCoupon.php new file mode 100644 index 0000000..be22b35 --- /dev/null +++ b/source/application/common/model/UserCoupon.php @@ -0,0 +1,133 @@ +belongsTo('User'); + } + + /** + * 优惠券状态 + * @param $value + * @param $data + * @return array + */ + public function getStateAttr($value, $data) + { + if ($data['is_use']) { + return ['text' => '已使用', 'value' => 0]; + } + if ($data['is_expire']) { + return ['text' => '已过期', 'value' => 0]; + } + return ['text' => '', 'value' => 1]; + } + + /** + * 优惠券颜色 + * @param $value + * @return mixed + */ + public function getColorAttr($value) + { + $status = [10 => 'blue', 20 => 'red', 30 => 'violet', 40 => 'yellow']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 优惠券类型 + * @param $value + * @return mixed + */ + public function getCouponTypeAttr($value) + { + $status = [10 => '满减券', 20 => '折扣券']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 折扣率 + * @param $value + * @return mixed + */ + public function getDiscountAttr($value) + { + return $value / 10; + } + + /** + * 有效期-开始时间 + * @param $value + * @return mixed + */ + public function getStartTimeAttr($value) + { + return ['text' => date('Y/m/d', $value), 'value' => $value]; + } + + /** + * 有效期-结束时间 + * @param $value + * @return mixed + */ + public function getEndTimeAttr($value) + { + return ['text' => date('Y/m/d', $value), 'value' => $value]; + } + + /** + * 优惠券详情 + * @param $coupon_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($coupon_id) + { + return static::get($coupon_id); + } + + /** + * 设置优惠券使用状态 + * @param int $couponId 用户的优惠券id + * @param bool $isUse 是否已使用 + * @return false|int + */ + public static function setIsUse($couponId, $isUse = true) + { + return (new static)->save(['is_use' => (int)$isUse], ['user_coupon_id' => $couponId]); + } + +} \ No newline at end of file diff --git a/source/application/common/model/Wxapp.php b/source/application/common/model/Wxapp.php new file mode 100644 index 0000000..650f9bc --- /dev/null +++ b/source/application/common/model/Wxapp.php @@ -0,0 +1,68 @@ +hasOne('WxappNavbar'); + } + + /** + * 小程序页面 + * @return \think\model\relation\HasOne + */ + public function diyPage() + { + return $this->hasOne('WxappPage'); + } + + /** + * 获取小程序信息 + * @param null $wxapp_id + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($wxapp_id = null) + { + return self::get($wxapp_id ?: []); + } + + /** + * 从缓存中获取小程序信息 + * @param null $wxapp_id + * @return mixed|null|static + * @throws BaseException + * @throws \think\exception\DbException + */ + public static function getWxappCache($wxapp_id = null) + { + if (is_null($wxapp_id)) { + $self = new static(); + $wxapp_id = $self::$wxapp_id; + } + if (!$data = Cache::get('wxapp_' . $wxapp_id)) { + $data = self::detail($wxapp_id); + if (empty($data)) throw new BaseException(['msg' => '未找到当前小程序信息']); + Cache::tag('cache')->set('wxapp_' . $wxapp_id, $data); + } + return $data; + } + +} diff --git a/source/application/common/model/WxappCategory.php b/source/application/common/model/WxappCategory.php new file mode 100644 index 0000000..3333bec --- /dev/null +++ b/source/application/common/model/WxappCategory.php @@ -0,0 +1,24 @@ +order(['sort' => 'asc'])->select(); + } + + /** + * 帮助详情 + * @param $help_id + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($help_id) + { + return self::get($help_id); + } + +} diff --git a/source/application/common/model/WxappNavbar.php b/source/application/common/model/WxappNavbar.php new file mode 100644 index 0000000..721547d --- /dev/null +++ b/source/application/common/model/WxappNavbar.php @@ -0,0 +1,51 @@ + '#000000', 20 => '#ffffff']; + return ['text' => $color[$value], 'value' => $value]; + } + + /** + * 小程序导航栏详情 + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail() + { + return self::get([]); + } + + /** + * 新增小程序导航栏默认设置 + * @param $wxapp_id + * @param $wxapp_title + * @return false|int + */ + public function insertDefault($wxapp_id, $wxapp_title) + { + return $this->save([ + 'wxapp_title' => $wxapp_title, + 'top_text_color' => 20, + 'top_background_color' => '#fd4a5f', + 'wxapp_id' => $wxapp_id + ]); + } + +} diff --git a/source/application/common/model/WxappPage.php b/source/application/common/model/WxappPage.php new file mode 100644 index 0000000..f715177 --- /dev/null +++ b/source/application/common/model/WxappPage.php @@ -0,0 +1,697 @@ + 'page', + 'name' => '页面设置', + 'params' => [ + 'name' => '页面名称', + 'title' => '页面标题', + 'share_title' => '分享标题' + ], + 'style' => [ + 'titleTextColor' => 'black', + 'titleBackgroundColor' => '#ffffff', + ] + ]; + } + + /** + * 页面diy元素默认数据 + * @return array + */ + public function getDefaultItems() + { + return [ + 'search' => [ + 'name' => '搜索框', + 'type' => 'search', + 'params' => ['placeholder' => '请输入关键字进行搜索'], + 'style' => [ + 'textAlign' => 'left', + 'searchStyle' => 'square' + ] + ], + 'banner' => [ + 'name' => '图片轮播', + 'type' => 'banner', + 'style' => [ + 'btnColor' => '#ffffff', + 'btnShape' => 'round' + ], + 'params' => [ + 'interval' => '2800' + ], + 'data' => [ + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/banner/01.png', + 'linkUrl' => '' + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/banner/01.png', + 'linkUrl' => '' + ] + ] + ], + 'imageSingle' => [ + 'name' => '单图组', + 'type' => 'imageSingle', + 'style' => [ + 'paddingTop' => 0, + 'paddingLeft' => 0, + 'background' => '#ffffff' + ], + 'data' => [ + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/banner/01.png', + 'imgName' => 'image-1.jpg', + 'linkUrl' => '' + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/banner/01.png', + 'imgName' => 'banner-2.jpg', + 'linkUrl' => '' + ] + ] + ], + 'navBar' => [ + 'name' => '导航组', + 'type' => 'navBar', + 'style' => ['background' => '#ffffff', 'rowsNum' => '4'], + 'data' => [ + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/navbar/01.png', + 'imgName' => 'icon-1.png', + 'linkUrl' => '', + 'text' => '按钮文字1', + 'color' => '#666666' + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/navbar/01.png', + 'imgName' => 'icon-2.jpg', + 'linkUrl' => '', + 'text' => '按钮文字2', + 'color' => '#666666' + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/navbar/01.png', + 'imgName' => 'icon-3.jpg', + 'linkUrl' => '', + 'text' => '按钮文字3', + 'color' => '#666666' + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/navbar/01.png', + 'imgName' => 'icon-4.jpg', + 'linkUrl' => '', + 'text' => '按钮文字4', + 'color' => '#666666' + ] + ] + ], + 'blank' => [ + 'name' => '辅助空白', + 'type' => 'blank', + 'style' => [ + 'height' => '20', + 'background' => '#ffffff' + ] + ], + 'guide' => [ + 'name' => '辅助线', + 'type' => 'guide', + 'style' => [ + 'background' => '#ffffff', + 'lineStyle' => 'solid', + 'lineHeight' => '1', + 'lineColor' => "#000000", + 'paddingTop' => 10 + ] + ], + 'video' => [ + 'name' => '视频组', + 'type' => 'video', + 'params' => [ + 'videoUrl' => 'http://wxsnsdy.tc.qq.com/105/20210/snsdyvideodownload?filekey=30280201010421301f0201690402534804102ca905ce620b1241b726bc41dcff44e00204012882540400', + 'poster' => self::$base_url . 'assets/store/img/diy/video_poster.png', + 'autoplay' => '0' + ], + 'style' => [ + 'paddingTop' => '0', + 'height' => '190' + ] + ], + 'article' => [ + 'name' => '文章组', + 'type' => 'article', + 'params' => [ + 'source' => 'auto', // choice; auto + 'auto' => [ + 'category' => 0, + 'showNum' => 6 + ] + ], + 'style' => [], + // '自动获取' => 默认数据 + 'defaultData' => [ + [ + 'article_title' => '此处显示文章标题', + 'show_type' => 10, + 'image' => self::$base_url . 'assets/store/img/diy/article/01.png', + 'views_num' => '309' + ], + [ + 'article_title' => '此处显示文章标题', + 'show_type' => 10, + 'image' => self::$base_url . 'assets/store/img/diy/article/01.png', + 'views_num' => '309' + ] + ], + // '手动选择' => 默认数据 + 'data' => [] + ], + 'special' => [ + 'name' => '头条快报', + 'type' => 'special', + 'params' => [ + 'source' => 'auto', // choice; auto + 'auto' => [ + 'category' => 0, + 'showNum' => 6 + ] + ], + 'style' => [ + 'display' => '1', + 'image' => self::$base_url . 'assets/store/img/diy/special.png' + ], + // '自动获取' => 默认数据 + 'defaultData' => [ + [ + 'article_title' => '张小龙4小时演讲:你和高手之间,隔着“简单”二字' + ], + [ + 'article_title' => '张小龙4小时演讲:你和高手之间,隔着“简单”二字' + ] + ], + // '手动选择' => 默认数据 + 'data' => [] + ], + 'notice' => [ + 'name' => '公告组', + 'type' => 'notice', + 'params' => [ + 'text' => '这里是第一条自定义公告的标题', + 'icon' => self::$base_url . 'assets/store/img/diy/notice.png' + ], + 'style' => [ + 'paddingTop' => '4', + 'background' => '#ffffff', + 'textColor' => '#000000' + ] + ], + 'richText' => [ + 'name' => '富文本', + 'type' => 'richText', + 'params' => [ + 'content' => '

这里是文本的内容

' + ], + 'style' => [ + 'paddingTop' => '0', + 'paddingLeft' => '0', + 'background' => '#ffffff' + ] + ], + 'window' => [ + 'name' => '图片橱窗', + 'type' => 'window', + 'style' => [ + 'paddingTop' => '0', + 'paddingLeft' => '0', + 'background' => '#ffffff', + 'layout' => '2' + ], + 'data' => [ + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/window/01.jpg', + 'linkUrl' => '' + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/window/02.jpg', + 'linkUrl' => '' + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/window/03.jpg', + 'linkUrl' => '' + ], + [ + 'imgUrl' => self::$base_url . 'assets/store/img/diy/window/04.jpg', + 'linkUrl' => '' + ] + ], + 'dataNum' => 4 + ], + 'goods' => [ + 'name' => '商品组', + 'type' => 'goods', + 'params' => [ + 'source' => 'auto', // choice; auto + 'auto' => [ + 'category' => 0, + 'goodsSort' => 'all', // all; sales; price + 'showNum' => 6 + ] + ], + 'style' => [ + 'background' => '#F6F6F6', + 'display' => 'list', // list; slide + 'column' => '2', + 'show' => [ + 'goodsName' => '1', + 'goodsPrice' => '1', + 'linePrice' => '1', + 'sellingPoint' => '0', + 'goodsSales' => '0', + ] + ], + // '自动获取' => 默认数据 + 'defaultData' => [ + [ + 'goods_name' => '此处显示商品名称', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'goods_price' => '99.00', + 'line_price' => '139.00', + 'selling_point' => '此款商品美观大方 不容错过', + 'goods_sales' => '100', + ], + [ + 'goods_name' => '此处显示商品名称', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'goods_price' => '99.00', + 'line_price' => '139.00', + 'selling_point' => '此款商品美观大方 不容错过', + 'goods_sales' => '100', + ], + [ + 'goods_name' => '此处显示商品名称', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'goods_price' => '99.00', + 'line_price' => '139.00', + 'selling_point' => '此款商品美观大方 不容错过', + 'goods_sales' => '100', + ], + [ + 'goods_name' => '此处显示商品名称', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'goods_price' => '99.00', + 'line_price' => '139.00', + 'selling_point' => '此款商品美观大方 不容错过', + 'goods_sales' => '100', + ] + ], + // '手动选择' => 默认数据 + 'data' => [ + [ + 'goods_name' => '此处显示商品名称', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'goods_price' => '99.00', + 'line_price' => '139.00', + 'selling_point' => '此款商品美观大方 不容错过', + 'goods_sales' => '100', + 'is_default' => true + ], + [ + 'goods_name' => '此处显示商品名称', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'goods_price' => '99.00', + 'line_price' => '139.00', + 'selling_point' => '此款商品美观大方 不容错过', + 'goods_sales' => '100', + 'is_default' => true + ] + ] + ], + 'coupon' => [ + 'name' => '优惠券组', + 'type' => 'coupon', + 'style' => [ + 'paddingTop' => '10', + 'background' => '#ffffff' + ], + 'params' => [ + 'limit' => '5' + ], + 'data' => [ + [ + 'color' => 'red', + 'reduce_price' => '10', + 'min_price' => '100.00' + ], + [ + 'color' => 'violet', + 'reduce_price' => '10', + 'min_price' => '100.00' + ] + ] + ], + 'sharingGoods' => [ + 'name' => '拼团商品组', + 'type' => 'sharingGoods', + 'params' => [ + 'source' => 'auto', // choice; auto + 'auto' => [ + 'category' => 0, + 'goodsSort' => 'all', // all; sales; price + 'showNum' => 6 + ] + ], + 'style' => [ + 'background' => '#F6F6F6', + 'show' => [ + 'goodsName' => '1', + 'sellingPoint' => '1', + 'sharingPrice' => '1', + 'linePrice' => '1' + ] + ], + // '自动获取' => 默认数据 + 'defaultData' => [ + [ + 'goods_name' => '此处是拼团商品', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'selling_point' => '此款商品美观大方 性价比较高 不容错过', + 'sharing_price' => '99.00', + 'line_price' => '139.00', + ], + [ + 'goods_name' => '此处是拼团商品', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'selling_point' => '此款商品美观大方 性价比较高 不容错过', + 'goods_price' => '99.00', + 'line_price' => '139.00', + ], + [ + 'goods_name' => '此处是拼团商品', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'selling_point' => '此款商品美观大方 性价比较高 不容错过', + 'sharing_price' => '99.00', + 'line_price' => '139.00', + ], + [ + 'goods_name' => '此处是拼团商品', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'selling_point' => '此款商品美观大方 性价比较高 不容错过', + 'sharing_price' => '99.00', + 'line_price' => '139.00', + ] + ], + // '手动选择' => 默认数据 + 'data' => [ + [ + 'goods_name' => '此处是拼团商品', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'selling_point' => '此款商品美观大方 性价比较高 不容错过', + 'sharing_price' => '99.00', + 'line_price' => '139.00', + 'is_default' => true + ], + [ + 'goods_name' => '此处是拼团商品', + 'image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'selling_point' => '此款商品美观大方 性价比较高 不容错过', + 'sharing_price' => '99.00', + 'line_price' => '139.00', + 'is_default' => true + ] + ] + ], + 'bargainGoods' => [ + 'name' => '砍价商品组', + 'type' => 'bargainGoods', + 'params' => [ + 'source' => 'auto', // choice; auto + 'auto' => [ + 'category' => 0, + 'goodsSort' => 'all', // all; sales; price + 'showNum' => 6 + ] + ], + 'style' => [ + 'background' => '#F6F6F6', + 'show' => [ + 'goodsName' => '1', + 'peoples' => '1', + 'floorPrice' => '1', + 'originalPrice' => '1' + ] + ], + 'demo' => [ + 'helps_count' => 2, + 'helps' => [ + ['avatarUrl' => 'http://tva1.sinaimg.cn/large/0060lm7Tly1g4c7zrytvvj30dw0dwwes.jpg'], + ['avatarUrl' => 'http://tva1.sinaimg.cn/large/0060lm7Tly1g4c7zs2u5ej30b40b4dfx.jpg'], + ] + ], + // '自动获取' => 默认数据 + 'defaultData' => [ + [ + 'goods_name' => '此处是砍价商品', + 'goods_image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'floor_price' => '0.01', + 'original_price' => '139.00', + ], + [ + 'goods_name' => '此处是砍价商品', + 'goods_image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'floor_price' => '0.01', + 'original_price' => '139.00', + ], + ], + // '手动选择' => 默认数据 + 'data' => [ + [ + 'goods_name' => '此处是砍价商品', + 'goods_image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'floor_price' => '0.01', + 'original_price' => '139.00', + ], + [ + 'goods_name' => '此处是砍价商品', + 'goods_image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'floor_price' => '0.01', + 'original_price' => '139.00', + ], + ] + ], + 'sharpGoods' => [ + 'name' => '秒杀商品组', + 'type' => 'sharpGoods', + 'params' => [ + 'showNum' => 6 + ], + 'style' => [ + 'background' => '#ffffff', + 'column' => '3', + 'show' => [ + 'goodsName' => '1', + 'seckillPrice' => '1', + 'originalPrice' => '1' + ] + ], + // '手动选择' => 默认数据 + 'data' => [ + [ + 'goods_name' => '此处是秒杀商品', + 'goods_image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'seckill_price' => '69.00', + 'original_price' => '139.00', + ], + [ + 'goods_name' => '此处是秒杀商品', + 'goods_image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'seckill_price' => '69.00', + 'original_price' => '139.00', + ], + [ + 'goods_name' => '此处是秒杀商品', + 'goods_image' => self::$base_url . 'assets/store/img/diy/goods/01.png', + 'seckill_price' => '69.00', + 'original_price' => '139.00', + ], + ] + ], + 'shop' => [ + 'name' => '线下门店', + 'type' => 'shop', + 'params' => [ + 'source' => 'auto', // choice; auto + 'auto' => [ + 'showNum' => 6 + ] + ], + 'style' => [ + ], + // '自动获取' => 默认数据 + 'defaultData' => [ + [ + 'shop_name' => '此处显示门店名称', + 'logo_image' => self::$base_url . 'assets/store/img/diy/circular.png', + 'phone' => '010-6666666', + 'region' => [ + 'province' => 'xx省', + 'city' => 'xx市', + 'region' => 'xx区' + ], + 'address' => 'xx街道', + ], + [ + 'shop_name' => '此处显示门店名称', + 'logo_image' => self::$base_url . 'assets/store/img/diy/circular.png', + 'phone' => '010-6666666', + 'region' => [ + 'province' => 'xx省', + 'city' => 'xx市', + 'region' => 'xx区' + ], + 'address' => 'xx街道', + ], + ], + // '手动选择' => 默认数据 + 'data' => [ + [ + 'shop_name' => '此处显示门店名称', + 'logo_image' => self::$base_url . 'assets/store/img/diy/circular.png', + 'phone' => '010-6666666', + 'region' => [ + 'province' => 'xx省', + 'city' => 'xx市', + 'region' => 'xx区' + ], + 'address' => 'xx街道', + ], + ] + ], + 'officialAccount' => [ + 'name' => '关注公众号', + 'type' => 'officialAccount', + 'params' => [], + 'style' => [] + ], + 'service' => [ + 'name' => '在线客服', + 'type' => 'service', + 'params' => [ + 'type' => 'chat', // '客服类型' => chat在线聊天,phone拨打电话 + 'image' => self::$base_url . 'assets/store/img/diy/service.png', + 'phone_num' => '' + ], + 'style' => [ + 'right' => '1', + 'bottom' => '10', + 'opacity' => '100' + ] + ], + ]; + } + + /** + * 格式化页面数据 + * @param $json + * @return array + */ + public function getPageDataAttr($json) + { + // 旧版数据转义 + $array = $this->_transferToNewData($json); + // 合并默认数据 + return $this->_mergeDefaultData($array); + } + + /** + * 自动转换data为json格式 + * @param $value + * @return string + */ + public function setPageDataAttr($value) + { + return json_encode($value ?: ['items' => []]); + } + + /** + * diy页面详情 + * @param int $page_id + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($page_id) + { + return static::get(['page_id' => $page_id]); + } + + /** + * diy页面详情 + * @return static|null + * @throws \think\exception\DbException + */ + public static function getHomePage() + { + return self::get(['page_type' => 10]); + } + + /** + * 旧版数据转义为新版格式 + * @param $json + * @return array + */ + private function _transferToNewData($json) + { + $array = json_decode($json, true); + $items = $array['items']; + if (isset($items['page'])) { + unset($items['page']); + } + foreach ($items as &$item) { + isset($item['data']) && $item['data'] = array_values($item['data']); + } + return [ + 'page' => isset($array['page']) ? $array['page'] : $array['items']['page'], + 'items' => array_values(array_filter($items)) + ]; + } + + /** + * 合并默认数据 + * @param $array + * @return mixed + */ + private function _mergeDefaultData($array) + { + $array['page'] = array_merge_multiple($this->getDefaultPage(), $array['page']); + $defaultItems = $this->getDefaultItems(); + foreach ($array['items'] as &$item) { + if (isset($defaultItems[$item['type']])) { + array_key_exists('data', $item) && $defaultItems[$item['type']]['data'] = []; + $item = array_merge_multiple($defaultItems[$item['type']], $item); + } + } + return $array; + } + +} diff --git a/source/application/common/model/WxappPrepayId.php b/source/application/common/model/WxappPrepayId.php new file mode 100644 index 0000000..0ea3ad3 --- /dev/null +++ b/source/application/common/model/WxappPrepayId.php @@ -0,0 +1,57 @@ +where('order_id', '=', $orderId) + ->where('order_type', '=', $orderType) + ->order(['create_time' => 'desc']) + ->find(); + } + + /** + * 记录prepay_id使用次数 + * @return int|true + * @throws \think\Exception + */ + public function updateUsedTimes() + { + return $this->setInc('used_times', 1); + } + + /** + * 更新prepay_id已付款状态 + * @param $orderId + * @param $orderType + * @return false|int + */ + public static function updatePayStatus($orderId, $orderType = OrderTypeEnum::MASTER) + { + // 获取prepay_id记录 + $model = static::detail($orderId, $orderType); + if (empty($model)) { + return false; + } + // 更新记录 + return $model->save(['can_use_times' => 3, 'pay_status' => 1]); + } + +} \ No newline at end of file diff --git a/source/application/common/model/admin/User.php b/source/application/common/model/admin/User.php new file mode 100644 index 0000000..d625d31 --- /dev/null +++ b/source/application/common/model/admin/User.php @@ -0,0 +1,16 @@ +order(['sort' => 'asc', 'create_time' => 'asc'])->select(); + $all = !empty($data) ? $data->toArray() : []; + Cache::tag('cache')->set('article_category_' . $model::$wxapp_id, $all); + } + return Cache::get('article_category_' . $model::$wxapp_id); + } + +} diff --git a/source/application/common/model/bargain/Active.php b/source/application/common/model/bargain/Active.php new file mode 100644 index 0000000..1a2aadf --- /dev/null +++ b/source/application/common/model/bargain/Active.php @@ -0,0 +1,98 @@ + 'integer', + 'is_floor_buy' => 'integer', + 'status' => 'integer', + ]; + + /** + * 追加的字段 + * @var array $append + */ + protected $append = [ + 'is_start', // 活动已开启 + 'is_end', // 活动已结束 + 'active_sales', // 活动销量 + ]; + + /** + * 获取器:活动开始时间 + * @param $value + * @return false|string + */ + public function getStartTimeAttr($value) + { + return \format_time($value); + } + + /** + * 获取器:活动结束时间 + * @param $value + * @return false|string + */ + public function getEndTimeAttr($value) + { + return \format_time($value); + } + + /** + * 获取器:活动是否已开启 + * @param $value + * @param $data + * @return false|string + */ + public function getIsStartAttr($value, $data) + { + return $value ?: $data['start_time'] <= time(); + } + + /** + * 获取器:活动是否已结束 + * @param $value + * @param $data + * @return false|string + */ + public function getIsEndAttr($value, $data) + { + return $value ?: $data['end_time'] <= time(); + } + + /** + * 获取器:显示销量 + * @param $value + * @param $data + * @return false|string + */ + public function getActiveSalesAttr($value, $data) + { + return $value ?: $data['actual_sales'] + $data['initial_sales']; + } + + /** + * 砍价活动详情 + * @param $activeId + * @param array $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($activeId, $with = []) + { + return static::get($activeId, $with); + } + +} \ No newline at end of file diff --git a/source/application/common/model/bargain/Setting.php b/source/application/common/model/bargain/Setting.php new file mode 100644 index 0000000..1b69c2e --- /dev/null +++ b/source/application/common/model/bargain/Setting.php @@ -0,0 +1,103 @@ +toArray(), null, 'key'); + Cache::tag('cache')->set($cacheKey, $data); + } + return array_merge_multiple($self->defaultData(), $data); + } + + /** + * 获取设置项信息 + * @param $key + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($key) + { + return static::get(compact('key')); + } + + /** + * 默认配置 + * @return array + */ + public function defaultData() + { + return [ + 'basic' => [ + 'key' => 'basic', + 'describe' => '基础设置', + 'values' => [ + // 是否开启分销 + 'is_dealer' => '0', + // 砍价规则 + 'bargain_rules' => "活动期间,用户可以在砍价活动页选择活动商品发起砍价,可通过微信分享砍价商品活动页面给好友,并通过好友助力砍价,将商品砍至一定金额。\n\n" . + "每次砍价金额随机,可砍出最高商品日常价内的随机金额,参与好友越多越容易成功。\n\n" . + "以最终砍价后的优惠价格购买该商品,且用户须在活动时间结束之前进行支付购买,否则砍价商品价格将过期失效。\n\n" . + "商品库存有限,以前台展示的库存数量为准,先到先得。", + // 模板消息 + 'template_msg' => [] + ] + ] + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/model/bargain/Task.php b/source/application/common/model/bargain/Task.php new file mode 100644 index 0000000..eb87039 --- /dev/null +++ b/source/application/common/model/bargain/Task.php @@ -0,0 +1,140 @@ + 'integer', + 'is_buy' => 'integer', + 'status' => 'integer', + 'is_delete' => 'integer', + ]; + + /** + * 追加的字段 + * @var array $append + */ + protected $append = [ + 'is_end', // 是否已结束 + 'surplus_money', // 剩余砍价金额 + 'bargain_rate', // 砍价进度百分比(0-100) + ]; + + /** + * 订单模型初始化 + */ + public static function init() + { + parent::init(); + // 监听行为管理 + $static = new static; + Hook::listen('bargain_task', $static); + } + + /** + * 关联用户表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + $module = self::getCalledModule() ?: 'common'; + return $this->BelongsTo("app\\{$module}\\model\\User"); + } + + /** + * 获取器:任务结束时间 + * @param $value + * @return false|string + */ + public function getEndTimeAttr($value) + { + return \format_time($value); + } + + /** + * 获取器:活动是否已结束 + * @param $value + * @param $data + * @return false|string + */ + public function getIsEndAttr($value, $data) + { + return $value ?: $data['end_time'] <= time(); + } + + /** + * 获取器:剩余砍价金额 + * @param $value + * @param $data + * @return false|string + */ + public function getSurplusMoneyAttr($value, $data) + { + $maxCutMoney = helper::bcsub($data['goods_price'], $data['floor_price']); + return $value ?: helper::bcsub($maxCutMoney, $data['cut_money']); + } + + /** + * 获取器:砍价进度百分比 + * @param $value + * @param $data + * @return false|string + */ + public function getBargainRateAttr($value, $data) + { + $maxCutMoney = helper::bcsub($data['goods_price'], $data['floor_price']); + $rate = helper::bcdiv($data['cut_money'], $maxCutMoney) * 100; + return $value ?: $rate; + } + + /** + * 获取器:砍价金额区间 + * @param $value + * @return mixed + */ + public function getSectionAttr($value) + { + return json_decode($value, true); + } + + /** + * 修改器:砍价金额区间 + * @param $value + * @return string + */ + public function setSectionAttr($value) + { + return json_encode($value); + } + + /** + * 砍价任务详情 + * @param $taskId + * @param array $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($taskId, $with = []) + { + $model = static::get($taskId, $with); + // 标识砍价任务过期 + if (!empty($model) && $model['status'] == 1 && $model->getData('end_time') <= time()) { + $model->save(['status' => 0]); + } + return $model; + } + +} \ No newline at end of file diff --git a/source/application/common/model/bargain/TaskHelp.php b/source/application/common/model/bargain/TaskHelp.php new file mode 100644 index 0000000..fc45d06 --- /dev/null +++ b/source/application/common/model/bargain/TaskHelp.php @@ -0,0 +1,28 @@ +BelongsTo("app\\{$module}\\model\\User") + ->field(['user_id', 'nickName', 'avatarUrl']); + } + +} \ No newline at end of file diff --git a/source/application/common/model/dealer/Apply.php b/source/application/common/model/dealer/Apply.php new file mode 100644 index 0000000..f8e90c0 --- /dev/null +++ b/source/application/common/model/dealer/Apply.php @@ -0,0 +1,95 @@ + '待审核', + 20 => '审核通过', + 30 => '驳回', + ]; + + /** + * 获取器:申请时间 + * @param $value + * @return false|string + */ + public function getApplyTimeAttr($value) + { + return date('Y-m-d H:i:s', $value); + } + + /** + * 获取器:审核时间 + * @param $value + * @return false|string + */ + public function getAuditTimeAttr($value) + { + return $value > 0 ? date('Y-m-d H:i:s', $value) : 0; + } + + /** + * 关联推荐人表 + * @return \think\model\relation\BelongsTo + */ + public function referee() + { + return $this->belongsTo('app\common\model\User', 'referee_id') + ->field(['user_id', 'nickName']); + } + + /** + * 销商申请记录详情 + * @param $where + * @return Apply|static + * @throws \think\exception\DbException + */ + public static function detail($where) + { + return self::get($where); + } + + /** + * 购买指定商品成为分销商 + * @param $userId + * @param $goodsIds + * @param $wxappId + * @return bool + * @throws \think\exception\DbException + */ + public function becomeDealerUser($userId, $goodsIds, $wxappId) + { + // 验证是否设置 + $config = Setting::getItem('condition', $wxappId); + if ($config['become__buy_goods'] != '1' || empty($config['become__buy_goods_ids'])) { + return false; + } + // 判断商品是否在设置范围内 + $intersect = array_intersect($goodsIds, $config['become__buy_goods_ids']); + if (empty($intersect)) { + return false; + } + // 新增分销商用户 + User::add($userId, [ + 'referee_id' => Referee::getRefereeUserId($userId, 1), + 'wxapp_id' => $wxappId, + ]); + return true; + } + +} \ No newline at end of file diff --git a/source/application/common/model/dealer/Capital.php b/source/application/common/model/dealer/Capital.php new file mode 100644 index 0000000..81c44c2 --- /dev/null +++ b/source/application/common/model/dealer/Capital.php @@ -0,0 +1,27 @@ +save(array_merge([ + 'wxapp_id' => $model::$wxapp_id + ], $data)); + } +} \ No newline at end of file diff --git a/source/application/common/model/dealer/Order.php b/source/application/common/model/dealer/Order.php new file mode 100644 index 0000000..96902ea --- /dev/null +++ b/source/application/common/model/dealer/Order.php @@ -0,0 +1,231 @@ +belongsTo('app\common\model\User'); + } + + /** + * 一级分销商用户 + * @return \think\model\relation\BelongsTo + */ + public function dealerFirst() + { + return $this->belongsTo('User', 'first_user_id'); + } + + /** + * 二级分销商用户 + * @return \think\model\relation\BelongsTo + */ + public function dealerSecond() + { + return $this->belongsTo('User', 'second_user_id'); + } + + /** + * 三级分销商用户 + * @return \think\model\relation\BelongsTo + */ + public function dealerThird() + { + return $this->belongsTo('User', 'third_user_id'); + } + + /** + * 订单类型 + * @param $value + * @return array + */ + public function getOrderTypeAttr($value) + { + $types = OrderTypeEnum::getTypeName(); + return ['text' => $types[$value], 'value' => $value]; + } + + /** + * 订单详情 + * @param $where + * @return Order|null + * @throws \think\exception\DbException + */ + public static function detail($where) + { + return static::get($where); + } + + /** + * 订单详情 + * @param $orderId + * @param $orderType + * @return Order|null + * @throws \think\exception\DbException + */ + public static function getDetailByOrderId($orderId, $orderType) + { + return static::detail(['order_id' => $orderId, 'order_type' => $orderType]); + } + + /** + * 发放分销订单佣金 + * @param array|\think\Model $order 订单详情 + * @param int $orderType 订单类型 + * @return bool|false|int + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public static function grantMoney($order, $orderType = OrderTypeEnum::MASTER) + { + // 订单是否已完成 + if ($order['order_status']['value'] != 30) { + return false; + } + // 佣金结算天数 + $settleDays = Setting::getItem('settlement', $order['wxapp_id'])['settle_days']; + // 判断该订单是否满足结算时间 (订单完成时间 + 佣金结算时间) ≤ 当前时间 + $deadlineTime = $order['receipt_time'] + ((int)$settleDays * 86400); + if ($settleDays > 0 && $deadlineTime > time()) { + return false; + } + // 分销订单详情 + $model = self::getDetailByOrderId($order['order_id'], $orderType); + if (!$model || $model['is_settled'] == 1) { + return false; + } + // 重新计算分销佣金 + $capital = $model->getCapitalByOrder($order); + // 发放一级分销商佣金 + $model['first_user_id'] > 0 && User::grantMoney($model['first_user_id'], $capital['first_money']); + // 发放二级分销商佣金 + $model['second_user_id'] > 0 && User::grantMoney($model['second_user_id'], $capital['second_money']); + // 发放三级分销商佣金 + $model['third_user_id'] > 0 && User::grantMoney($model['third_user_id'], $capital['third_money']); + // 更新分销订单记录 + return $model->save([ + 'order_price' => $capital['orderPrice'], + 'first_money' => $capital['first_money'], + 'second_money' => $capital['second_money'], + 'third_money' => $capital['third_money'], + 'is_settled' => 1, + 'settle_time' => time() + ]); + } + + /** + * 计算订单分销佣金 + * @param $order + * @return array + */ + protected function getCapitalByOrder($order) + { + // 分销佣金设置 + $setting = Setting::getItem('commission', $order['wxapp_id']); + // 分销层级 + $level = Setting::getItem('basic', $order['wxapp_id'])['level']; + // 分销订单佣金数据 + $capital = [ + // 订单总金额(不含运费) + 'orderPrice' => bcsub($order['pay_price'], $order['express_price'], 2), + // 一级分销佣金 + 'first_money' => 0.00, + // 二级分销佣金 + 'second_money' => 0.00, + // 三级分销佣金 + 'third_money' => 0.00 + ]; + // 计算分销佣金 + foreach ($order['goods'] as $goods) { + // 判断商品存在售后退款则不计算佣金 + if ($this->checkGoodsRefund($goods)) { + continue; + } + // 商品实付款金额 + $goodsPrice = min($capital['orderPrice'], $goods['total_pay_price']); + // 计算商品实际佣金 + $goodsCapital = $this->calculateGoodsCapital($setting, $goods, $goodsPrice); + // 累积分销佣金 + $level >= 1 && $capital['first_money'] += $goodsCapital['first_money']; + $level >= 2 && $capital['second_money'] += $goodsCapital['second_money']; + $level == 3 && $capital['third_money'] += $goodsCapital['third_money']; + } + return $capital; + } + + /** + * 计算商品实际佣金 + * @param $setting + * @param $goods + * @param $goodsPrice + * @return array + */ + private function calculateGoodsCapital($setting, $goods, $goodsPrice) + { + // 判断是否开启商品单独分销 + if ($goods['is_ind_dealer'] == false) { + // 全局分销比例 + return [ + 'first_money' => $goodsPrice * ($setting['first_money'] * 0.01), + 'second_money' => $goodsPrice * ($setting['second_money'] * 0.01), + 'third_money' => $goodsPrice * ($setting['third_money'] * 0.01) + ]; + } + // 商品单独分销 + if ($goods['dealer_money_type'] == 10) { + // 分销佣金类型:百分比 + return [ + 'first_money' => $goodsPrice * ($goods['first_money'] * 0.01), + 'second_money' => $goodsPrice * ($goods['second_money'] * 0.01), + 'third_money' => $goodsPrice * ($goods['third_money'] * 0.01) + ]; + } else { + return [ + 'first_money' => $goods['total_num'] * $goods['first_money'], + 'second_money' => $goods['total_num'] * $goods['second_money'], + 'third_money' => $goods['total_num'] * $goods['third_money'] + ]; + } + } + + /** + * 验证商品是否存在售后 + * @param $goods + * @return bool + */ + private function checkGoodsRefund($goods) + { + return !empty($goods['refund']) + && $goods['refund']['type']['value'] == 10 + && $goods['refund']['is_agree']['value'] != 20; + } + +} diff --git a/source/application/common/model/dealer/Referee.php b/source/application/common/model/dealer/Referee.php new file mode 100644 index 0000000..2d453b3 --- /dev/null +++ b/source/application/common/model/dealer/Referee.php @@ -0,0 +1,81 @@ +belongsTo('app\api\model\User'); + } + + /** + * 关联分销商用户表 + * @return \think\model\relation\BelongsTo + */ + public function dealer1() + { + return $this->belongsTo('User', 'dealer_id')->where('is_delete', '=', 0); + } + + /** + * 关联分销商用户表 + * @return \think\model\relation\BelongsTo + */ + public function dealer() + { + return $this->belongsTo('User')->where('is_delete', '=', 0); + } + + /** + * 获取上级用户id + * @param $user_id + * @param $level + * @param bool $is_dealer 必须是分销商 + * @return bool|mixed + * @throws \think\exception\DbException + */ + public static function getRefereeUserId($user_id, $level, $is_dealer = false) + { + $dealer_id = (new self)->where(compact('user_id', 'level')) + ->value('dealer_id'); + if (!$dealer_id) return 0; + return $is_dealer ? (User::isDealerUser($dealer_id) ? $dealer_id : 0) : $dealer_id; + } + + /** + * 获取我的团队列表 + * @param $user_id + * @param int $level + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList($user_id, $level = -1) + { + $level > -1 && $this->where('referee.level', '=', $level); + return $this->with(['dealer', 'user']) + ->alias('referee') + ->field('referee.*') + ->join('user', 'user.user_id = referee.user_id') + ->where('referee.dealer_id', '=', $user_id) + ->where('user.is_delete', '=', 0) + ->order(['referee.create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/common/model/dealer/Setting.php b/source/application/common/model/dealer/Setting.php new file mode 100644 index 0000000..40c0e40 --- /dev/null +++ b/source/application/common/model/dealer/Setting.php @@ -0,0 +1,389 @@ +toArray(), null, 'key'); + Cache::tag('cache')->set('dealer_setting_' . $wxapp_id, $data); + } + return array_merge_multiple($self->defaultData(), $data); + } + + /** + * 获取设置项信息 + * @param $key + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($key) + { + return static::get(compact('key')); + } + + /** + * 是否开启分销功能 + * @param null $wxapp_id + * @return mixed + */ + public static function isOpen($wxapp_id = null) + { + return static::getItem('basic', $wxapp_id)['is_open']; + } + + /** + * 分销中心页面名称 + * @param null $wxapp_id + * @return mixed + */ + public static function getDealerTitle($wxapp_id = null) + { + return static::getItem('words', $wxapp_id)['index']['title']['value']; + } + + /** + * 默认配置 + * @return array + */ + public function defaultData() + { + return [ + 'basic' => [ + 'key' => 'basic', + 'describe' => '基础设置', + 'values' => [ + // 是否开启分销功能 + 'is_open' => '0', // 参数值:1开启 0关闭 + // 分销层级 + 'level' => '3', // 参数值:1一级 2二级 3三级 + // 分销商内购 + 'self_buy' => '0' // 参数值:1开启 0关闭 + ], + ], + 'condition' => [ + 'key' => 'condition', + 'describe' => '分销商条件', + 'values' => [ + // 成为分销商条件 + 'become' => '10', // 参数值:10填写申请信息(需后台审核) 20填写申请信息(无需审核) + // 购买指定商品成为分销商 0关闭 1开启 + 'become__buy_goods' => '0', + // 购买指定商品的id集 + 'become__buy_goods_ids' => [], + // 成为下线条件 + 'downline' => '10', // 参数值:10首次点击分享链接 20首次下单 30首次付款 + ] + ], + 'commission' => [ + 'key' => 'commission', + 'describe' => '佣金设置', + 'values' => [ + // 一级佣金 + 'first_money' => '0', + // 一级佣金 + 'second_money' => '0', + // 一级佣金 + 'third_money' => '0', + ] + ], + 'settlement' => [ + 'key' => 'settlement', + 'describe' => '结算', + 'values' => [ + // 提现方式 + 'pay_type' => [], // 参数值:10微信支付 20支付宝支付 30银行卡支付 + // 微信支付自动打款 + 'wechat_pay_auto' => '0', // 微信支付自动打款:1开启 0关闭 + // 最低提现额度 + 'min_money' => '10.00', + // 佣金结算天数 + 'settle_days' => '10', + ] + ], + 'words' => [ + 'key' => 'words', + 'describe' => '自定义文字', + 'values' => [ + 'index' => [ + 'title' => [ + 'default' => '分销中心', + 'value' => '分销中心' + ], + 'words' => [ + 'not_dealer' => [ + 'default' => '您还不是分销商,请先提交申请', + 'value' => '您还不是分销商,请先提交申请' + ], + 'apply_now' => [ + 'default' => '立即加入', + 'value' => '立即加入' + ], + 'referee' => [ + 'default' => '推荐人', + 'value' => '推荐人' + ], + 'money' => [ + 'default' => '可提现佣金', + 'value' => '可提现' + ], + 'freeze_money' => [ + 'default' => '待提现佣金', + 'value' => '待提现' + ], + 'total_money' => [ + 'default' => '已提现金额', + 'value' => '已提现金额' + ], + 'withdraw' => [ + 'default' => '去提现', + 'value' => '去提现' + ], + ] + ], + 'apply' => [ + 'title' => [ + 'default' => '申请成为分销商', + 'value' => '申请成为分销商' + ], + 'words' => [ + 'title' => [ + 'default' => '请填写申请信息', + 'value' => '请填写申请信息' + ], + 'license' => [ + 'default' => '分销商申请协议', + 'value' => '分销商申请协议' + ], + 'submit' => [ + 'default' => '申请成为经销商', + 'value' => '申请成为经销商' + ], + 'wait_audit' => [ + 'default' => '您的申请已受理,正在进行信息核验,请耐心等待。', + 'value' => '您的申请已受理,正在进行信息核验,请耐心等待。' + ], + 'goto_mall' => [ + 'default' => '去商城逛逛', + 'value' => '去商城逛逛' + ], + ] + ], + 'order' => [ + 'title' => [ + 'default' => '分销订单', + 'value' => '分销订单' + ], + 'words' => [ + 'all' => [ + 'default' => '全部', + 'value' => '全部' + ], + 'unsettled' => [ + 'default' => '未结算', + 'value' => '未结算' + ], + 'settled' => [ + 'default' => '已结算', + 'value' => '已结算' + ], + ] + ], + 'team' => [ + 'title' => [ + 'default' => '我的团队', + 'value' => '我的团队' + ], + 'words' => [ + 'total_team' => [ + 'default' => '团队总人数', + 'value' => '团队总人数' + ], + 'first' => [ + 'default' => '一级团队', + 'value' => '一级团队' + ], + 'second' => [ + 'default' => '二级团队', + 'value' => '二级团队' + ], + 'third' => [ + 'default' => '三级团队', + 'value' => '三级团队' + ], + ] + ], + 'withdraw_list' => [ + 'title' => [ + 'default' => '提现明细', + 'value' => '提现明细' + ], + 'words' => [ + 'all' => [ + 'default' => '全部', + 'value' => '全部' + ], + 'apply_10' => [ + 'default' => '审核中', + 'value' => '审核中' + ], + 'apply_20' => [ + 'default' => '审核通过', + 'value' => '审核通过' + ], + 'apply_40' => [ + 'default' => '已打款', + 'value' => '已打款' + ], + 'apply_30' => [ + 'default' => '驳回', + 'value' => '驳回' + ], + ] + ], + 'withdraw_apply' => [ + 'title' => [ + 'default' => '申请提现', + 'value' => '申请提现' + ], + 'words' => [ + 'capital' => [ + 'default' => '可提现佣金', + 'value' => '可提现佣金' + ], + 'money' => [ + 'default' => '提现金额', + 'value' => '提现金额' + ], + 'money_placeholder' => [ + 'default' => '请输入要提取的金额', + 'value' => '请输入要提取的金额' + ], + 'min_money' => [ + 'default' => '最低提现佣金', + 'value' => '最低提现佣金' + ], + 'submit' => [ + 'default' => '提交申请', + 'value' => '提交申请' + ], + ] + ], + 'qrcode' => [ + 'title' => [ + 'default' => '推广二维码', + 'value' => '推广二维码' + ] + ], + ] + ], + 'license' => [ + 'key' => 'license', + 'describe' => '申请协议', + 'values' => [ + 'license' => '' + ] + ], + 'background' => [ + 'key' => 'background', + 'describe' => '页面背景图', + 'values' => [ + // 分销中心首页 + 'index' => self::$base_url . 'assets/api/dealer-bg.png', + // 申请成为分销商页 + 'apply' => self::$base_url . 'assets/api/dealer-bg.png', + // 申请提现页 + 'withdraw_apply' => self::$base_url . 'assets/api/dealer-bg.png', + ], + ], + 'template_msg' => [ + 'key' => 'template_msg', + 'describe' => '模板消息', + 'values' => [ + 'apply_tpl' => '', // 分销商审核通知 + 'withdraw_tpl' => '', // 提现状态通知 + ] + ], + 'qrcode' => [ + 'key' => 'template_msg', + 'describe' => '分销海报', + 'values' => [ + 'backdrop' => [ + 'src' => self::$base_url . 'assets/store/img/dealer/backdrop.png', + ], + 'nickName' => [ + 'fontSize' => 14, + 'color' => '#000000', + 'left' => 150, + 'top' => 99 + ], + 'avatar' => [ + 'width' => 70, + 'style' => 'circle', + 'left' => 150, + 'top' => 18 + ], + 'qrcode' => [ + 'width' => 100, + 'style' => 'circle', + 'left' => 136, + 'top' => 128 + ] + ], + ] + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/model/dealer/User.php b/source/application/common/model/dealer/User.php new file mode 100644 index 0000000..e8a84d2 --- /dev/null +++ b/source/application/common/model/dealer/User.php @@ -0,0 +1,114 @@ + 'integer', + 'second_num' => 'integer', + 'third_num' => 'integer', + ]; + + /** + * 关联会员记录表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + return $this->belongsTo('app\common\model\User'); + } + + /** + * 关联推荐人表 + * @return \think\model\relation\BelongsTo + */ + public function referee() + { + return $this->belongsTo('app\common\model\User', 'referee_id') + ->field(['user_id', 'nickName']); + } + + /** + * 获取分销商用户信息 + * @param $userId + * @param array $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($userId, $with = ['user', 'referee']) + { + return self::get($userId, $with); + } + + /** + * 是否为分销商 + * @param $userId + * @return bool + */ + public static function isDealerUser($userId) + { + return !!(new static)->where('user_id', '=', $userId) + ->where('is_delete', '=', 0) + ->value('user_id'); + } + + /** + * 新增分销商用户记录 + * @param $user_id + * @param $data + * @return false|int + * @throws \think\exception\DbException + */ + public static function add($user_id, $data) + { + $model = static::detail($user_id) ?: new static; + return $model->save(array_merge([ + 'user_id' => $user_id, + 'is_delete' => 0, + 'wxapp_id' => $model::$wxapp_id + ], $data)); + } + + /** + * 发放分销商佣金 + * @param $user_id + * @param $money + * @return bool + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public static function grantMoney($user_id, $money) + { + // 分销商详情 + $model = static::detail($user_id); + if (!$model || $model['is_delete']) { + return false; + } + // 累积分销商可提现佣金 + $model->setInc('money', $money); + // 记录分销商资金明细 + Capital::add([ + 'user_id' => $user_id, + 'flow_type' => 10, + 'money' => $money, + 'describe' => '订单佣金结算', + 'wxapp_id' => $model['wxapp_id'], + ]); + return true; + } + +} \ No newline at end of file diff --git a/source/application/common/model/dealer/Withdraw.php b/source/application/common/model/dealer/Withdraw.php new file mode 100644 index 0000000..9a80793 --- /dev/null +++ b/source/application/common/model/dealer/Withdraw.php @@ -0,0 +1,57 @@ + '微信', + 20 => '支付宝', + 30 => '银行卡', + ]; + + /** + * 申请状态 + * @var array + */ + public $applyStatus = [ + 10 => '待审核', + 20 => '审核通过', + 30 => '驳回', + 40 => '已打款', + ]; + + /** + * 关联分销商用户表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + return $this->belongsTo('User'); + } + + /** + * 提现详情 + * @param $id + * @return Apply|static + * @throws \think\exception\DbException + */ + public static function detail($id) + { + return self::get($id); + } + +} \ No newline at end of file diff --git a/source/application/common/model/recharge/Order.php b/source/application/common/model/recharge/Order.php new file mode 100644 index 0000000..f712342 --- /dev/null +++ b/source/application/common/model/recharge/Order.php @@ -0,0 +1,94 @@ + RechargeTypeEnum::data(), + // 支付状态 + 'pay_status' => PayStatusTypeEnum::data(), + ]; + } + + /** + * 关联会员记录表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + return $this->belongsTo('app\common\model\User'); + } + + /** + * 关联订单套餐快照表 + * @return \think\model\relation\HasOne + */ + public function orderPlan() + { + return $this->hasOne('OrderPlan', 'order_id'); + } + + /** + * 付款状态 + * @param $value + * @return array + */ + public function getRechargeTypeAttr($value) + { + return ['text' => RechargeTypeEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 付款状态 + * @param $value + * @return array + */ + public function getPayStatusAttr($value) + { + return ['text' => PayStatusTypeEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 付款时间 + * @param $value + * @return array + */ + public function getPayTimeAttr($value) + { + return [ + 'text' => $value > 0 ? date('Y-m-d H:i:s', $value) : '', + 'value' => $value + ]; + } + + /** + * 获取订单详情 + * @param $where + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($where) + { + return static::get($where); + } + +} \ No newline at end of file diff --git a/source/application/common/model/recharge/OrderPlan.php b/source/application/common/model/recharge/OrderPlan.php new file mode 100644 index 0000000..114f464 --- /dev/null +++ b/source/application/common/model/recharge/OrderPlan.php @@ -0,0 +1,16 @@ + '未拼单', + 10 => '拼单中', + 20 => '拼单成功', + 30 => '拼单失败', + ]; + return ['text' => $state[$value], 'value' => $value]; + } + + /** + * 获取器:结束时间 + * @param $value + * @return array + */ + public function getEndTimeAttr($value) + { + return ['text' => date('Y-m-d H:i:s', $value), 'value' => $value]; + } + + /** + * 获取器:剩余拼团人数 + * @param $value + * @return array + */ + public function getSurplusPeopleAttr($value, $data) + { + return $data['people'] - $data['actual_people']; + } + + /** + * 关联拼团商品表 + * @return \think\model\relation\BelongsTo + */ + public function goods() + { + return $this->belongsTo('Goods'); + } + + /** + * 关联用户表(团长) + * @return \think\model\relation\BelongsTo + */ + public function user() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\User", 'creator_id'); + } + + /** + * 关联拼单成员表 + * @return \think\model\relation\HasMany + */ + public function users() + { + return $this->hasMany('ActiveUsers', 'active_id') + ->order(['is_creator' => 'desc', 'create_time' => 'asc']); + } + + /** + * 拼单详情 + * @param $active_id + * @param array $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($active_id, $with = []) + { + return static::get($active_id, array_merge(['goods', 'users' => ['user', 'sharingOrder']], $with)); + } + + /** + * 验证当前拼单是否允许加入新成员 + * @return bool + */ + public function checkAllowJoin() + { + if (!in_array($this['status']['value'], [0, 10])) { + $this->error = '当前拼单已结束'; + return false; + } + if (time() > $this['end_time']) { + $this->error = '当前拼单已结束'; + return false; + } + if ($this['actual_people'] >= $this['people']) { + $this->error = '当前拼单人数已满'; + return false; + } + return true; + } + + /** + * 新增拼单记录 + * @param $creator_id + * @param $order_id + * @param OrderGoods $goods + * @return false|int + */ + public function onCreate($creator_id, $order_id, $goods) + { + // 新增拼单记录 + $this->save([ + 'goods_id' => $goods['goods_id'], + 'people' => $goods['people'], + 'actual_people' => 1, + 'creator_id' => $creator_id, + 'end_time' => time() + ($goods['group_time'] * 60 * 60), + 'status' => 10, + 'wxapp_id' => $goods['wxapp_id'] + ]); + // 新增拼单成员记录 + ActiveUsers::add([ + 'active_id' => $this['active_id'], + 'order_id' => $order_id, + 'user_id' => $creator_id, + 'is_creator' => 1, + 'wxapp_id' => $goods['wxapp_id'] + ]); + return true; + } + + /** + * 更新拼单记录 + * @param $user_id + * @param $order_id + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function onUpdate($user_id, $order_id) + { + // 验证当前拼单是否允许加入新成员 + if (!$this->checkAllowJoin()) { + return false; + } + // 新增拼单成员记录 + ActiveUsers::add([ + 'active_id' => $this['active_id'], + 'order_id' => $order_id, + 'user_id' => $user_id, + 'is_creator' => 0, + 'wxapp_id' => $this['wxapp_id'] + ]); + // 累计已拼人数 + $actual_people = $this['actual_people'] + 1; + // 更新拼单记录:当前已拼人数、拼单状态 + $status = $actual_people >= $this['people'] ? 20 : 10; + $this->save([ + 'actual_people' => $actual_people, + 'status' => $status + ]); + // 拼单成功, 发送模板消息 + if ($status == 20) { + $model = static::detail($this['active_id']); + (new MessageService)->sharingActive($model, '拼团成功'); + } + return true; + } + +} diff --git a/source/application/common/model/sharing/ActiveUsers.php b/source/application/common/model/sharing/ActiveUsers.php new file mode 100644 index 0000000..a463788 --- /dev/null +++ b/source/application/common/model/sharing/ActiveUsers.php @@ -0,0 +1,46 @@ +belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 关联拼团订单表 + * @return \think\model\relation\BelongsTo + */ + public function sharingOrder() + { + return $this->belongsTo('Order', 'order_id'); + } + + /** + * 新增拼团拼单成员记录 + * @param $data + * @return false|int + */ + public static function add($data) + { + return (new static)->save($data); + } + +} diff --git a/source/application/common/model/sharing/Category.php b/source/application/common/model/sharing/Category.php new file mode 100644 index 0000000..cd400f9 --- /dev/null +++ b/source/application/common/model/sharing/Category.php @@ -0,0 +1,98 @@ +order(['sort' => 'asc', 'create_time' => 'asc'])->select(); + $all = !empty($data) ? $data->toArray() : []; + $tree = []; + foreach ($all as $first) { + if ($first['parent_id'] != 0) continue; + $twoTree = []; + foreach ($all as $two) { + if ($two['parent_id'] != $first['category_id']) continue; + $threeTree = []; + foreach ($all as $three) + $three['parent_id'] == $two['category_id'] + && $threeTree[$three['category_id']] = $three; + !empty($threeTree) && $two['child'] = $threeTree; + $twoTree[$two['category_id']] = $two; + } + if (!empty($twoTree)) { + array_multisort(array_column($twoTree, 'sort'), SORT_ASC, $twoTree); + $first['child'] = $twoTree; + } + $tree[$first['category_id']] = $first; + } + Cache::tag('cache')->set('sharing_category_' . $model::$wxapp_id, compact('all', 'tree')); + } + return Cache::get('sharing_category_' . $model::$wxapp_id); + } + + /** + * 获取所有分类 + * @return mixed + */ + public static function getCacheAll() + { + return self::getALL()['all']; + } + + /** + * 获取所有分类(树状结构) + * @return mixed + */ + public static function getCacheTree() + { + return self::getALL()['tree']; + } + + /** + * 获取所有分类(树状结构) + * @return string + */ + public static function getCacheTreeJson() + { + return json_encode(static::getCacheTree()); + } + + /** + * 获取指定分类下的所有子分类id + * @param $parent_id + * @param array $all + * @return array + */ + public static function getSubCategoryId($parent_id, $all = []) + { + $arrIds = [$parent_id]; + empty($all) && $all = self::getCacheAll(); + foreach ($all as $key => $item) { + if ($item['parent_id'] == $parent_id) { + unset($all[$key]); + $subIds = self::getSubCategoryId($item['category_id'], $all); + !empty($subIds) && $arrIds = array_merge($arrIds, $subIds); + } + } + return $arrIds; + } + +} diff --git a/source/application/common/model/sharing/Comment.php b/source/application/common/model/sharing/Comment.php new file mode 100644 index 0000000..285a973 --- /dev/null +++ b/source/application/common/model/sharing/Comment.php @@ -0,0 +1,64 @@ +belongsTo('Order'); + } + + /** + * 订单商品 + * @return \think\model\relation\BelongsTo + */ + public function OrderGoods() + { + return $this->belongsTo('OrderGoods'); + } + + /** + * 关联用户表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 关联评价图片表 + * @return \think\model\relation\HasMany + */ + public function image() + { + return $this->hasMany('CommentImage', 'comment_id')->order(['id' => 'asc']); + } + + /** + * 评价详情 + * @param $comment_id + * @return Comment|null + * @throws \think\exception\DbException + */ + public static function detail($comment_id) + { + return self::get($comment_id, ['user', 'orderM', 'OrderGoods', 'image.file']); + } + +} \ No newline at end of file diff --git a/source/application/common/model/sharing/CommentImage.php b/source/application/common/model/sharing/CommentImage.php new file mode 100644 index 0000000..a728d83 --- /dev/null +++ b/source/application/common/model/sharing/CommentImage.php @@ -0,0 +1,28 @@ +belongsTo("app\\{$module}\\model\\UploadFile", 'image_id', 'file_id') + ->bind(['file_path', 'file_name', 'file_url']); + } + +} diff --git a/source/application/common/model/sharing/Goods.php b/source/application/common/model/sharing/Goods.php new file mode 100644 index 0000000..87b7cd9 --- /dev/null +++ b/source/application/common/model/sharing/Goods.php @@ -0,0 +1,391 @@ +belongsTo('Category'); + } + + /** + * 关联商品规格表 + * @return \think\model\relation\HasMany + */ + public function sku() + { + return $this->hasMany('GoodsSku', 'goods_id')->order(['goods_sku_id' => 'asc']); + } + + /** + * 关联商品规格关系表 + * @return \think\model\relation\BelongsToMany + */ + public function specRel() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsToMany( + "app\\{$module}\\model\\SpecValue", + 'SharingGoodsSpecRel', + 'spec_value_id', + 'goods_id' + )->order(['id' => 'asc']); + } + + /** + * 关联商品图片表 + * @return \think\model\relation\HasMany + */ + public function image() + { + $module = self::getCalledModule() ?: 'common'; + return $this->hasMany("app\\{$module}\\model\\sharing\\GoodsImage", 'goods_id') + ->order(['id' => 'asc']); + } + + /** + * 关联运费模板表 + * @return \think\model\relation\BelongsTo + */ + public function delivery() + { + $module = self::getCalledModule() ?: 'common'; + return $this->BelongsTo("app\\{$module}\\model\\Delivery"); + } + + /** + * 关联订单评价表 + * @return \think\model\relation\HasMany + */ + public function commentData() + { + return $this->hasMany('Comment', 'goods_id'); + } + + /** + * 计费方式 + * @param $value + * @return mixed + */ + public function getGoodsStatusAttr($value) + { + $status = [10 => '上架', 20 => '下架']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 获取商品列表 + * @param $param + * @return mixed + * @throws \think\exception\DbException + */ + public function getList($param) + { + // 商品列表获取条件 + $params = array_merge([ + 'status' => 10, // 商品状态 + 'category_id' => 0, // 分类id + 'search' => '', // 搜索关键词 + 'sortType' => 'all', // 排序类型 + 'sortPrice' => false, // 价格排序 高低 + 'listRows' => 15, // 每页数量 + ], $param); + // 筛选条件 + $filter = []; + $params['category_id'] > 0 && $filter['category_id'] = ['IN', Category::getSubCategoryId($params['category_id'])]; + $params['status'] > 0 && $filter['goods_status'] = $params['status']; + !empty($params['search']) && $filter['goods_name'] = ['like', '%' . trim($params['search']) . '%']; + // 排序规则 + $sort = []; + if ($params['sortType'] === 'all') { + $sort = ['goods_sort', 'goods_id' => 'desc']; + } elseif ($params['sortType'] === 'sales') { + $sort = ['goods_sales' => 'desc']; + } elseif ($params['sortType'] === 'price') { + $sort = $params['sortPrice'] ? ['goods_max_price' => 'desc'] : ['goods_min_price']; + } + // 商品表名称 + $tableName = $this->getTable(); + // 多规格商品 最高价与最低价 + $GoodsSku = new GoodsSku; + $minPriceSql = $GoodsSku->field(['MIN(sharing_price)']) + ->where('goods_id', 'EXP', "= `$tableName`.`goods_id`")->buildSql(); + $maxPriceSql = $GoodsSku->field(['MAX(sharing_price)']) + ->where('goods_id', 'EXP', "= `$tableName`.`goods_id`")->buildSql(); + // 执行查询 + $list = $this + ->field(['*', '(sales_initial + sales_actual) as goods_sales', + "$minPriceSql AS goods_min_price", + "$maxPriceSql AS goods_max_price" + ]) + ->with(['category', 'image.file', 'sku']) + ->where('is_delete', '=', 0) + ->where($filter) + ->order($sort) + ->paginate($params['listRows'], false, [ + 'query' => \request()->request() + ]); + // 整理列表数据并返回 + return $this->setGoodsListData($list, true); + } + + /** + * 设置商品展示的数据 + * @param $data + * @param bool $isMultiple + * @param callable $callback + * @return mixed + */ + protected function setGoodsListData(&$data, $isMultiple = true, callable $callback = null) + { + if (!$isMultiple) $dataSource = [&$data]; else $dataSource = &$data; + // 整理商品列表数据 + foreach ($dataSource as &$goods) { + // 商品默认规格 + $goodsSku = $goods['sku'][0]; + // 商品默认数据 + $goods['goods_image'] = $goods['image'][0]['file_path']; + $goods['goods_sku'] = $goodsSku; + // 回调函数 + is_callable($callback) && call_user_func($callback, $goods); + } + return $data; + } + + /** + * 根据商品id集获取商品列表 + * @param array $goodsIds + * @param null $status + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($goodsIds, $status = null) + { + // 筛选条件 + $filter = ['goods_id' => ['in', $goodsIds]]; + $status > 0 && $filter['goods_status'] = $status; + if (!empty($goodsIds)) { + $this->orderRaw('field(goods_id, ' . implode(',', $goodsIds) . ')'); + } + // 获取商品列表数据 + $data = $this->with(['category', 'image.file', 'sku', 'spec_rel.spec', 'delivery.rule']) + ->where($filter) + ->select(); + if ($data->isEmpty()) return $data; + // 格式化数据 + foreach ($data as &$item) { + $item['goods_image'] = $item['image'][0]['file_path']; + } + return $data; + } + + /** + * 商品多规格信息 + * @param \think\Collection $spec_rel + * @param \think\Collection $skuData + * @return array + */ + public function getManySpecData($spec_rel, $skuData) + { + // spec_attr + $specAttrData = []; + foreach ($spec_rel->toArray() as $item) { + if (!isset($specAttrData[$item['spec_id']])) { + $specAttrData[$item['spec_id']] = [ + 'group_id' => $item['spec']['spec_id'], + 'group_name' => $item['spec']['spec_name'], + 'spec_items' => [], + ]; + } + $specAttrData[$item['spec_id']]['spec_items'][] = [ + 'item_id' => $item['spec_value_id'], + 'spec_value' => $item['spec_value'], + ]; + } + // spec_list + $specListData = []; + foreach ($skuData->toArray() as $item) { + $image = (isset($item['image']) && !empty($item['image'])) ? $item['image'] : ['file_id' => 0, 'file_path' => '']; + $specListData[] = [ + 'goods_sku_id' => $item['goods_sku_id'], + 'spec_sku_id' => $item['spec_sku_id'], + 'rows' => [], + 'form' => [ + 'image_id' => $image['file_id'], + 'image_path' => $image['file_path'], + 'goods_no' => $item['goods_no'], + 'goods_price' => $item['goods_price'], + 'sharing_price' => $item['sharing_price'], + 'goods_weight' => $item['goods_weight'], + 'line_price' => $item['line_price'], + 'stock_num' => $item['stock_num'], + ], + ]; + } + return ['spec_attr' => array_values($specAttrData), 'spec_list' => $specListData]; + } + + /** + * 多规格表格数据 + * @param $goods + * @return array + */ + public function getManySpecTable(&$goods) + { + $specData = $this->getManySpecData($goods['spec_rel'], $goods['sku']); + $totalRow = count($specData['spec_list']); + foreach ($specData['spec_list'] as $i => &$sku) { + $rowData = []; + $rowCount = 1; + foreach ($specData['spec_attr'] as $attr) { + $skuValues = $attr['spec_items']; + $rowCount *= count($skuValues); + $anInterBankNum = ($totalRow / $rowCount); + $point = (($i / $anInterBankNum) % count($skuValues)); + if (0 === ($i % $anInterBankNum)) { + $rowData[] = [ + 'rowspan' => $anInterBankNum, + 'item_id' => $skuValues[$point]['item_id'], + 'spec_value' => $skuValues[$point]['spec_value'] + ]; + } + } + $sku['rows'] = $rowData; + } + return $specData; + } + + /** + * 获取商品详情 + * @param $goodsId + * @return static + */ + public static function detail($goodsId) + { + /* @var $model self */ + $model = (new static)->with([ + 'category', + 'image.file', + 'sku.image', + 'spec_rel.spec', + ])->where('goods_id', '=', $goodsId) + ->find(); + // 整理列表数据并返回 + return $model->setGoodsListData($model, false); + } + + /** + * 指定的商品规格信息 + * @param static $goods 商品详情 + * @param int $specSkuId + * @return array|bool + */ + public static function getGoodsSku($goods, $specSkuId) + { + // 获取指定的sku + $goodsSku = []; + foreach ($goods['sku'] as $item) { + if ($item['spec_sku_id'] == $specSkuId) { + $goodsSku = $item; + break; + } + } + if (empty($goodsSku)) { + return false; + } + // 多规格文字内容 + $goodsSku['goods_attr'] = ''; + if ($goods['spec_type'] == 20) { + $specRelData = helper::arrayColumn2Key($goods['spec_rel'], 'spec_value_id'); + $attrs = explode('_', $goodsSku['spec_sku_id']); + foreach ($attrs as $specValueId) { + $goodsSku['goods_attr'] .= $specRelData[$specValueId]['spec']['spec_name'] . ':' + . $specRelData[$specValueId]['spec_value'] . '; '; + } + } + return $goodsSku; + } + + /** + * 更新商品库存销量 + * @param $goodsList + * @throws \Exception + */ + public function updateStockSales($goodsList) + { + // 整理批量更新商品销量 + $goodsSave = []; + // 批量更新商品规格:sku销量、库存 + $goodsSpecSave = []; + foreach ($goodsList as $goods) { + $goodsSave[] = [ + 'goods_id' => $goods['goods_id'], + 'sales_actual' => ['inc', $goods['total_num']] + ]; + // 付款减库存 + if ($goods['deduct_stock_type'] == 20) { + $goodsSpecSave[] = [ + 'data' => ['stock_num' => ['dec', $goods['total_num']]], + 'where' => [ + 'goods_id' => $goods['goods_id'], + 'spec_sku_id' => $goods['spec_sku_id'], + ], + ]; + } + } + // 更新商品总销量 + $this->allowField(true)->isUpdate()->saveAll($goodsSave); + // 更新商品规格库存 + !empty($goodsSpecSave) && (new GoodsSku)->updateAll($goodsSpecSave); + } + +} diff --git a/source/application/common/model/sharing/GoodsImage.php b/source/application/common/model/sharing/GoodsImage.php new file mode 100644 index 0000000..0a66546 --- /dev/null +++ b/source/application/common/model/sharing/GoodsImage.php @@ -0,0 +1,28 @@ +belongsTo("app\\{$module}\\model\\UploadFile", 'image_id', 'file_id') + ->bind(['file_path', 'file_name', 'file_url']); + } + +} diff --git a/source/application/common/model/sharing/GoodsSku.php b/source/application/common/model/sharing/GoodsSku.php new file mode 100644 index 0000000..7166b03 --- /dev/null +++ b/source/application/common/model/sharing/GoodsSku.php @@ -0,0 +1,51 @@ +hasOne("app\\{$module}\\model\\UploadFile", 'file_id', 'image_id'); + } + + /** + * 获取器:拼团价与划线价差额 + * @param $value + * @param $data + * @return mixed + */ + public function getDiffPriceAttr($value, $data) + { + return max(0, helper::bcsub($data['line_price'], $data['sharing_price'])); + } + + /** + * 获取sku信息详情 + * @param $goodsId + * @param $specSkuId + * @return GoodsSku|null + * @throws \think\exception\DbException + */ + public static function detail($goodsId, $specSkuId) + { + return static::get(['goods_id' => $goodsId, 'spec_sku_id' => $specSkuId]); + } + +} diff --git a/source/application/common/model/sharing/GoodsSpecRel.php b/source/application/common/model/sharing/GoodsSpecRel.php new file mode 100644 index 0000000..fca6bb4 --- /dev/null +++ b/source/application/common/model/sharing/GoodsSpecRel.php @@ -0,0 +1,26 @@ +belongsTo('Spec'); + } + +} diff --git a/source/application/common/model/sharing/Order.php b/source/application/common/model/sharing/Order.php new file mode 100644 index 0000000..092967c --- /dev/null +++ b/source/application/common/model/sharing/Order.php @@ -0,0 +1,433 @@ +belongsTo('Active'); + } + + /** + * 订单商品列表 + * @return \think\model\relation\HasMany + */ + public function goods() + { + return $this->hasMany('OrderGoods', 'order_id'); + } + + /** + * 关联订单收货地址表 + * @return \think\model\relation\HasOne + */ + public function address() + { + return $this->hasOne('OrderAddress', 'order_id'); + } + + /** + * 关联订单收货地址表 + * @return \think\model\relation\HasOne + */ + public function extract() + { + return $this->hasOne('OrderExtract', 'order_id'); + } + + /** + * 关联自提门店表 + * @return \think\model\relation\BelongsTo + */ + public function extractShop() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\store\\Shop", 'extract_shop_id'); + } + + /** + * 关联门店店员表 + * @return \think\model\relation\BelongsTo + */ + public function extractClerk() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\store\\shop\\Clerk", 'extract_clerk_id'); + } + + /** + * 关联用户表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 关联物流公司表 + * @return \think\model\relation\BelongsTo + */ + public function express() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\Express"); + } + + /** + * 拼团订单状态文字描述 + * @param $value + * @param $data + * @return string + */ + public function getStateTextAttr($value, $data) + { + if (!isset($data['active_status'])) { + $data['active_status'] = ''; + } + // 订单状态:已完成 + if ($data['order_status'] == 30) { + return '已完成'; + } + // 订单状态:已取消 + if ($data['order_status'] == 20) { + // 拼单未成功 + if ($data['order_type'] == 20 && $data['active_status'] == 30) { + return $data['is_refund'] ? '拼团未成功,已退款' : '拼团未成功,待退款'; + } + return '已取消'; + } + // 付款状态 + if ($data['pay_status'] == 10) { + return '待付款'; + } + // 订单类型:单独购买 + if ($data['order_type'] == 10) { + if ($data['delivery_status'] == 10) { + return '已付款,待发货'; + } + if ($data['receipt_status'] == 10) { + return '已发货,待收货'; + } + } + // 订单类型:拼团 + if ($data['order_type'] == 20) { + // 拼单未成功 + if ($data['active_status'] == 30) { + return $data['is_refund'] ? '拼团未成功,已退款' : '拼团未成功,待退款'; + } + // 拼单中 + if ($data['active_status'] == 10) { + return '已付款,待成团'; + } + // 拼单成功 + if ($data['active_status'] == 20) { + if ($data['delivery_status'] == 10) { + return '拼团成功,待发货'; + } + if ($data['receipt_status'] == 10) { + return '已发货,待收货'; + } + } + } + return $value; + } + + /** + * 获取器:拼单状态 + * @param $value + * @return array|bool + */ + public function getActiveStatusAttr($value) + { + if (is_null($value)) { + return false; + } + $state = [ + 0 => '未拼单', + 10 => '拼单中', + 20 => '拼单成功', + 30 => '拼单失败', + ]; + return ['text' => $state[$value], 'value' => $value]; + } + + /** + * 获取器:订单金额(含优惠折扣) + * @param $value + * @param $data + * @return string + */ + public function getOrderPriceAttr($value, $data) + { + // 兼容旧数据:订单金额 + if ($value == 0) { + return helper::bcadd(helper::bcsub($data['total_price'], $data['coupon_money']), $data['update_price']); + } + return $value; + } + + /** + * 改价金额(差价) + * @param $value + * @return array + */ + public function getUpdatePriceAttr($value) + { + return [ + 'symbol' => $value < 0 ? '-' : '+', + 'value' => sprintf('%.2f', abs($value)) + ]; + } + + /** + * 订单类型 + * @param $value + * @return array + */ + public function getOrderTypeAttr($value) + { + $status = [10 => '单独购买', 20 => '拼团']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 付款状态 + * @param $value + * @return array + */ + public function getPayTypeAttr($value) + { + return ['text' => PayTypeEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 付款状态 + * @param $value + * @return array + */ + public function getPayStatusAttr($value) + { + return ['text' => PayStatusEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 发货状态 + * @param $value + * @return array + */ + public function getDeliveryStatusAttr($value) + { + $status = [10 => '待发货', 20 => '已发货']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 收货状态 + * @param $value + * @return array + */ + public function getReceiptStatusAttr($value) + { + $status = [10 => '待收货', 20 => '已收货']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 收货状态 + * @param $value + * @return array + */ + public function getOrderStatusAttr($value) + { + $status = [10 => '进行中', 20 => '已取消', 21 => '待取消', 30 => '已完成', 40 => '拼团失败']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 配送方式 + * @param $value + * @return array + */ + public function getDeliveryTypeAttr($value) + { + return ['text' => DeliveryTypeEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 生成订单号 + * @return string + */ + public function orderNo() + { + return OrderService::createOrderNo(); + } + + /** + * 订单详情 + * @param int|array $where + * @param array $with + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($where, $with = [ + 'active', + 'goods' => ['image'], + 'address', + 'extract', + 'express', + 'extract_shop.logo', + 'extract_clerk' + ]) + { + is_array($where) ? $filter = $where : $filter['order_id'] = (int)$where; + return self::get($filter, $with); + } + + /** + * 批量获取订单列表 + * @param $orderIds + * @param array $with 关联查询 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($orderIds, $with = []) + { + $data = $this->getListByInArray('order_id', $orderIds, $with); + return helper::arrayColumn2Key($data, 'order_id'); + } + + /** + * 批量获取订单列表 + * @param string $field + * @param array $data + * @param array $with + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getListByInArray($field, $data, $with = []) + { + return $this->with($with) + ->alias('order') + ->field('order.*, active.status as active_status') + ->join('sharing_active active', 'order.active_id = active.active_id', 'LEFT') + ->where($field, 'in', $data) + ->where('order.is_delete', '=', 0) + ->select(); + } + + /** + * 根据订单号批量查询 + * @param $orderNos + * @param array $with + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByOrderNos($orderNos, $with = []) + { + return $this->getListByInArray('order_no', $orderNos, $with); + } + + /** + * 批量更新订单 + * @param $orderIds + * @param $data + * @return false|int + */ + public function onBatchUpdate($orderIds, $data) + { + return $this->isUpdate(true)->save($data, ['order_id' => ['in', $orderIds]]); + } + + /** + * 确认核销(自提订单) + * @param int $clerkId 核销员id + * @return bool|false|int + */ + public function verificationOrder($clerkId) + { + if ( + $this['pay_status']['value'] != 20 + || $this['delivery_type']['value'] != DeliveryTypeEnum::EXTRACT + || $this['delivery_status']['value'] == 20 + || in_array($this['order_status']['value'], [20, 21]) + // 拼团订单验证拼单状态 + || ($this['order_type']['value'] == 20 ? $this['active']['status']['value'] != 20 : false) + ) { + $this->error = '该订单不满足核销条件'; + return false; + } + $this->transaction(function () use ($clerkId) { + // 更新订单状态:已发货、已收货 + $this->save([ + 'extract_clerk_id' => $clerkId, // 核销员 + 'delivery_status' => 20, + 'delivery_time' => time(), + 'receipt_status' => 20, + 'receipt_time' => time(), + 'order_status' => 30 + ]); + // 新增订单核销记录 + ShopOrder::add( + $this['order_id'], + $this['extract_shop_id'], + $this['extract_clerk_id'], + OrderTypeEnum::SHARING + ); + // 执行订单完成后的操作 + $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::SHARING); + $OrderCompleteService->complete([$this], static::$wxapp_id); + }); + return true; + } + +} diff --git a/source/application/common/model/sharing/OrderAddress.php b/source/application/common/model/sharing/OrderAddress.php new file mode 100644 index 0000000..7dda862 --- /dev/null +++ b/source/application/common/model/sharing/OrderAddress.php @@ -0,0 +1,48 @@ + RegionModel::getNameById($data['province_id']), + 'city' => RegionModel::getNameById($data['city_id']), + 'region' => $data['region_id'] == 0 ? '' : RegionModel::getNameById($data['region_id']), + ]; + } + + /** + * 获取完整地址 + * @return string + */ + public function getFullAddress() + { + return $this['region']['province'] . $this['region']['city'] . $this['region']['region'] . $this['detail']; + } + +} diff --git a/source/application/common/model/sharing/OrderExtract.php b/source/application/common/model/sharing/OrderExtract.php new file mode 100644 index 0000000..d591f7f --- /dev/null +++ b/source/application/common/model/sharing/OrderExtract.php @@ -0,0 +1,17 @@ +belongsTo('Goods'); + } + + /** + * 订单拼团商品图 + * @return \think\model\relation\BelongsTo + */ + public function image() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\UploadFile", 'image_id', 'file_id'); + } + + /** + * 关联拼团商品sku表 + * @return \think\model\relation\BelongsTo + */ + public function sku() + { + return $this->belongsTo('GoodsSku', 'spec_sku_id', 'spec_sku_id'); + } + + /** + * 关联拼团订单主表 + * @return \think\model\relation\BelongsTo + */ + public function orderM() + { + return $this->belongsTo('Order'); + } + + /** + * 关联拼团售后单记录表 + * @return \think\model\relation\HasOne + */ + public function refund() + { + return $this->hasOne('OrderRefund', 'order_goods_id'); + } + + /** + * 拼团订单商品详情 + * @param $where + * @return OrderGoods|null + * @throws \think\exception\DbException + */ + public static function detail($where) + { + return static::get($where, ['image', 'refund']); + } + + /** + * 回退商品库存 + * @param $goodsList + * @param $isPayOrder + * @return array|false + * @throws \Exception + */ + public function backGoodsStock($goodsList, $isPayOrder = false) + { + $goodsSkuData = []; + foreach ($goodsList as $goods) { + $item = [ + 'where' => [ + 'goods_id' => $goods['goods_id'], + 'spec_sku_id' => $goods['spec_sku_id'], + ], + 'data' => ['stock_num' => ['inc', $goods['total_num']]], + ]; + if ($isPayOrder == true) { + // 付款订单全部库存 + $goodsSkuData[] = $item; + } else { + // 未付款订单,判断必须为下单减库存时才回退 + $goods['deduct_stock_type'] == DeductStockTypeEnum::CREATE && $goodsSkuData[] = $item; + } + } + // 更新商品sku库存 + return !empty($goodsSkuData) && (new GoodsSkuModel)->updateAll($goodsSkuData); + } + +} diff --git a/source/application/common/model/sharing/OrderRefund.php b/source/application/common/model/sharing/OrderRefund.php new file mode 100644 index 0000000..97eb77a --- /dev/null +++ b/source/application/common/model/sharing/OrderRefund.php @@ -0,0 +1,117 @@ +belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 关联订单主表 + * @return \think\model\relation\BelongsTo + */ + public function orderMaster() + { + return $this->belongsTo('Order'); + } + + /** + * 关联订单商品表 + * @return \think\model\relation\BelongsTo + */ + public function orderGoods() + { + return $this->belongsTo('OrderGoods'); + } + + /** + * 关联图片记录表 + * @return \think\model\relation\HasMany + */ + public function image() + { + return $this->hasMany('OrderRefundImage', 'order_refund_id'); + } + + /** + * 关联物流公司表 + * @return \think\model\relation\BelongsTo + */ + public function express() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\Express"); + } + + /** + * 关联用户表 + * @return \think\model\relation\HasOne + */ + public function address() + { + return $this->hasOne('OrderRefundAddress', 'order_refund_id'); + } + + /** + * 售后类型 + * @param $value + * @return array + */ + public function getTypeAttr($value) + { + $status = [10 => '退货退款', 20 => '换货']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 商家是否同意售后 + * @param $value + * @return array + */ + public function getIsAgreeAttr($value) + { + $status = [0 => '待审核', 10 => '已同意', 20 => '已拒绝']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 售后单状态 + * @param $value + * @return array + */ + public function getStatusAttr($value) + { + $status = [0 => '进行中', 10 => '已拒绝', 20 => '已完成', 30 => '已取消']; + return ['text' => $status[$value], 'value' => $value]; + } + + /** + * 售后单详情 + * @param $where + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($where) + { + return static::get($where, ['image.file', 'order_goods.image', 'express', 'address']); + } + +} \ No newline at end of file diff --git a/source/application/common/model/sharing/OrderRefundAddress.php b/source/application/common/model/sharing/OrderRefundAddress.php new file mode 100644 index 0000000..aa4f191 --- /dev/null +++ b/source/application/common/model/sharing/OrderRefundAddress.php @@ -0,0 +1,17 @@ +belongsTo("app\\{$module}\\model\\UploadFile", 'image_id', 'file_id') + ->bind(['file_path', 'file_name', 'file_url']); + } + +} diff --git a/source/application/common/model/sharing/Setting.php b/source/application/common/model/sharing/Setting.php new file mode 100644 index 0000000..be1ddc4 --- /dev/null +++ b/source/application/common/model/sharing/Setting.php @@ -0,0 +1,105 @@ +toArray(), null, 'key'); + Cache::tag('cache')->set('sharing_setting_' . $wxapp_id, $data); + } + return array_merge_multiple($self->defaultData(), $data); + } + + /** + * 获取设置项信息 + * @param $key + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($key) + { + return static::get(compact('key')); + } + + /** + * 默认配置 + * @return array + */ + public function defaultData() + { + return [ + 'basic' => [ + 'key' => 'basic', + 'describe' => '基础设置', + 'values' => [ + // 拼团失败自动退款 + 'auto_refund' => '1', + // 是否允许使用优惠券 + 'is_coupon' => '1', + // 是否开启分销 + 'is_dealer' => '0', + // 拼团规则 简述 + 'rule_brief' => '好友拼单 · 人满发货 · 人不满退款', + // 拼团规则 详述 + 'rule_detail' => "开团:选择商品,点击“发起拼单”按钮,付款完成后即开团成功,就可以邀请小伙伴一起拼团啦;\n\n参团:进入朋友分享的页面,点击“立即参团”按钮,付款完成后参团成功,在有效时间内凑齐人数即成团,就可以等待收货喽;\n\n成团:在开团或参团成功后,点击“立即分享”将页面分享给好友,在有效时间内凑齐人数即成团,成团后商家开始发货;\n\n组团失败:在有效时间内未凑齐人数,即组团失败,组团失败后订单所付款将原路退回到支付账户。", + // 拼单状态模板消息id + 'tpl_msg_id' => '', + ] + ] + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/model/sharp/Active.php b/source/application/common/model/sharp/Active.php new file mode 100644 index 0000000..18be773 --- /dev/null +++ b/source/application/common/model/sharp/Active.php @@ -0,0 +1,38 @@ +hasMany('ActiveTime', 'active_id') + ->order(['active_time' => 'asc']); + } + + /** + * 活动会场详情 + * @param $activeId + * @param array $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($activeId, $with = []) + { + return static::get($activeId, $with); + } + +} \ No newline at end of file diff --git a/source/application/common/model/sharp/ActiveGoods.php b/source/application/common/model/sharp/ActiveGoods.php new file mode 100644 index 0000000..b699af1 --- /dev/null +++ b/source/application/common/model/sharp/ActiveGoods.php @@ -0,0 +1,111 @@ +belongsTo('Active', 'active_id'); + } + + /** + * 关联活动会场场次表 + * @return \think\model\relation\BelongsTo + */ + public function activeTime() + { + return $this->belongsTo('ActiveTime', 'active_time_id'); + } + + /** + * 根据活动场次ID获取商品列表 + * @param int $activeTimeId + * @param array $goodsParam + * @return false|\PDOStatement|string|\think\Collection + */ + public static function getGoodsListByActiveTimeId($activeTimeId, $goodsParam = []) + { + // 商品关联列表 + $model = new static; + $activeGoodsList = $model->getSharpGoodsByActiveTimeId($activeTimeId); + // 将列表的索引值设置为商品ID + $activeGoodsList = helper::arrayColumn2Key($activeGoodsList, 'sharp_goods_id'); + // 获取商品列表 + $sharpGoodsList = $model->getGoodsListByIds(array_keys($activeGoodsList), $goodsParam); + // 整理活动商品信息 + foreach ($sharpGoodsList as &$item) { + // 活动商品的销量 + $item['sales_actual'] = $activeGoodsList[$item['sharp_goods_id']]['sales_actual']; + // 商品销售进度 + $item['progress'] = $model->getProgress($item['sales_actual'], $item['seckill_stock']); + } + /* @var $sharpGoodsList \think\model\Collection */ + return $sharpGoodsList->hidden(['sku', 'goods']); + } + + /** + * 计算商品销售进度 + * @param $value1 + * @param $value2 + * @return mixed + */ + protected function getProgress($value1, $value2) + { + if ($value2 <= 0) return 100; + $progress = helper::bcdiv($value1, $value2); + return min(100, (int)helper::bcmul($progress, 100, 0)); + } + + /** + * 获取秒杀商品模型 + * @return Goods + */ + protected function getGoodsModel() + { + return new GoodsModel; + } + + /** + * 根据活动场次ID获取商品集 + * @param $activeTimeId + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getSharpGoodsByActiveTimeId($activeTimeId) + { + return $this->where('active_time_id', '=', $activeTimeId)->select(); + } + + /** + * 根据商品ID集获取商品列表 + * @param array $sharpGoodsIds + * @param array $param + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getGoodsListByIds($sharpGoodsIds, $param = []) + { + return $this->getGoodsModel()->getListByIds($sharpGoodsIds, $param); + } + +} \ No newline at end of file diff --git a/source/application/common/model/sharp/ActiveTime.php b/source/application/common/model/sharp/ActiveTime.php new file mode 100644 index 0000000..f954359 --- /dev/null +++ b/source/application/common/model/sharp/ActiveTime.php @@ -0,0 +1,56 @@ +belongsTo('Active', 'active_id'); + } + + /** + * 当前场次下秒杀商品的数量 + * @return \think\model\relation\HasMany + */ + public function goods() + { + return $this->hasMany('ActiveGoods', 'active_time_id'); + } + + /** + * 获取器:活动场次时间 + * @param $value + * @return string + */ + public function getActiveTimeAttr($value) + { + return \pad_left($value) . ':00'; + } + + /** + * 活动会场场次详情 + * @param $activeTimeId + * @param array $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($activeTimeId, $with = []) + { + return static::get($activeTimeId, $with); + } + +} \ No newline at end of file diff --git a/source/application/common/model/sharp/Goods.php b/source/application/common/model/sharp/Goods.php new file mode 100644 index 0000000..49f710b --- /dev/null +++ b/source/application/common/model/sharp/Goods.php @@ -0,0 +1,153 @@ +belongsTo("app\\{$module}\\model\\Goods"); + } + + /** + * 关联商品规格表 + * @return \think\model\relation\HasMany + */ + public function sku() + { + return $this->hasMany('GoodsSku', 'sharp_goods_id') + ->order(['seckill_price' => 'asc', 'goods_sku_id' => 'asc']); + } + + /** + * 根据商品id集获取商品列表 + * @param array $goodsIds + * @param array $param + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($goodsIds, $param = []) + { + // 默认条件 + $param = array_merge([ + 'status' => null, + 'limit' => 15, + ], $param); + // 筛选条件 + !is_null($param['status']) && $this->where('status', '=', (int)$param['status']); + // 获取商品列表数据 + $list = $this->with(['sku']) + ->where('sharp_goods_id', 'in', $goodsIds) + ->where('is_delete', '=', 0) + ->order(['sort' => 'asc']) + ->paginate($param['limit'], false, [ + 'query' => \request()->request() + ]); + // 设置商品数据 + return $this->setGoodsListData($list, true); + } + + /** + * 秒杀商品详情 + * @param $sharpGoodsId + * @param array $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($sharpGoodsId, $with = []) + { + // 整理商品数据并返回 + $model = static::get($sharpGoodsId, $with); + return $model->setGoodsListData($model, false); + } + + /** + * 商品多规格信息 + * @param $goods + * @param $sharpGoodsSku + * @return array|null + */ + public function getSpecData($goods, $sharpGoodsSku) + { + $specData = GoodsService::getSpecData($goods); + if ($goods['spec_type'] == 10) { + return $specData; + } + $skuData = helper::arrayColumn2Key($sharpGoodsSku, 'spec_sku_id'); + foreach ($specData['spec_list'] as &$item) { + if (isset($skuData[$item['spec_sku_id']])) { + $item['form']['seckill_price'] = $skuData[$item['spec_sku_id']]['seckill_price']; + $item['form']['original_price'] = $item['form']['goods_price']; + $item['form']['seckill_stock'] = $skuData[$item['spec_sku_id']]['seckill_stock']; + } + } + return $specData; + } + + /** + * 设置商品展示的数据 + * @param $data + * @param bool $isMultiple + * @param callable|null $callback + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + protected function setGoodsListData($data, $isMultiple = true, callable $callback = null) + { + // 设置原商品数据 + $data = GoodsService::setGoodsData($data, $isMultiple); + if (!$isMultiple) $dataSource = [&$data]; else $dataSource = &$data; + // 整理商品数据 + foreach ($dataSource as &$item) { + // 商品名称 + $item['goods_name'] = $item['goods']['goods_name']; + // 商品图片 + $item['goods_image'] = $item['goods']['goods_image']; + // 秒杀商品sku信息 + $item['goods_sku'] = $this->getDefaultSharpSku($item['sku'], $item['goods']['sku']); + // 回调函数 + is_callable($callback) && call_user_func($callback, $item); + } + return $data; + } + + /** + * 整理秒杀商品的默认sku信息 (用于商品列表) + * @param $sharpSku + * @param $goodsSku + * @return mixed + */ + private function getDefaultSharpSku($sharpSku, $goodsSku) + { + $sharpGoodsSku = $sharpSku[0]; + $goodsSkuItem = helper::getArrayItemByColumn($goodsSku, 'spec_sku_id', $sharpGoodsSku['spec_sku_id']); + $sharpGoodsSku['original_price'] = $goodsSkuItem['goods_price']; + $sharpGoodsSku['image_id'] = $goodsSkuItem['image_id']; + $sharpGoodsSku['goods_no'] = $goodsSkuItem['goods_no']; + $sharpGoodsSku['line_price'] = $goodsSkuItem['line_price']; + $sharpGoodsSku['goods_weight'] = $goodsSkuItem['goods_weight']; + return $sharpGoodsSku; + } + +} \ No newline at end of file diff --git a/source/application/common/model/sharp/GoodsSku.php b/source/application/common/model/sharp/GoodsSku.php new file mode 100644 index 0000000..5923f7a --- /dev/null +++ b/source/application/common/model/sharp/GoodsSku.php @@ -0,0 +1,26 @@ +belongsTo("app\\{$module}\\model\\sharp\\Goods"); + } + +} \ No newline at end of file diff --git a/source/application/common/model/sharp/Setting.php b/source/application/common/model/sharp/Setting.php new file mode 100644 index 0000000..fb5f8eb --- /dev/null +++ b/source/application/common/model/sharp/Setting.php @@ -0,0 +1,100 @@ +toArray(), null, 'key'); + Cache::tag('cache')->set($cacheKey, $data); + } + return array_merge_multiple($self->defaultData(), $data); + } + + /** + * 获取设置项信息 + * @param $key + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($key) + { + return static::get(compact('key')); + } + + /** + * 默认配置 + * @return array + */ + public function defaultData() + { + return [ + 'basic' => [ + 'key' => 'basic', + 'describe' => '基础设置', + 'values' => [ + // 是否开启分销 + 'is_dealer' => '0', + 'order' => [ + // 秒杀订单未支付n分钟后自动关闭 + 'order_close' => '10', + ] + ] + ] + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/model/store/Access.php b/source/application/common/model/store/Access.php new file mode 100644 index 0000000..a548fa3 --- /dev/null +++ b/source/application/common/model/store/Access.php @@ -0,0 +1,61 @@ +order(['sort' => 'asc', 'create_time' => 'asc'])->select(); + return $data ? $data->toArray() : []; + } + + /** + * 权限信息 + * @param int|array $where + * @return array|false|\PDOStatement|string|\think\Model|static + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public static function detail($where) + { + $model = static::useGlobalScope(false); + is_array($where) ? $model->where($where) : $model->where('access_id', '=', $where); + return $model->find(); + } + + /** + * 获取权限url集 + * @param $accessIds + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public static function getAccessUrls($accessIds) + { + $urls = []; + foreach (static::getAll() as $item) { + in_array($item['access_id'], $accessIds) && $urls[] = $item['url']; + } + return $urls; + } + +} \ No newline at end of file diff --git a/source/application/common/model/store/Role.php b/source/application/common/model/store/Role.php new file mode 100644 index 0000000..df5b81a --- /dev/null +++ b/source/application/common/model/store/Role.php @@ -0,0 +1,27 @@ +hasOne("app\\{$module}\\model\\UploadFile", 'file_id', 'logo_image_id'); + } + + /** + * 地区名称 + * @param $value + * @param $data + * @return array + */ + public function getRegionAttr($value, $data) + { + return [ + 'province' => RegionModel::getNameById($data['province_id']), + 'city' => RegionModel::getNameById($data['city_id']), + 'region' => $data['region_id'] == 0 ? '' : RegionModel::getNameById($data['region_id']), + ]; + } + + /** + * 门店详情 + * @param $shop_id + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($shop_id) + { + return static::get($shop_id, ['logo']); + } + +} \ No newline at end of file diff --git a/source/application/common/model/store/User.php b/source/application/common/model/store/User.php new file mode 100644 index 0000000..01ca08a --- /dev/null +++ b/source/application/common/model/store/User.php @@ -0,0 +1,82 @@ +belongsTo("app\\{$module}\\model\\Wxapp"); + } + + /** + * 关联用户角色表表 + * @return \think\model\relation\BelongsToMany + */ + public function role() + { + return $this->belongsToMany('Role', 'StoreUserRole'); + } + + /** + * 验证用户名是否重复 + * @param $user_name + * @return bool + */ + public static function checkExist($user_name) + { + return !!static::useGlobalScope(false) + ->where('user_name', '=', $user_name) + ->where('is_delete', '=', 0) + ->value('store_user_id'); + } + + /** + * 商家用户详情 + * @param $where + * @param array $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($where, $with = []) + { + !is_array($where) && $where = ['store_user_id' => (int)$where]; + return static::get(array_merge(['is_delete' => 0], $where), $with); + } + + /** + * 保存登录状态 + * @param $user + * @throws \think\Exception + */ + public function loginState($user) + { + /** @var \app\common\model\Wxapp $wxapp */ + $wxapp = $user['wxapp']; + // 保存登录状态 + Session::set('yoshop_store', [ + 'user' => [ + 'store_user_id' => $user['store_user_id'], + 'user_name' => $user['user_name'], + ], + 'wxapp' => $wxapp->toArray(), + 'is_login' => true, + ]); + } + +} diff --git a/source/application/common/model/store/UserRole.php b/source/application/common/model/store/UserRole.php new file mode 100644 index 0000000..f0d7727 --- /dev/null +++ b/source/application/common/model/store/UserRole.php @@ -0,0 +1,17 @@ +BelongsTo("app\\{$module}\\model\\User"); + } + + /** + * 关联门店表 + * @return \think\model\relation\BelongsTo + */ + public function shop() + { + $module = static::getCalledModule() ?: 'common'; + return $this->BelongsTo("app\\{$module}\\model\\store\\Shop"); + } + + /** + * 店员详情 + * @param $where + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($where) + { + $filter = is_array($where) ? $where : ['clerk_id' => $where]; + return static::get(array_merge(['is_delete' => 0], $filter)); + } + +} \ No newline at end of file diff --git a/source/application/common/model/store/shop/Order.php b/source/application/common/model/store/shop/Order.php new file mode 100644 index 0000000..619a170 --- /dev/null +++ b/source/application/common/model/store/shop/Order.php @@ -0,0 +1,73 @@ +BelongsTo("app\\{$module}\\model\\store\\Shop"); + } + + /** + * 关联店员表 + * @return \think\model\relation\BelongsTo + */ + public function clerk() + { + $module = static::getCalledModule() ?: 'common'; + return $this->BelongsTo("app\\{$module}\\model\\store\\shop\\Clerk"); + } + + /** + * 订单类型 + * @param $value + * @return array + */ + public function getOrderTypeAttr($value) + { + $types = OrderTypeEnum::getTypeName(); + return ['text' => $types[$value], 'value' => $value]; + } + + /** + * 新增核销记录 + * @param int $order_id 订单id + * @param int $shop_id 门店id + * @param int $clerk_id 核销员id + * @param int $order_type + * @return mixed + */ + public static function add( + $order_id, + $shop_id, + $clerk_id, + $order_type = OrderTypeEnum::MASTER + ) + { + return (new static)->save([ + 'order_id' => $order_id, + 'order_type' => $order_type, + 'shop_id' => $shop_id, + 'clerk_id' => $clerk_id, + 'wxapp_id' => static::$wxapp_id + ]); + } + +} \ No newline at end of file diff --git a/source/application/common/model/user/BalanceLog.php b/source/application/common/model/user/BalanceLog.php new file mode 100644 index 0000000..2044264 --- /dev/null +++ b/source/application/common/model/user/BalanceLog.php @@ -0,0 +1,66 @@ + SceneEnum::data(), + ]; + } + + /** + * 关联会员记录表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 余额变动场景 + * @param $value + * @return array + */ + public function getSceneAttr($value) + { + return ['text' => SceneEnum::data()[$value]['name'], 'value' => $value]; + } + + /** + * 新增记录 + * @param $scene + * @param $data + * @param $describeParam + */ + public static function add($scene, $data, $describeParam) + { + $model = new static; + $model->save(array_merge([ + 'scene' => $scene, + 'describe' => vsprintf(SceneEnum::data()[$scene]['describe'], $describeParam), + 'wxapp_id' => $model::$wxapp_id + ], $data)); + } + +} \ No newline at end of file diff --git a/source/application/common/model/user/Grade.php b/source/application/common/model/user/Grade.php new file mode 100644 index 0000000..e6093fd --- /dev/null +++ b/source/application/common/model/user/Grade.php @@ -0,0 +1,111 @@ + 'asc']) + { + $model = new static; + $wxappId = $wxappId ? $wxappId : $model::$wxapp_id; + return $model->where('status', '=', '1') + ->where('is_delete', '=', '0') + ->where('wxapp_id', '=', $wxappId) + ->order($order) + ->select(); + } + + /** + * 验证等级权重是否存在 + * @param int $weight 验证的权重 + * @param int $gradeId 自身的等级ID + * @return bool + */ + public static function checkExistByWeight($weight, $gradeId = 0) + { + $model = new static; + $gradeId > 0 && $model->where('grade_id', '<>', (int)$gradeId); + return $model->where('weight', '=', (int)$weight) + ->value('grade_id'); + } + +} \ No newline at end of file diff --git a/source/application/common/model/user/GradeLog.php b/source/application/common/model/user/GradeLog.php new file mode 100644 index 0000000..ed4adbf --- /dev/null +++ b/source/application/common/model/user/GradeLog.php @@ -0,0 +1,36 @@ + ChangeTypeEnum::ADMIN_USER, + 'wxapp_id' => static::$wxapp_id + ], $item); + } + return $this->isUpdate(false)->saveAll($saveData); + } + +} \ No newline at end of file diff --git a/source/application/common/model/user/PointsLog.php b/source/application/common/model/user/PointsLog.php new file mode 100644 index 0000000..5323cb7 --- /dev/null +++ b/source/application/common/model/user/PointsLog.php @@ -0,0 +1,48 @@ +belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 新增记录 + * @param $data + */ + public static function add($data) + { + $static = new static; + $static->save(array_merge(['wxapp_id' => $static::$wxapp_id], $data)); + } + + /** + * 新增记录 (批量) + * @param $saveData + * @return array|false + * @throws \Exception + */ + public function onBatchAdd($saveData) + { + return $this->isUpdate(false)->saveAll($saveData); + } + +} \ No newline at end of file diff --git a/source/application/common/model/wow/Order.php b/source/application/common/model/wow/Order.php new file mode 100644 index 0000000..459d6d2 --- /dev/null +++ b/source/application/common/model/wow/Order.php @@ -0,0 +1,123 @@ +belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 获取器:最后更新时间 + * @param $value + * @return array + */ + public function getLastTimeAttr($value) + { + return ['value' => $value, 'text' => date('Y-m-d H:i:s', $value)]; + } + + /** + * 获取单条记录 + * @param $id + * @param $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($id, $with = ['goods.image.file', 'user']) + { + return static::get($id, $with); + } + + /** + * 添加好物圈订单同步记录(批量) + * @param $wxappId + * @param $orderList + * @param $orderType + * @return array|false + * @throws \Exception + */ + public function add($wxappId, $orderList, $orderType = OrderTypeEnum::MASTER) + { + // 批量添加 + $saveData = []; + foreach ($orderList as $item) { + $saveData[] = [ + 'order_id' => $item['order_id'], + 'user_id' => $item['user_id'], + 'order_type' => $orderType, + 'status' => WowOrderService::getStatusByOrder($item), + 'last_time' => time(), + 'wxapp_id' => $wxappId, + ]; + } + return $this->isUpdate(false)->saveAll($saveData); + } + + /** + * 更新好物圈订单同步记录(批量) + * @param $legalList + * @return array|false + * @throws \Exception + */ + public function edit($legalList) + { + // 批量更新 + $saveData = []; + foreach ($legalList as $id => $item) { + $saveData[] = [ + 'id' => $id, + 'status' => WowOrderService::getStatusByOrder($item), + 'last_time' => time(), + ]; + } + return $this->isUpdate()->saveAll($saveData); + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]); + } + + /** + * 根据订单id集和订单类型获取记录 + * @param array $orderIds + * @param int $orderType + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByOrderIds($orderIds, $orderType = OrderTypeEnum::MASTER) + { + return $this->where('order_id', 'in', $orderIds) + ->where('order_type', '=', $orderType) + ->where('is_delete', '=', 0) + ->select(); + } + +} \ No newline at end of file diff --git a/source/application/common/model/wow/Setting.php b/source/application/common/model/wow/Setting.php new file mode 100644 index 0000000..07b505d --- /dev/null +++ b/source/application/common/model/wow/Setting.php @@ -0,0 +1,98 @@ +toArray(), null, 'key'); + Cache::tag('cache')->set($cacheKey, $data); + } + return array_merge_multiple($self->defaultData(), $data); + } + + /** + * 获取设置项信息 + * @param $key + * @return null|static + * @throws \think\exception\DbException + */ + public static function detail($key) + { + return static::get(compact('key')); + } + + /** + * 默认配置 + * @return array + */ + public function defaultData() + { + return [ + 'basic' => [ + 'key' => 'basic', + 'describe' => '基础设置', + 'values' => [ + // 是否同步购物车 (商品收藏) + 'is_shopping' => '0', + // 是否同步订单 + 'is_order' => '0', + ] + ] + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/model/wow/Shoping.php b/source/application/common/model/wow/Shoping.php new file mode 100644 index 0000000..3724a4b --- /dev/null +++ b/source/application/common/model/wow/Shoping.php @@ -0,0 +1,98 @@ +belongsTo("app\\{$module}\\model\\Goods"); + } + + /** + * 关联用户表 + * @return \think\model\relation\BelongsTo + */ + public function user() + { + $module = self::getCalledModule() ?: 'common'; + return $this->belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 获取单条记录 + * @param $id + * @param $with + * @return static|null + * @throws \think\exception\DbException + */ + public static function detail($id, $with = ['goods.image.file', 'user']) + { + return static::get($id, $with); + } + + /** + * 新增好物圈商品收藏记录 + * @param int $userId 用户id + * @param array $goodsIds 商品id + * @return array|false + * @throws \Exception + */ + public function add($userId, $goodsIds) + { + // 过滤该用户已收藏的商品id + $newGoodsIds = $this->getFilterGoodsIds($userId, $goodsIds); + if (empty($newGoodsIds)) { + return false; + } + $saveData = []; + foreach ($newGoodsIds as $goodsId) { + $saveData[] = [ + 'goods_id' => $goodsId, + 'user_id' => $userId, + 'wxapp_id' => self::$wxapp_id, + ]; + } + return $this->isUpdate(false)->saveAll($saveData); + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]); + } + + /** + * 过滤指定用户已收藏的商品id + * @param $userId + * @param $newGoodsIds + * @return array + */ + private function getFilterGoodsIds($userId, $newGoodsIds) + { + $alreadyGoodsId = $this->where('user_id', '=', $userId) + ->where('is_delete', '=', 0) + ->column('goods_id'); + return array_diff($newGoodsIds, $alreadyGoodsId); + } + +} \ No newline at end of file diff --git a/source/application/common/model/wxapp/Formid.php b/source/application/common/model/wxapp/Formid.php new file mode 100644 index 0000000..bc57e61 --- /dev/null +++ b/source/application/common/model/wxapp/Formid.php @@ -0,0 +1,50 @@ +belongsTo("app\\{$module}\\model\\User"); + } + + /** + * 获取一个可用的formid + * @param $userId + * @return array|false|\PDOStatement|string|\think\Model|static + */ + public static function getAvailable($userId) + { + return (new static)->where([ + 'user_id' => $userId, + 'is_used' => 0, + 'expiry_time' => ['>=', time()] + ])->order(['create_time' => 'asc'])->find(); + } + + /** + * 标记为已使用 + * @param $id + * @return Formid + */ + public static function setIsUsed($id) + { + return static::update(['is_used' => 1], compact('id')); + } + +} \ No newline at end of file diff --git a/source/application/common/service/Basics.php b/source/application/common/service/Basics.php new file mode 100644 index 0000000..71ae337 --- /dev/null +++ b/source/application/common/service/Basics.php @@ -0,0 +1,31 @@ +error; + } + + /** + * 是否存在错误 + * @return bool + */ + public function hasError() + { + return !empty($this->error); + } + +} \ No newline at end of file diff --git a/source/application/common/service/Goods.php b/source/application/common/service/Goods.php new file mode 100644 index 0000000..1db8d28 --- /dev/null +++ b/source/application/common/service/Goods.php @@ -0,0 +1,53 @@ +getListByIds(helper::getArrayColumn($dataSource, $goodsIndex)); + $goodsList = helper::arrayColumn2Key($goodsData, 'goods_id'); + // 整理列表数据 + foreach ($dataSource as &$item) { + $item['goods'] = isset($goodsList[$item[$goodsIndex]]) ? $goodsList[$item[$goodsIndex]] : null; + } + return $data; + } + + /** + * 商品多规格信息 + * @param GoodsModel|null $model + * @return null|array + */ + public static function getSpecData($model = null) + { + // 商品sku数据 + if (!is_null($model) && $model['spec_type'] == 20) { + return $model->getManySpecData($model['spec_rel'], $model['sku']); + } + return null; + } + +} \ No newline at end of file diff --git a/source/application/common/service/Message.php b/source/application/common/service/Message.php new file mode 100644 index 0000000..42a4154 --- /dev/null +++ b/source/application/common/service/Message.php @@ -0,0 +1,346 @@ + 'pages/order/detail', + OrderTypeEnum::SHARING => 'pages/sharing/order/detail/detail', + ]; + // 发送模板消息 + $status = $this->sendTemplateMessage($order['wxapp_id'], [ + 'touser' => $order['user']['open_id'], + 'template_id' => $template['template_id'], + 'page' => $urls[$orderType] . '?order_id=' . $order['order_id'], + 'form_id' => $formId['form_id'], + 'data' => [ + // 订单编号 + 'keyword1' => $order['order_no'], + // 支付时间 + 'keyword2' => date('Y-m-d H:i:s', $order['pay_time']), + // 订单金额 + 'keyword3' => $order['pay_price'], + // 商品名称 + 'keyword4' => $this->formatGoodsName($order['goods']), + ] + ]); + // 标记formid已使用 + $status === true && FormIdService::setIsUsed($formId['id']); + // 2. 商家短信通知 + $smsConfig = SettingModel::getItem('sms', $order['wxapp_id']); + $SmsDriver = new SmsDriver($smsConfig); + return $SmsDriver->sendSms('order_pay', ['order_no' => $order['order_no']]); + } + + /** + * 后台发货通知 + * @param \think\Model $order + * @param int $orderType 订单类型 (10商城订单 20拼团订单) + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function delivery($order, $orderType = OrderTypeEnum::MASTER) + { + // 微信模板消息 + $template = SettingModel::getItem('tplMsg', $order['wxapp_id'])['delivery']; + if (!$template['is_enable'] || empty($template['template_id'])) { + return false; + } + // 获取可用的formid + if (!$formId = FormIdService::getAvailableFormId($order['user_id'])) { + return false; + } + // 页面链接 + $urls = [ + OrderTypeEnum::MASTER => 'pages/order/detail', + OrderTypeEnum::SHARING => 'pages/sharing/order/detail/detail', + ]; + // 发送模板消息 + $status = $this->sendTemplateMessage($order['wxapp_id'], [ + 'touser' => $order['user']['open_id'], + 'template_id' => $template['template_id'], + 'page' => $urls[$orderType] . '?order_id=' . $order['order_id'], + 'form_id' => $formId['form_id'], + 'data' => [ + // 订单编号 + 'keyword1' => $order['order_no'], + // 商品信息 + 'keyword2' => $this->formatGoodsName($order['goods']), + // 收货人 + 'keyword3' => $order['address']['name'], + // 收货地址 + 'keyword4' => implode('', $order['address']['region']) . $order['address']['detail'], + // 物流公司 + 'keyword5' => $order['express']['express_name'], + // 物流单号 + 'keyword6' => $order['express_no'], + ] + ]); + // 标记formid已使用 + $status === true && FormIdService::setIsUsed($formId['id']); + return $status; + } + + /** + * 后台售后单状态通知 + * @param \think\Model $refund + * @param $order_no + * @param int $orderType 订单类型 (10商城订单 20拼团订单) + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function refund($refund, $order_no, $orderType = OrderTypeEnum::MASTER) + { + // 微信模板消息 + $template = SettingModel::getItem('tplMsg', $refund['wxapp_id'])['refund']; + if (!$template['is_enable'] || empty($template['template_id'])) { + return false; + } + // 获取可用的formid + if (!$formId = FormIdService::getAvailableFormId($refund['user_id'])) { + return false; + } + // 页面链接 + $urls = [ + OrderTypeEnum::MASTER => 'pages/order/refund/index', + OrderTypeEnum::SHARING => 'pages/sharing/order/refund/index', + ]; + // 发送模板消息 + $status = $this->sendTemplateMessage($refund['wxapp_id'], [ + 'touser' => $refund['user']['open_id'], + 'template_id' => $template['template_id'], + 'page' => $urls[$orderType], + 'form_id' => $formId['form_id'], + 'data' => [ + // 售后类型 + 'keyword1' => $refund['type']['text'], + // 状态 + 'keyword2' => $refund['status']['text'], + // 订单号 + 'keyword3' => $order_no, + // 商品名称 + 'keyword4' => $refund['order_goods']['goods_name'], + // 申请时间 + 'keyword5' => $refund['create_time'], + // 申请原因 + 'keyword6' => $refund['apply_desc'], + ] + ]); + // 标记formid已使用 + FormIdService::setIsUsed($formId['id']); + return $status; + } + + /** + * 拼团拼单状态通知 + * @param \app\common\model\sharing\Active $active + * @param string $status_text + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function sharingActive($active, $status_text) + { + // 微信模板消息 + $config = SharingSettingModel::getItem('basic', $active['wxapp_id']); + if (empty($config['tpl_msg_id'])) { + return false; + } + foreach ($active['users'] as $item) { + // 获取可用的formid + if (!$formId = FormIdService::getAvailableFormId($item['user']['user_id'])) { + continue; + } + // 发送模板消息 + $this->sendTemplateMessage($active['wxapp_id'], [ + 'touser' => $item['user']['open_id'], + 'template_id' => $config['tpl_msg_id'], + 'page' => 'pages/sharing/active/index?active_id=' . $active['active_id'], + 'form_id' => $formId['form_id'], + 'data' => [ + // 订单编号 + 'keyword1' => $item['sharing_order']['order_no'], + // 商品名称 + 'keyword2' => $active['goods']['goods_name'], + // 拼团价格 + 'keyword3' => $item['sharing_order']['pay_price'], + // 拼团人数 + 'keyword4' => $active['people'], + // 拼团时间 + 'keyword5' => $item['create_time'], + // 拼团结果 + 'keyword6' => $status_text, + ] + ]); + // 标记formid已使用 + FormIdService::setIsUsed($formId['id']); + } + return true; + } + + /** + * 分销商提现审核通知 + * @param \app\common\model\dealer\Withdraw $withdraw + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function withdraw($withdraw) + { + // 模板消息id + $template = DealerSettingModel::getItem('template_msg', $withdraw['wxapp_id']); + if (empty($template['withdraw_tpl'])) { + return false; + } + // 获取可用的formid + if (!$formId = FormIdService::getAvailableFormId($withdraw['user_id'])) { + return false; + } + // 获取用户信息 + $user = UserModel::detail($withdraw['user_id']); + // 发送模板消息 + $remark = '无'; + if ($withdraw['apply_status'] == 30) { + $remark = $withdraw['reject_reason']; + } + $status = $this->sendTemplateMessage($withdraw['wxapp_id'], [ + 'touser' => $user['open_id'], + 'template_id' => $template['withdraw_tpl'], + 'page' => 'pages/dealer/withdraw/list/list', + 'form_id' => $formId['form_id'], + 'data' => [ + // 提现时间 + 'keyword1' => $withdraw['create_time'], + // 提现方式 + 'keyword2' => $withdraw['pay_type']['text'], + // 提现金额 + 'keyword3' => $withdraw['money'], + // 提现状态 + 'keyword4' => $withdraw->applyStatus[$withdraw['apply_status']], + // 备注 + 'keyword5' => $remark, + ] + ]); + // 标记formid已使用 + FormIdService::setIsUsed($formId['id']); + return $status; + } + + /** + * 分销商入驻审核通知 + * @param \app\common\model\dealer\Apply $dealer + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function dealer($dealer) + { + // 模板消息id + $template = DealerSettingModel::getItem('template_msg', $dealer['wxapp_id']); + if (empty($template['apply_tpl'])) { + return false; + } + // 获取可用的formid + if (!$formId = FormIdService::getAvailableFormId($dealer['user_id'])) { + return false; + } + // 获取用户信息 + $user = UserModel::detail($dealer['user_id']); + // 发送模板消息 + $remark = '分销商入驻审核通知'; + if ($dealer['apply_status'] == 30) { + $remark .= "\n\n驳回原因:" . $dealer['reject_reason']; + } + $status = $this->sendTemplateMessage($dealer['wxapp_id'], [ + 'touser' => $user['open_id'], + 'template_id' => $template['apply_tpl'], + 'page' => 'pages/dealer/index/index', + 'form_id' => $formId['form_id'], + 'data' => [ + // 申请时间 + 'keyword1' => $dealer['apply_time'], + // 审核状态 + 'keyword2' => $dealer->applyStatus[$dealer['apply_status']], + // 审核时间 + 'keyword3' => $dealer['audit_time'], + // 备注信息 + 'keyword4' => $remark, + ] + ]); + // 标记formid已使用 + FormIdService::setIsUsed($formId['id']); + return $status; + } + + /** + * 发送模板消息 + * @param $wxappId + * @param $params + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + private function sendTemplateMessage($wxappId, $params) + { + // 微信模板消息 + $wxConfig = WxappModel::getWxappCache($wxappId); + $WxTplMsg = new WxTplMsg($wxConfig['app_id'], $wxConfig['app_secret']); + return $WxTplMsg->sendTemplateMessage($params); + } + + /** + * 格式化商品名称 + * @param $goodsData + * @return string + */ + private function formatGoodsName($goodsData) + { + $str = ''; + foreach ($goodsData as $goods) { + $str .= $goods['goods_name'] . ' '; + } + return $str; + } + +} \ No newline at end of file diff --git a/source/application/common/service/Order.php b/source/application/common/service/Order.php new file mode 100644 index 0000000..596c93b --- /dev/null +++ b/source/application/common/service/Order.php @@ -0,0 +1,91 @@ + 'app\common\model\Order', + OrderTypeEnum::SHARING => 'app\common\model\sharing\Order' + ]; + + /** + * 生成订单号 + * @return string + */ + public static function createOrderNo() + { + return date('Ymd') . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8); + } + + /** + * 整理订单列表 (根据order_type获取不同类型的订单记录) + * @param \think\Collection|\think\Paginator $data 数据源 + * @param string $orderIndex 订单记录的索引 + * @param array $with 关联查询 + * @return mixed + */ + public static function getOrderList($data, $orderIndex = 'order', $with = []) + { + // 整理订单id + $orderIds = []; + foreach ($data as &$item) { + $orderIds[$item['order_type']['value']][] = $item['order_id']; + } + // 获取订单列表 + $orderList = []; + foreach ($orderIds as $orderType => $values) { + $model = self::model($orderType); + $orderList[$orderType] = $model->getListByIds($values, $with); + } + // 格式化到数据源 + foreach ($data as $key => &$item) { + if (!isset($orderList[$item['order_type']['value']][$item['order_id']])) { + // todo: 兼容错误数据 + $item->delete(); + unset($data[$key]); + continue; + } + $item[$orderIndex] = $orderList[$item['order_type']['value']][$item['order_id']]; + } + return $data; + } + + /** + * 获取订单详情 (根据order_type获取不同类型的订单详情) + * @param $orderId + * @param int $orderType + * @return mixed + */ + public static function getOrderDetail($orderId, $orderType = OrderTypeEnum::MASTER) + { + $model = self::model($orderType); + return $model::detail($orderId); + } + + /** + * 根据订单类型获取对应的订单模型类 + * @param int $orderType + * @return mixed + */ + public static function model($orderType = OrderTypeEnum::MASTER) + { + static $models = []; + if (!isset($models[$orderType])) { + $models[$orderType] = new self::$orderModelClass[$orderType]; + } + return $models[$orderType]; + } + +} \ No newline at end of file diff --git a/source/application/common/service/delivery/Express.php b/source/application/common/service/delivery/Express.php new file mode 100644 index 0000000..4142aa5 --- /dev/null +++ b/source/application/common/service/delivery/Express.php @@ -0,0 +1,249 @@ +cityId = $cityId; + $this->goodsList = $goodsList; + $this->orderType = $orderType; + // 整合运费模板 + $this->initDeliveryTemplate(); + } + + /** + * 验证用户收货地址是否在配送范围 + * @return bool + */ + public function isIntraRegion() + { + if (!$this->cityId) return false; + foreach ($this->data as $item) { + $cityIds = []; + foreach ($item['delivery']['rule'] as $ruleItem) { + $cityIds = array_merge($cityIds, $ruleItem['region_data']); + } + if (!in_array($this->cityId, $cityIds)) { + $this->notInRuleGoodsId = current($item['goodsList'])['goods_id']; + return false; + } + } + return true; + } + + /** + * 获取不在配送范围的商品名称 + * @return null + */ + public function getNotInRuleGoodsName() + { + $item = helper::getArrayItemByColumn($this->goodsList, 'goods_id', $this->notInRuleGoodsId); + return !empty($item) ? $item['goods_name'] : null; + } + + /** + * 获取订单的配送费用 + * @return float|string + */ + public function getDeliveryFee() + { + if (empty($this->cityId) || empty($this->goodsList) || $this->notInRuleGoodsId > 0) { + return helper::number2(0.00); + } + // 处理商品包邮 + $this->freeshipping(); + // 计算配送金额 + foreach ($this->data as &$item) { + // 计算当前配送模板的运费 + $item['delivery_fee'] = $this->calcDeliveryAmount($item); + } + // 根据运费组合策略获取最终运费金额 + return helper::number2($this->getFinalFreight()); + } + + /** + * 根据运费组合策略 计算最终运费 + * @return double + */ + private function getFinalFreight() + { + // 运费合集 + $expressPriceArr = helper::getArrayColumn($this->data, 'delivery_fee'); + // 最终运费金额 + $expressPrice = 0.00; + // 判断运费组合策略 + switch (SettingModel::getItem('trade')['freight_rule']) { + case '10': // 策略1: 叠加 + $expressPrice = array_sum($expressPriceArr); + break; + case '20': // 策略2: 以最低运费结算 + $expressPrice = min($expressPriceArr); + break; + case '30': // 策略3: 以最高运费结算 + $expressPrice = max($expressPriceArr); + break; + } + return $expressPrice; + } + + /** + * 处理商品包邮 + * @return bool + */ + private function freeshipping() + { + // 订单商品总金额 + $orderTotalPrice = helper::getArrayColumnSum($this->goodsList, 'total_price'); + // 获取满额包邮设置 + $options = SettingModel::getItem('full_free'); + foreach ($this->data as &$item) { + $item['free_goods_list'] = []; + foreach ($item['goodsList'] as $goodsItem) { + if ( + $this->orderType === OrderTypeEnum::MASTER + && $options['is_open'] == true + && $orderTotalPrice >= $options['money'] + && !in_array($goodsItem['goods_id'], $options['notin_goods']) + && !in_array($this->cityId, $options['notin_region']['citys']) + ) { + $item['free_goods_list'][] = $goodsItem['goods_id']; + } + } + } + return true; + } + + /** + * 计算当前配送模板的运费 + * @param $item + * @return float|mixed|string + */ + private function calcDeliveryAmount($item) + { + // 获取运费模板下商品总数量or总重量 + if (!$totality = $this->getItemGoodsTotal($item)) { + return 0.00; + } + // 当前收货城市配送规则 + $deliveryRule = $this->getCityDeliveryRule($item['delivery']); + if ($totality <= $deliveryRule['first']) { + return $deliveryRule['first_fee']; + } + // 续件or续重 数量 + $additional = $totality - $deliveryRule['first']; + if ($additional <= $deliveryRule['additional']) { + return helper::bcadd($deliveryRule['first_fee'], $deliveryRule['additional_fee']); + } + // 计算续重/件金额 + if ($deliveryRule['additional'] < 1) { + // 配送规则中续件为0 + $additionalFee = 0.00; + } else { + $additionalFee = helper::bcdiv($deliveryRule['additional_fee'], $deliveryRule['additional']) * $additional; + } + return helper::bcadd($deliveryRule['first_fee'], $additionalFee); + } + + /** + * 获取运费模板下商品总数量or总重量 + * @param $item + * @return int|string + */ + private function getItemGoodsTotal($item) + { + $totalWeight = 0; // 总重量 + $totalNum = 0; // 总数量 + foreach ($item['goodsList'] as $goodsItem) { + // 如果商品为包邮,则不计算总量中 + if (!in_array($goodsItem['goods_id'], $item['free_goods_list'])) { + $goodsWeight = helper::bcmul($goodsItem['goods_sku']['goods_weight'], $goodsItem['total_num']); + $totalWeight = helper::bcadd($totalWeight, $goodsWeight); + $totalNum = helper::bcadd($totalNum, $goodsItem['total_num']); + } + } + return $item['delivery']['method']['value'] == 10 ? $totalNum : $totalWeight; + } + + /** + * 根据城市id获取规则信息 + * @param + * @return array|false + */ + private function getCityDeliveryRule($delivery) + { + foreach ($delivery['rule'] as $item) { + if (in_array($this->cityId, $item['region_data'])) { + return $item; + } + } + return false; + } + + /** + * 整合运费模板 + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function initDeliveryTemplate() + { + // 运费模板ID集 + $deliveryIds = helper::getArrayColumn($this->goodsList, 'delivery_id'); + // 运费模板列表 + $deliveryList = (new DeliveryModel)->getListByIds(array_values(array_unique($deliveryIds))); + // 整理数据集 + foreach ($deliveryList as $item) { + $this->data[$item['delivery_id']]['delivery'] = $item; + $this->data[$item['delivery_id']]['goodsList'] = $this->getGoodsListByDeliveryId($item['delivery_id']); + } + return true; + } + + /** + * 根据运费模板id整理商品集 + * @param $deliveryId + * @return array + */ + private function getGoodsListByDeliveryId($deliveryId) + { + $data = []; + foreach ($this->goodsList as $item) { + $item['delivery_id'] == $deliveryId && $data[] = $item; + } + return $data; + } + + +} \ No newline at end of file diff --git a/source/application/common/service/goods/source/Bargain.php b/source/application/common/service/goods/source/Bargain.php new file mode 100644 index 0000000..198000c --- /dev/null +++ b/source/application/common/service/goods/source/Bargain.php @@ -0,0 +1,13 @@ + 'Master', + OrderSourceEnum::BARGAIN => 'Bargain', + OrderSourceEnum::SHARP => 'Sharp', + ]; + + /** + * 根据订单来源获取商品库存类 + * @param int $orderSource + * @return mixed + */ + public static function getFactory($orderSource = OrderSourceEnum::MASTER) + { + static $classObj = []; + if (!isset($classObj[$orderSource])) { + $className = __NAMESPACE__ . '\\' . static::$class[$orderSource]; + $classObj[$orderSource] = new $className(); + } + return $classObj[$orderSource]; + } +} \ No newline at end of file diff --git a/source/application/common/service/goods/source/Master.php b/source/application/common/service/goods/source/Master.php new file mode 100644 index 0000000..a7c25ef --- /dev/null +++ b/source/application/common/service/goods/source/Master.php @@ -0,0 +1,123 @@ + ['stock_num' => ['dec', $goods['total_num']]], + 'where' => [ + 'goods_id' => $goods['goods_id'], + 'spec_sku_id' => $goods['spec_sku_id'], + ], + ]; + } + } + return !empty($data) && $this->updateGoodsSku($data); + } + + /** + * 更新商品库存销量(订单付款后) + * @param $goodsList + * @return bool + * @throws \Exception + */ + public function updateStockSales($goodsList) + { + $goodsData = []; + $goodsSkuData = []; + foreach ($goodsList as $goods) { + // 记录商品的销量 + $goodsData[] = [ + 'goods_id' => $goods['goods_id'], + 'sales_actual' => ['inc', $goods['total_num']] + ]; + // 付款减库存 + if ($goods['deduct_stock_type'] == 20) { + $goodsSkuData[] = [ + 'data' => ['stock_num' => ['dec', $goods['total_num']]], + 'where' => [ + 'goods_id' => $goods['goods_id'], + 'spec_sku_id' => $goods['spec_sku_id'], + ], + ]; + } + } + // 更新商品销量 + !empty($goodsData) && $this->updateGoods($goodsData); + // 更新商品sku库存 + !empty($goodsSkuData) && $this->updateGoodsSku($goodsSkuData); + return true; + } + + /** + * 回退商品库存 + * @param $goodsList + * @param $isPayOrder + * @return array|false + * @throws \Exception + */ + public function backGoodsStock($goodsList, $isPayOrder = false) + { + $goodsSkuData = []; + foreach ($goodsList as $goods) { + $item = [ + 'where' => [ + 'goods_id' => $goods['goods_id'], + 'spec_sku_id' => $goods['spec_sku_id'], + ], + 'data' => ['stock_num' => ['inc', $goods['total_num']]], + ]; + if ($isPayOrder == true) { + // 付款订单全部库存 + $goodsSkuData[] = $item; + } else { + // 未付款订单,判断必须为下单减库存时才回退 + $goods['deduct_stock_type'] == DeductStockTypeEnum::CREATE && $goodsSkuData[] = $item; + } + } + // 更新商品sku库存 + return !empty($goodsSkuData) && $this->updateGoodsSku($goodsSkuData); + } + + /** + * 更新商品信息 + * @param $data + * @return array|false + * @throws \Exception + */ + private function updateGoods($data) + { + return (new GoodsModel)->allowField(true)->isUpdate()->saveAll($data); + } + + /** + * 更新商品sku信息 + * @param $data + * @return \think\Collection + */ + private function updateGoodsSku($data) + { + return (new GoodsSkuModel)->updateAll($data); + } +} \ No newline at end of file diff --git a/source/application/common/service/goods/source/Sharp.php b/source/application/common/service/goods/source/Sharp.php new file mode 100644 index 0000000..9e47e36 --- /dev/null +++ b/source/application/common/service/goods/source/Sharp.php @@ -0,0 +1,151 @@ + $goods['sharp_goods_id'], + 'seckill_stock' => ['dec', $goods['total_num']] + ]; + // 记录商品sku库存 + $goodsSkuData[] = [ + 'data' => ['seckill_stock' => ['dec', $goods['total_num']]], + 'where' => [ + 'sharp_goods_id' => $goods['sharp_goods_id'], + 'spec_sku_id' => $goods['spec_sku_id'], + ], + ]; + } + } + // 更新商品总销量 + !empty($goodsData) && $this->updateGoods($goodsData); + return !empty($goodsSkuData) && $this->updateGoodsSku($goodsSkuData); + } + + /** + * 更新商品库存销量(订单付款后) + * @param $goodsList + * @return bool + * @throws \Exception + */ + public function updateStockSales($goodsList) + { + $goodsData = []; + $goodsSkuData = []; + foreach ($goodsList as $goods) { + // 商品id + $sharpGoodsId = $goods['goods_source_id']; + // 记录商品总销量 + $goodsItem = [ + 'sharp_goods_id' => $sharpGoodsId, + 'total_sales' => ['inc', $goods['total_num']] + ]; + // 付款减库存 + if ($goods['deduct_stock_type'] == 20) { + // 记录商品总库存 + $goodsItem['seckill_stock'] = ['dec', $goods['total_num']]; + // 记录商品sku库存 + $goodsSkuData[] = [ + 'data' => ['seckill_stock' => ['dec', $goods['total_num']]], + 'where' => [ + 'sharp_goods_id' => $sharpGoodsId, + 'spec_sku_id' => $goods['spec_sku_id'], + ], + ]; + } + $goodsData[] = $goodsItem; + } + // 更新商品库存销量 + !empty($goodsData) && $this->updateGoods(array_values($goodsData)); + return !empty($goodsSkuData) && $this->updateGoodsSku($goodsSkuData); + } + + /** + * 回退商品库存 + * @param $goodsList + * @param $isPayOrder + * @return array|false + * @throws \Exception + */ + public function backGoodsStock($goodsList, $isPayOrder = false) + { + $goodsData = []; + $goodsSkuData = []; + foreach ($goodsList as $goods) { + // 商品id + $sharpGoodsId = $goods['goods_source_id']; + $goodsItem = [ + 'sharp_goods_id' => $sharpGoodsId, + 'seckill_stock' => ['inc', $goods['total_num']] + ]; + $goodsSkuItem = [ + 'where' => [ + 'sharp_goods_id' => $sharpGoodsId, + 'spec_sku_id' => $goods['spec_sku_id'], + ], + 'data' => ['seckill_stock' => ['inc', $goods['total_num']]], + ]; + // 付款订单全部库存 + if ($isPayOrder == true) { + $goodsData[] = $goodsItem; + $goodsSkuData[] = $goodsSkuItem; + } + // 未付款订单,判断必须为下单减库存时才回退 + if ($isPayOrder == false + && $goods['deduct_stock_type'] == DeductStockTypeEnum::CREATE) { + $goodsData[] = $goodsItem; + $goodsSkuData[] = $goodsSkuItem; + } + } + // 更新商品总库存 + !empty($goodsData) && $this->updateGoods($goodsData); + // 更新商品sku库存 + return !empty($goodsSkuData) && $this->updateGoodsSku($goodsSkuData); + } + + /** + * 更新商品信息 + * @param $data + * @return array|false + * @throws \Exception + */ + private function updateGoods($data) + { + return (new SharpGoodsModel)->allowField(true)->isUpdate()->saveAll($data); + } + + /** + * 更新商品sku信息 + * @param $data + * @return \think\Collection + */ + private function updateGoodsSku($data) + { + return (new GoodsSkuModel)->updateAll($data); + } + +} \ No newline at end of file diff --git a/source/application/common/service/order/Complete.php b/source/application/common/service/order/Complete.php new file mode 100644 index 0000000..ea391e2 --- /dev/null +++ b/source/application/common/service/order/Complete.php @@ -0,0 +1,183 @@ + 'app\common\model\Order', + OrderTypeEnum::SHARING => 'app\common\model\sharing\Order', + ]; + + /* @var \app\common\model\Order $model */ + private $model; + + /* @var UserModel $model */ + private $UserModel; + + /** + * 构造方法 + * Complete constructor. + * @param int $orderType + */ + public function __construct($orderType = OrderTypeEnum::MASTER) + { + $this->orderType = $orderType; + $this->model = $this->getOrderModel(); + $this->UserModel = new UserModel; + } + + /** + * 初始化订单模型类 + * @return \app\common\model\Order|mixed + */ + private function getOrderModel() + { + $class = $this->orderModelClass[$this->orderType]; + return new $class; + } + + /** + * 执行订单完成后的操作 + * @param \think\Collection|array $orderList + * @param int $wxappId + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + * @throws \Exception + */ + public function complete($orderList, $wxappId) + { + // 已完成订单结算 + // 条件:后台订单流程设置 - 已完成订单设置0天不允许申请售后 + if (SettingModel::getItem('trade', $wxappId)['order']['refund_days'] == 0) { + $this->settled($orderList); + } + // 发放分销商佣金 + foreach ($orderList as $order) { + DealerOrderModel::grantMoney($order, $this->orderType); + } + // 更新好物圈订单状态 + if ($this->orderType == OrderTypeEnum::MASTER) { + (new WowService($wxappId))->update($orderList); + } + return true; + } + + /** + * 执行订单结算 + * @param $orderList + * @return bool + * @throws \Exception + */ + public function settled($orderList) + { + // 订单id集 + $orderIds = helper::getArrayColumn($orderList, 'order_id'); + // 累积用户实际消费金额 + $this->setIncUserExpend($orderList); + // 处理订单赠送的积分 + $this->setGiftPointsBonus($orderList); + // 将订单设置为已结算 + $this->model->onBatchUpdate($orderIds, ['is_settled' => 1]); + return true; + } + + /** + * 处理订单赠送的积分 + * @param $orderList + * @return bool + * @throws \Exception + */ + private function setGiftPointsBonus($orderList) + { + // 计算用户所得积分 + $userData = []; + $logData = []; + foreach ($orderList as $order) { + // 计算用户所得积分 + $pointsBonus = $order['points_bonus']; + if ($pointsBonus <= 0) continue; + // 减去订单退款的积分 + foreach ($order['goods'] as $goods) { + if ( + !empty($goods['refund']) + && $goods['refund']['type']['value'] == 10 // 售后类型:退货退款 + && $goods['refund']['is_agree']['value'] == 10 // 商家审核:已同意 + ) { + $pointsBonus -= $goods['points_bonus']; + } + } + // 计算用户所得积分 + !isset($userData[$order['user_id']]) && $userData[$order['user_id']] = 0; + $userData[$order['user_id']] += $pointsBonus; + // 整理用户积分变动明细 + $logData[] = [ + 'user_id' => $order['user_id'], + 'value' => $pointsBonus, + 'describe' => "订单赠送:{$order['order_no']}", + 'wxapp_id' => $order['wxapp_id'], + ]; + } + if (!empty($userData)) { + // 累积到会员表记录 + $this->UserModel->onBatchIncPoints($userData); + // 批量新增积分明细记录 + (new PointsLogModel)->onBatchAdd($logData); + } + return true; + } + + /** + * 累积用户实际消费金额 + * @param $orderList + * @return bool + * @throws \Exception + */ + private function setIncUserExpend($orderList) + { + // 计算并累积实际消费金额(需减去售后退款的金额) + $userData = []; + foreach ($orderList as $order) { + // 订单实际支付金额 + $expendMoney = $order['pay_price']; + // 减去订单退款的金额 + foreach ($order['goods'] as $goods) { + if ( + !empty($goods['refund']) + && $goods['refund']['type']['value'] == 10 // 售后类型:退货退款 + && $goods['refund']['is_agree']['value'] == 10 // 商家审核:已同意 + ) { + $expendMoney -= $goods['refund']['refund_money']; + } + } + !isset($userData[$order['user_id']]) && $userData[$order['user_id']] = 0.00; + $expendMoney > 0 && $userData[$order['user_id']] += $expendMoney; + } + // 累积到会员表记录 + $this->UserModel->onBatchIncExpendMoney($userData); + return true; + } + +} \ No newline at end of file diff --git a/source/application/common/service/order/Printer.php b/source/application/common/service/order/Printer.php new file mode 100644 index 0000000..2dad26b --- /dev/null +++ b/source/application/common/service/order/Printer.php @@ -0,0 +1,115 @@ +getPrintContent($order); + // 执行打印请求 + return $PrinterDriver->printTicket($content); + } + + /** + * 构建订单打印的内容 + * @param \app\common\model\BaseModel $order + * @return string + */ + private function getPrintContent($order) + { + // 商城名称 + $storeName = SettingModel::getItem('store', $order['wxapp_id'])['name']; + // 收货地址 + /* @var \app\common\model\OrderAddress $address */ + $address = $order['address']; + // 拼接模板内容 + $content = "{$storeName}
"; + $content .= '--------------------------------
'; + $content .= "昵称:{$order['user']['nickName']} [{$order['user_id']}]
"; + $content .= "订单号:{$order['order_no']}
"; + $content .= '付款时间:' . date('Y-m-d H:i:s', $order['pay_time']) . '
'; + // 收货人信息 + if ($order['delivery_type']['value'] == DeliveryTypeEnum::EXPRESS) { + $content .= "--------------------------------
"; + $content .= "收货人:{$address['name']}
"; + $content .= "联系电话:{$address['phone']}
"; + $content .= '收货地址:' . $address->getFullAddress() . '
'; + } + // 自提信息 + if ($order['delivery_type']['value'] == DeliveryTypeEnum::EXTRACT && !empty($order['extract'])) { + $content .= "--------------------------------
"; + $content .= "联系人:{$order['extract']['linkman']}
"; + $content .= "联系电话:{$order['extract']['phone']}
"; + $content .= "自提门店:{$order['extract_shop']['shop_name']}
"; + } + // 商品信息 + $content .= '=========== 商品信息 ===========
'; + foreach ($order['goods'] as $key => $goods) { + $content .= ($key + 1) . ".商品名称:{$goods['goods_name']}
"; + !empty($goods['goods_attr']) && $content .= " 商品规格:{$goods['goods_attr']}
"; + $content .= " 购买数量:{$goods['total_num']}
"; + $content .= " 商品总价:{$goods['total_price']}元
"; + $content .= '--------------------------------
'; + } + // 买家备注 + if (!empty($order['buyer_remark'])) { + $content .= '============ 买家备注 ============
'; + $content .= "{$order['buyer_remark']}
"; + $content .= '--------------------------------
'; + } + // 订单金额 + if ($order['coupon_money'] > 0) { + $content .= "优惠券:-{$order['coupon_money']}元
"; + } + if ($order['points_num'] > 0) { + $content .= "积分抵扣:-{$order['points_money']}元
"; + } + if ($order['update_price']['value'] != '0.00') { + $content .= "后台改价:{$order['update_price']['symbol']}{$order['update_price']['value']}元
"; + } + // 运费 + if ($order['delivery_type']['value'] == DeliveryTypeEnum::EXPRESS) { + $content .= "运费:{$order['express_price']}元
"; + $content .= '------------------------------
'; + } + // 实付款 + $content .= "实付款:{$order['pay_price']}
"; + return $content; + } + +} \ No newline at end of file diff --git a/source/application/common/service/order/Refund.php b/source/application/common/service/order/Refund.php new file mode 100644 index 0000000..3e80be6 --- /dev/null +++ b/source/application/common/service/order/Refund.php @@ -0,0 +1,79 @@ +wxpay($order, $money); + } + // 2.余额支付退款 + if ($order['pay_type']['value'] == PayTypeEnum::BALANCE) { + return $this->balance($order, $money); + } + return false; + } + + /** + * 余额支付退款 + * @param $order + * @param $money + * @return bool + * @throws \think\Exception + * @throws \think\exception\DbException + */ + private function balance(&$order, $money) + { + // 回退用户余额 + $user = UserModel::detail($order['user_id']); + $user->setInc('balance', $money); + // 记录余额明细 + BalanceLogModel::add(SceneEnum::REFUND, [ + 'user_id' => $user['user_id'], + 'money' => $money, + ], ['order_no' => $order['order_no']]); + return true; + } + + /** + * 微信支付退款 + * @param $order + * @param double $money + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + private function wxpay(&$order, $money) + { + $wxConfig = WxappModel::getWxappCache($order['wxapp_id']); + $WxPay = new WxPay($wxConfig); + return $WxPay->refund($order['transaction_id'], $order['pay_price'], $money); + } + +} \ No newline at end of file diff --git a/source/application/common/service/order/Source.php b/source/application/common/service/order/Source.php new file mode 100644 index 0000000..e945047 --- /dev/null +++ b/source/application/common/service/order/Source.php @@ -0,0 +1,9 @@ +getQrcode($scene, $page); + // 保存到文件 + file_put_contents($savePath, $content); + return $savePath; + } + + /** + * 获取网络图片到临时目录 + * @param $wxapp_id + * @param $url + * @param string $mark + * @return string + */ + protected function saveTempImage($wxapp_id, $url, $mark = 'temp') + { + $dirPath = RUNTIME_PATH . 'image' . '/' . $wxapp_id; + !is_dir($dirPath) && mkdir($dirPath, 0755, true); + $savePath = $dirPath . '/' . $mark . '_' . md5($url) . '.png'; + if (file_exists($savePath)) return $savePath; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1); + $img = curl_exec($ch); + curl_close($ch); + $fp = fopen($savePath, 'w'); + fwrite($fp, $img); + fclose($fp); + return $savePath; + } + +} \ No newline at end of file diff --git a/source/application/common/service/qrcode/Extract.php b/source/application/common/service/qrcode/Extract.php new file mode 100644 index 0000000..6fbbea1 --- /dev/null +++ b/source/application/common/service/qrcode/Extract.php @@ -0,0 +1,98 @@ +wxappId = $wxappId; + $this->user = $user; + $this->orderId = $orderId; + $this->orderType = $orderType; + } + + /** + * 获取小程序码 + * @return mixed + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function getImage() + { + // 判断二维码文件存在则直接返回url + if (file_exists($this->getPosterPath())) { + return $this->getPosterUrl(); + } + // 下载小程序码 + $qrcode = $this->saveQrcode( + $this->wxappId, + "oid:{$this->orderId},oty:{$this->orderType}", + 'pages/store/check/order' + ); + return $this->savePoster($qrcode); + } + + private function savePoster($qrcode) + { + copy($qrcode, $this->getPosterPath()); + return $this->getPosterUrl(); + } + + /** + * 二维码文件路径 + * @return string + */ + private function getPosterPath() + { + // 保存路径 + $tempPath = WEB_PATH . "temp/{$this->wxappId}/"; + !is_dir($tempPath) && mkdir($tempPath, 0755, true); + return $tempPath . $this->getPosterName(); + } + + /** + * 二维码文件名称 + * @return string + */ + private function getPosterName() + { + return 'extract_' . md5("{$this->orderId}_{$this->user['open_id']}}") . '.png'; + } + + /** + * 二维码url + * @return string + */ + private function getPosterUrl() + { + return \base_url() . 'temp/' . $this->wxappId . '/' . $this->getPosterName() . '?t=' . time(); + } + +} \ No newline at end of file diff --git a/source/application/common/service/qrcode/Goods.php b/source/application/common/service/qrcode/Goods.php new file mode 100644 index 0000000..3ec22d7 --- /dev/null +++ b/source/application/common/service/qrcode/Goods.php @@ -0,0 +1,177 @@ + 'pages/goods/index', + 20 => 'pages/sharing/goods/index' + ]; + + /** + * 构造方法 + * Goods constructor. + * @param $goods + * @param $user + * @param int $goodsType + */ + public function __construct($goods, $user, $goodsType = 10) + { + parent::__construct(); + // 商品信息 + $this->goods = $goods; + // 当前用户id + $this->user_id = $user ? $user['user_id'] : 0; + // 商品类型:10商城商品 20拼团商品 + $this->goodsType = $goodsType; + } + + /** + * @return mixed + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function getImage() + { + // 判断海报图文件存在则直接返回url + if (file_exists($this->getPosterPath())) { + return $this->getPosterUrl(); + } + // 小程序id + $wxappId = $this->goods['wxapp_id']; + // 商品海报背景图 + $backdrop = __DIR__ . '/resource/goods_bg.png'; + // 下载商品首图 + $goodsUrl = $this->saveTempImage($wxappId, $this->goods['image'][0]['file_path'], 'goods'); + // 小程序码参数 + $scene = "gid:{$this->goods['goods_id']},uid:" . ($this->user_id ?: ''); + // 下载小程序码 + $qrcode = $this->saveQrcode($wxappId, $scene, $this->pages[$this->goodsType]); + // 拼接海报图 + return $this->savePoster($backdrop, $goodsUrl, $qrcode); + } + + /** + * 拼接海报图 + * @param $backdrop + * @param $goodsUrl + * @param $qrcode + * @return string + * @throws \Exception + */ + private function savePoster($backdrop, $goodsUrl, $qrcode) + { + // 实例化图像编辑器 + $editor = Grafika::createEditor(['Gd']); + // 字体文件路径 + $fontPath = Grafika::fontsDir() . '/' . 'st-heiti-light.ttc'; + // 打开海报背景图 + $editor->open($backdropImage, $backdrop); + // 打开商品图片 + $editor->open($goodsImage, $goodsUrl); + // 重设商品图片宽高 + $editor->resizeExact($goodsImage, 690, 690); + // 商品图片添加到背景图 + $editor->blend($backdropImage, $goodsImage, 'normal', 1.0, 'top-left', 30, 30); + // 商品名称处理换行 + $fontSize = 30; + $goodsName = $this->wrapText($fontSize, 0, $fontPath, $this->goods['goods_name'], 680, 2); + // 写入商品名称 + $editor->text($backdropImage, $goodsName, $fontSize, 30, 750, new Color('#333333'), $fontPath); + // 写入商品价格 + $priceType = [10 => 'goods_price', 20 => 'sharing_price']; + $editor->text($backdropImage, $this->goods['sku'][0][$priceType[$this->goodsType]], 38, 62, 964, new Color('#ff4444')); + // 打开小程序码 + $editor->open($qrcodeImage, $qrcode); + // 重设小程序码宽高 + $editor->resizeExact($qrcodeImage, 140, 140); + // 小程序码添加到背景图 + $editor->blend($backdropImage, $qrcodeImage, 'normal', 1.0, 'top-left', 570, 914); + // 保存图片 + $editor->save($backdropImage, $this->getPosterPath()); + return $this->getPosterUrl(); + } + + /** + * 处理文字超出长度自动换行 + * @param integer $fontsize 字体大小 + * @param integer $angle 角度 + * @param string $fontface 字体名称 + * @param string $string 字符串 + * @param integer $width 预设宽度 + * @param null $max_line 最多行数 + * @return string + */ + private function wrapText($fontsize, $angle, $fontface, $string, $width, $max_line = null) + { + // 这几个变量分别是 字体大小, 角度, 字体名称, 字符串, 预设宽度 + $content = ""; + // 将字符串拆分成一个个单字 保存到数组 letter 中 + $letter = []; + for ($i = 0; $i < mb_strlen($string, 'UTF-8'); $i++) { + $letter[] = mb_substr($string, $i, 1, 'UTF-8'); + } + $line_count = 0; + foreach ($letter as $l) { + $testbox = imagettfbbox($fontsize, $angle, $fontface, $content . ' ' . $l); + // 判断拼接后的字符串是否超过预设的宽度 + if (($testbox[2] > $width) && ($content !== "")) { + $line_count++; + if ($max_line && $line_count >= $max_line) { + $content = mb_substr($content, 0, -1, 'UTF-8') . "..."; + break; + } + $content .= "\n"; + } + $content .= $l; + } + return $content; + } + + /** + * 海报图文件路径 + * @return string + */ + private function getPosterPath() + { + // 保存路径 + $tempPath = WEB_PATH . 'temp' . '/' . $this->goods['wxapp_id'] . '/'; + !is_dir($tempPath) && mkdir($tempPath, 0755, true); + return $tempPath . $this->getPosterName(); + } + + /** + * 海报图文件名称 + * @return string + */ + private function getPosterName() + { + return 'goods_' . md5("{$this->user_id}_{$this->goodsType}_{$this->goods['goods_id']}") . '.png'; + } + + /** + * 海报图url + * @return string + */ + private function getPosterUrl() + { + return \base_url() . 'temp/' . $this->goods['wxapp_id'] . '/' . $this->getPosterName() . '?t=' . time(); + } + + +} \ No newline at end of file diff --git a/source/application/common/service/qrcode/Poster.php b/source/application/common/service/qrcode/Poster.php new file mode 100644 index 0000000..1a4ad8f --- /dev/null +++ b/source/application/common/service/qrcode/Poster.php @@ -0,0 +1,175 @@ +dealer = $dealer; + // 分销商海报设置 + $this->config = Setting::getItem('qrcode', $dealer['wxapp_id']); + } + + /** + * 获取分销二维码 + * @return string + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function getImage() + { + if (file_exists($this->getPosterPath())) { + return $this->getPosterUrl(); + } + // 小程序id + $wxappId = $this->dealer['wxapp_id']; + // 1. 下载背景图 + $backdrop = $this->saveTempImage($wxappId, $this->config['backdrop']['src'], 'backdrop'); + // 2. 下载用户头像 + $avatarUrl = $this->saveTempImage($wxappId, $this->dealer['user']['avatarUrl'], 'avatar'); + // 3. 下载小程序码 + $qrcode = $this->saveQrcode($wxappId, 'uid:' . $this->dealer['user_id']); + // 4. 拼接海报图 + return $this->savePoster($backdrop, $avatarUrl, $qrcode); + } + + /** + * 海报图文件路径 + * @return string + */ + private function getPosterPath() + { + // 保存路径 + $tempPath = WEB_PATH . 'temp' . DS . $this->dealer['wxapp_id'] . DS; + !is_dir($tempPath) && mkdir($tempPath, 0755, true); + return $tempPath . $this->getPosterName(); + } + + /** + * 海报图文件名称 + * @return string + */ + private function getPosterName() + { + return md5('poster_' . $this->dealer['user_id']) . '.png'; + } + + /** + * 海报图url + * @return string + */ + private function getPosterUrl() + { + return \base_url() . 'temp/' . $this->dealer['wxapp_id'] . '/' . $this->getPosterName() . '?t=' . time(); + } + + /** + * 拼接海报图 + * @param $backdrop + * @param $avatarUrl + * @param $qrcode + * @return string + * @throws \Exception + */ + private function savePoster($backdrop, $avatarUrl, $qrcode) + { + // 实例化图像编辑器 + $editor = Grafika::createEditor(['Gd']); + // 打开海报背景图 + $editor->open($backdropImage, $backdrop); + // 生成圆形用户头像 + $this->config['avatar']['style'] === 'circle' && $this->circular($avatarUrl, $avatarUrl); + // 打开用户头像 + $editor->open($avatarImage, $avatarUrl); + // 重设用户头像宽高 + $avatarWidth = $this->config['avatar']['width'] * 2; + $editor->resizeExact($avatarImage, $avatarWidth, $avatarWidth); + // 用户头像添加到背景图 + $avatarX = $this->config['avatar']['left'] * 2; + $avatarY = $this->config['avatar']['top'] * 2; + $editor->blend($backdropImage, $avatarImage, 'normal', 1.0, 'top-left', $avatarX, $avatarY); + + // 生成圆形小程序码 + $this->config['qrcode']['style'] === 'circle' && $this->circular($qrcode, $qrcode); + // 打开小程序码 + $editor->open($qrcodeImage, $qrcode); + // 重设小程序码宽高 + $qrcodeWidth = $this->config['qrcode']['width'] * 2; + $editor->resizeExact($qrcodeImage, $qrcodeWidth, $qrcodeWidth); + // 小程序码添加到背景图 + $qrcodeX = $this->config['qrcode']['left'] * 2; + $qrcodeY = $this->config['qrcode']['top'] * 2; + $editor->blend($backdropImage, $qrcodeImage, 'normal', 1.0, 'top-left', $qrcodeX, $qrcodeY); + + // 写入用户昵称 + $fontSize = $this->config['nickName']['fontSize'] * 2 * 0.76; + $fontX = $this->config['nickName']['left'] * 2; + $fontY = $this->config['nickName']['top'] * 2; + $Color = new Color($this->config['nickName']['color']); + $fontPath = Grafika::fontsDir() . DS . 'st-heiti-light.ttc'; + $editor->text($backdropImage, $this->dealer['user']['nickName'], $fontSize, $fontX, $fontY, $Color, $fontPath); + + // 保存图片 + $editor->save($backdropImage, $this->getPosterPath()); + return $this->getPosterUrl(); + } + + /** + * 生成圆形图片 + * @param static $imgpath 图片地址 + * @param string $saveName 保存文件名,默认空。 + */ + private function circular($imgpath, $saveName = '') + { + $srcImg = imagecreatefromstring(file_get_contents($imgpath)); + $w = imagesx($srcImg); + $h = imagesy($srcImg); + $w = $h = min($w, $h); + $newImg = imagecreatetruecolor($w, $h); + // 这一句一定要有 + imagesavealpha($newImg, true); + // 拾取一个完全透明的颜色,最后一个参数127为全透明 + $bg = imagecolorallocatealpha($newImg, 255, 255, 255, 127); + imagefill($newImg, 0, 0, $bg); + $r = $w / 2; //圆半径 + for ($x = 0; $x < $w; $x++) { + for ($y = 0; $y < $h; $y++) { + $rgbColor = imagecolorat($srcImg, $x, $y); + if (((($x - $r) * ($x - $r) + ($y - $r) * ($y - $r)) < ($r * $r))) { + imagesetpixel($newImg, $x, $y, $rgbColor); + } + } + } + // 输出图片到文件 + imagepng($newImg, $saveName); + // 释放空间 + imagedestroy($srcImg); + imagedestroy($newImg); + } + +} \ No newline at end of file diff --git a/source/application/common/service/qrcode/bargain/Goods.php b/source/application/common/service/qrcode/bargain/Goods.php new file mode 100644 index 0000000..221b13d --- /dev/null +++ b/source/application/common/service/qrcode/bargain/Goods.php @@ -0,0 +1,171 @@ +active = $active; + // 商品信息 + $this->goods = $goods; + // 当前用户id + $this->user_id = $user ? $user['user_id'] : 0; + } + + /** + * @return mixed + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function getImage() + { + // 判断海报图文件存在则直接返回url + if (file_exists($this->getPosterPath())) { + return $this->getPosterUrl(); + } + // 小程序id + $wxappId = $this->active['wxapp_id']; + // 商品海报背景图 + $backdrop = __DIR__ . '/../resource/goods_bg.png'; + // 下载商品首图 + $goodsUrl = $this->saveTempImage($wxappId, $this->goods['goods_image'], 'goods'); + // 小程序码参数 + $scene = "aid:{$this->active['active_id']},uid:" . ($this->user_id ?: ''); + // 下载小程序码 + $qrcode = $this->saveQrcode($wxappId, $scene, 'pages/bargain/goods/index'); + // 拼接海报图 + return $this->savePoster($backdrop, $goodsUrl, $qrcode); + } + + /** + * 拼接海报图 + * @param $backdrop + * @param $goodsUrl + * @param $qrcode + * @return string + * @throws \Exception + */ + private function savePoster($backdrop, $goodsUrl, $qrcode) + { + // 实例化图像编辑器 + $editor = Grafika::createEditor(['Gd']); + // 字体文件路径 + $fontPath = Grafika::fontsDir() . '/' . 'st-heiti-light.ttc'; + // 打开海报背景图 + $editor->open($backdropImage, $backdrop); + // 打开商品图片 + $editor->open($goodsImage, $goodsUrl); + // 重设商品图片宽高 + $editor->resizeExact($goodsImage, 690, 690); + // 商品图片添加到背景图 + $editor->blend($backdropImage, $goodsImage, 'normal', 1.0, 'top-left', 30, 30); + // 商品名称处理换行 + $fontSize = 30; + $goodsName = $this->wrapText($fontSize, 0, $fontPath, $this->goods['goods_name'], 680, 2); + // 写入商品名称 + $editor->text($backdropImage, $goodsName, $fontSize, 30, 750, new Color('#333333'), $fontPath); + // 写入商品价格 + $editor->text($backdropImage, $this->active['floor_price'], 38, 62, 964, new Color('#ff4444')); + // 打开小程序码 + $editor->open($qrcodeImage, $qrcode); + // 重设小程序码宽高 + $editor->resizeExact($qrcodeImage, 140, 140); + // 小程序码添加到背景图 + $editor->blend($backdropImage, $qrcodeImage, 'normal', 1.0, 'top-left', 570, 914); + // 保存图片 + $editor->save($backdropImage, $this->getPosterPath()); + return $this->getPosterUrl(); + } + + /** + * 处理文字超出长度自动换行 + * @param integer $fontsize 字体大小 + * @param integer $angle 角度 + * @param string $fontface 字体名称 + * @param string $string 字符串 + * @param integer $width 预设宽度 + * @param null $max_line 最多行数 + * @return string + */ + private function wrapText($fontsize, $angle, $fontface, $string, $width, $max_line = null) + { + // 这几个变量分别是 字体大小, 角度, 字体名称, 字符串, 预设宽度 + $content = ""; + // 将字符串拆分成一个个单字 保存到数组 letter 中 + $letter = []; + for ($i = 0; $i < mb_strlen($string, 'UTF-8'); $i++) { + $letter[] = mb_substr($string, $i, 1, 'UTF-8'); + } + $line_count = 0; + foreach ($letter as $l) { + $testbox = imagettfbbox($fontsize, $angle, $fontface, $content . ' ' . $l); + // 判断拼接后的字符串是否超过预设的宽度 + if (($testbox[2] > $width) && ($content !== "")) { + $line_count++; + if ($max_line && $line_count >= $max_line) { + $content = mb_substr($content, 0, -1, 'UTF-8') . "..."; + break; + } + $content .= "\n"; + } + $content .= $l; + } + return $content; + } + + /** + * 海报图文件路径 + * @return string + */ + private function getPosterPath() + { + // 保存路径 + $tempPath = WEB_PATH . 'temp' . '/' . $this->active['wxapp_id'] . '/'; + !is_dir($tempPath) && mkdir($tempPath, 0755, true); + return $tempPath . $this->getPosterName(); + } + + /** + * 海报图文件名称 + * @return string + */ + private function getPosterName() + { + return 'bargain_active_' . md5("{$this->user_id}_{$this->active['active_id']}") . '.png'; + } + + /** + * 海报图url + * @return string + */ + private function getPosterUrl() + { + return \base_url() . 'temp/' . $this->active['wxapp_id'] . '/' . $this->getPosterName() . '?t=' . time(); + } + +} \ No newline at end of file diff --git a/source/application/common/service/qrcode/resource/goods_bg.png b/source/application/common/service/qrcode/resource/goods_bg.png new file mode 100644 index 0000000..26eeb04 Binary files /dev/null and b/source/application/common/service/qrcode/resource/goods_bg.png differ diff --git a/source/application/common/service/qrcode/sharp/Goods.php b/source/application/common/service/qrcode/sharp/Goods.php new file mode 100644 index 0000000..524169d --- /dev/null +++ b/source/application/common/service/qrcode/sharp/Goods.php @@ -0,0 +1,170 @@ +active = $active; + // 商品信息 + $this->goods = $goods; + // 当前用户id + $this->user_id = $user ? $user['user_id'] : 0; + } + + /** + * @return mixed + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function getImage() + { + // 判断海报图文件存在则直接返回url + if (file_exists($this->getPosterPath())) { + return $this->getPosterUrl(); + } + // 小程序id + $wxappId = $this->active['wxapp_id']; + // 商品海报背景图 + $backdrop = __DIR__ . '/../resource/goods_bg.png'; + // 下载商品首图 + $goodsUrl = $this->saveTempImage($wxappId, $this->goods['goods_image'], 'goods'); + // 小程序码参数 + $scene = "aid:{$this->active['active_time_id']},gid:{$this->goods['sharp_goods_id']},uid:" . ($this->user_id ?: ''); + // 下载小程序码 + $qrcode = $this->saveQrcode($wxappId, $scene, 'pages/sharp/goods/index'); + // 拼接海报图 + return $this->savePoster($backdrop, $goodsUrl, $qrcode); + } + + /** + * 拼接海报图 + * @param $backdrop + * @param $goodsUrl + * @param $qrcode + * @return string + * @throws \Exception + */ + private function savePoster($backdrop, $goodsUrl, $qrcode) + { + // 实例化图像编辑器 + $editor = Grafika::createEditor(['Gd']); + // 字体文件路径 + $fontPath = Grafika::fontsDir() . '/' . 'st-heiti-light.ttc'; + // 打开海报背景图 + $editor->open($backdropImage, $backdrop); + // 打开商品图片 + $editor->open($goodsImage, $goodsUrl); + // 重设商品图片宽高 + $editor->resizeExact($goodsImage, 690, 690); + // 商品图片添加到背景图 + $editor->blend($backdropImage, $goodsImage, 'normal', 1.0, 'top-left', 30, 30); + // 商品名称处理换行 + $fontSize = 30; + $goodsName = $this->wrapText($fontSize, 0, $fontPath, $this->goods['goods_name'], 680, 2); + // 写入商品名称 + $editor->text($backdropImage, $goodsName, $fontSize, 30, 750, new Color('#333333'), $fontPath); + // 写入商品价格 + $editor->text($backdropImage, $this->goods['goods_sku']['seckill_price'], 38, 62, 964, new Color('#ff4444')); + // 打开小程序码 + $editor->open($qrcodeImage, $qrcode); + // 重设小程序码宽高 + $editor->resizeExact($qrcodeImage, 140, 140); + // 小程序码添加到背景图 + $editor->blend($backdropImage, $qrcodeImage, 'normal', 1.0, 'top-left', 570, 914); + // 保存图片 + $editor->save($backdropImage, $this->getPosterPath()); + return $this->getPosterUrl(); + } + + /** + * 处理文字超出长度自动换行 + * @param integer $fontsize 字体大小 + * @param integer $angle 角度 + * @param string $fontface 字体名称 + * @param string $string 字符串 + * @param integer $width 预设宽度 + * @param null $max_line 最多行数 + * @return string + */ + private function wrapText($fontsize, $angle, $fontface, $string, $width, $max_line = null) + { + // 这几个变量分别是 字体大小, 角度, 字体名称, 字符串, 预设宽度 + $content = ""; + // 将字符串拆分成一个个单字 保存到数组 letter 中 + $letter = []; + for ($i = 0; $i < mb_strlen($string, 'UTF-8'); $i++) { + $letter[] = mb_substr($string, $i, 1, 'UTF-8'); + } + $line_count = 0; + foreach ($letter as $l) { + $testbox = imagettfbbox($fontsize, $angle, $fontface, $content . ' ' . $l); + // 判断拼接后的字符串是否超过预设的宽度 + if (($testbox[2] > $width) && ($content !== "")) { + $line_count++; + if ($max_line && $line_count >= $max_line) { + $content = mb_substr($content, 0, -1, 'UTF-8') . "..."; + break; + } + $content .= "\n"; + } + $content .= $l; + } + return $content; + } + + /** + * 海报图文件路径 + * @return string + */ + private function getPosterPath() + { + // 保存路径 + $tempPath = WEB_PATH . 'temp' . '/' . $this->active['wxapp_id'] . '/'; + !is_dir($tempPath) && mkdir($tempPath, 0755, true); + return $tempPath . $this->getPosterName(); + } + + /** + * 海报图文件名称 + * @return string + */ + private function getPosterName() + { + return 'sharp_goods_' . md5("{$this->user_id}_{$this->active['active_time_id']}_{$this->goods['sharp_goods_id']}") . '.png'; + } + + /** + * 海报图url + * @return string + */ + private function getPosterUrl() + { + return \base_url() . 'temp/' . $this->active['wxapp_id'] . '/' . $this->getPosterName() . '?t=' . time(); + } + +} \ No newline at end of file diff --git a/source/application/common/service/wechat/wow/Order.php b/source/application/common/service/wechat/wow/Order.php new file mode 100644 index 0000000..9bacd4c --- /dev/null +++ b/source/application/common/service/wechat/wow/Order.php @@ -0,0 +1,364 @@ +wxappId = $wxappId; + $this->initApiDriver(); + } + + /** + * 导入好物圈订单信息 + * @param array|\think\Collection $orderList 订单列表 + * @param bool $isCheck 验证后台是否开启同步设置 + * @return bool + * @throws \app\common\exception\BaseException + * @throws \Exception + */ + public function import($orderList, $isCheck = true) + { + // 判断是否开启同步设置 + $setting = SettingModel::getItem('basic', $this->wxappId); + if ($isCheck && $setting['is_order'] == false) { + return false; + } + // 整理订单列表 + $orderListParams = $this->getOrderList($orderList, true); + // 执行api请求 + $status = $this->ApiDriver->import($orderListParams); + if ($status == false) { + $this->error = $this->ApiDriver->getError(); + return $status; + } + // 新增好物圈订单记录 + $this->model()->add($this->wxappId, $orderList); + return $status; + } + + /** + * 更新好物圈订单信息 + * @param array|\think\Collection $orderList 订单列表 + * @return bool + * @throws \app\common\exception\BaseException + * @throws \Exception + */ + public function update($orderList) + { + // 过滤不存在的订单列表 + $legalList = $this->getLegalOrderList($orderList); + if (empty($legalList)) { + return false; + } + // 整理订单列表 + $orderListParams = $this->getOrderList($legalList); + // 执行api请求 + $status = $this->ApiDriver->update($orderListParams); + if ($status == false) { + $this->error = $this->ApiDriver->getError(); + return $status; + } + // 更新好物圈订单记录 + $this->model()->edit($legalList); + return $status; + } + + /** + * 获取存在好物圈记录的订单列表 + * 用于过滤不存在好物圈同步记录的订单 + * @param array|\think\Collection $orderList 订单列表 + * @param int $orderType + * @return array|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getLegalOrderList($orderList, $orderType = OrderTypeEnum::MASTER) + { + // 把order_id设置为key + $orderList = helper::arrayColumn2Key($orderList, 'order_id'); + // 查询出合法的id集 + $legalOrderList = $this->model()->getListByOrderIds(array_keys($orderList), $orderType); + // 遍历合法的订单信息 + $legalList = []; + foreach ($legalOrderList as $item) { + $legalList[$item['id']] = $orderList[$item['order_id']]; + } + return $legalList; + } + + /** + * 删除好物圈订单记录 + * @param array $id 订单同步记录id + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function delete($id) + { + // 实例化模型 + $model = $this->model($id, ['user']); + // 执行api请求 + $status = $this->ApiDriver->delete($model['user']['open_id'], $model['order_id']); + if ($status == false) { + $this->error = $this->ApiDriver->getError(); + } + // 删除订单记录 + $model->setDelete(); + return true; + } + + /** + * 返回错误信息 + * @return mixed + */ + public function getError() + { + return $this->error; + } + + /** + * 返回商城id + * @return mixed + */ + public function getWxappId() + { + return $this->wxappId; + } + + /** + * 实例化微信api驱动 + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + private function initApiDriver() + { + $config = WxappModel::getWxappCache($this->wxappId); + $this->ApiDriver = new WowOrder($config['app_id'], $config['app_secret']); + } + + /** + * 获取好物圈订单记录模型 + * @param int|null $id + * @param array $with + * @return OrderModel|null + * @throws \think\exception\DbException + */ + private function model($id = null, $with = ['user']) + { + static $model; + if (!$model instanceof OrderModel) { + $model = $id > 0 ? OrderModel::detail($id, $with) : (new OrderModel); + } + return $model; + } + + /** + * 整理订单列表 (用于添加好物圈接口) + * @param $orderList + * @param bool $isCreate 是否为创建新订单 + * @return array + */ + private function getOrderList($orderList, $isCreate = false) + { + // 整理api参数 + $data = []; + foreach ($orderList as $order) { + // 商品列表 + $goodsList = $this->getProductList($order['goods']); + // 订单记录 + $item = [ + 'order_id' => $order['order_id'], + 'trans_id' => $order['transaction_id'], // 微信支付交易单号 + 'status' => self::getStatusByOrder($order), // 订单状态,3:支付完成 4:已发货 5:已退款 100: 已完成 + 'ext_info' => [ + 'user_open_id' => $order['user']['open_id'], + 'order_detail_page' => [ + 'path' => "pages/order/detail?order_id={$order['order_id']}" + ], + ], + ]; + + // 用于更新订单的参数 + if ($isCreate == false) { + + // 快递及包裹信息 + // 条件1:配送方式为快递配送 + // 条件2: 订单已发货 + if ( + $order['delivery_type']['value'] == DeliveryTypeEnum::EXPRESS + && $order['delivery_status']['value'] == 20 + ) { + $item['ext_info']['express_info']['express_package_info_list'] = [[ + 'express_company_id' => $order['express']['express_id'], // 快递公司id + 'express_company_name' => $order['express']['express_name'], // 快递公司名 + 'express_code' => $order['express_no'], // 快递单号 + 'ship_time' => $order['delivery_time'], // 发货时间 + 'express_page' => [ + 'path' => "pages/order/detail?order_id={$order['order_id']}" + ], + 'express_goods_info_list' => helper::getArrayColumns($goodsList, ['item_code', 'sku_id']) + ]]; + } + + } + + // 用于新增订单的参数 + if ($isCreate == true) { + // 订单创建时间 + $item['create_time'] = $order['create_time']; + // 支付完成时间 + $item['pay_finish_time'] = $order['pay_time']; + // 订单金额,单位:分 + $item['fee'] = $order['pay_price'] * 100; + // 订单支付方式,0:未知方式 1:微信支付 2:其他支付方式 + $item['ext_info']['payment_method'] = $order['pay_type']['value'] == PayTypeEnum::WECHAT ? 1 : 2; + // 商品列表 + $item['ext_info']['product_info'] = ['item_list' => $goodsList]; + // 收件人信息 + $item['ext_info']['express_info'] = array_merge( + $this->getAddressInfo($order['delivery_type']['value'], $order['address']), + ['price' => $order['express_price'] * 100] // 运费 + ); + // todo: 商家信息 + $item['ext_info']['brand_info'] = [ + 'phone' => '020-666666', // 必填:联系电话 + 'contact_detail_page' => [ + 'kf_type' => 3, + 'path' => 'pages/index/index', + ], + ]; + } + $data[] = $item; + } + return $data; + } + + /** + * 整理订单状态码 + * 订单状态,3:支付完成 4:已发货 5:已退款 100: 已完成 + * @param array $order + * @return bool|int + */ + public static function getStatusByOrder($order) + { + // 未付款 + if ($order['pay_status']['value'] != 20) { + return (int)false; + } + // 已退款 + if ($order['order_status']['value'] == 20) { + return 5; + } + // 已完成 + if ($order['order_status']['value'] == 30) { + return 100; + } + // 支付完成(未发货) + if ($order['delivery_status']['value'] == 10) { + return 3; + } + // 已发货 + if ($order['delivery_status']['value'] == 20) { + return 4; + } + return (int)false; + } + + /** + * 订单状态,3:支付完成 4:已发货 5:已退款 100: 已完成 + * @return array + */ + public static function status() + { + return [ + 0 => '未知', + 3 => '支付完成', + 4 => '已发货', + 5 => '已退款', + 100 => '已完成', + ]; + } + + /** + * 整理商品列表 + * @param array $goodsList + * @return array + */ + private function getProductList($goodsList) + { + $data = []; + foreach ($goodsList as $goods) { + $data[] = [ + 'item_code' => $goods['goods_id'], // 物品id,要求appid下全局唯一 + 'sku_id' => $goods['goods_id'], + 'amount' => $goods['total_num'], // 物品数量 + 'total_fee' => $goods['total_price'] * 100, // 物品总价,单位:分 + 'thumb_url' => $goods['image']['file_path'], + 'title' => $goods['goods_name'], // 商品名称 + 'unit_price' => $goods['goods_price'] * 100, // 物品单价(实际售价) + 'original_price' => $goods['line_price'] * 100, // 物品原价 + 'category_list' => ['商品分类'], // todo: 商品分类 + 'item_detail_page' => ['path' => "pages/goods/index?goods_id={$goods['goods_id']}"], + ]; + } + return $data; + } + + /** + * 整理收件人信息 + * @param int $deliveryType + * @param array $express + * @return array + */ + private function getAddressInfo($deliveryType, $express) + { + // 快递信息 + $data = []; + if ($deliveryType == DeliveryTypeEnum::EXPRESS) { + $data = [ + 'name' => $express['name'], // 收件人姓名 + 'phone' => $express['phone'], // 收件人联系电话 + 'province' => $express['region']['province'], // 省 + 'city' => $express['region']['city'], // 市 + 'district' => $express['region']['region'], // 区 + ]; + // 详细地址 + $data['address'] = "{$data['province']} {$data['city']} {$data['district']} {$express['detail']}"; + } + return $data; + } + +} \ No newline at end of file diff --git a/source/application/common/service/wechat/wow/Shoping.php b/source/application/common/service/wechat/wow/Shoping.php new file mode 100644 index 0000000..872581a --- /dev/null +++ b/source/application/common/service/wechat/wow/Shoping.php @@ -0,0 +1,163 @@ +wxappId = $wxappId; + $this->initApiDriver(); + } + + /** + * 添加好物圈商品收藏 + * @param \think\Collection $user 用户信息 + * @param array $goodsList 商品列表 + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function add($user, $goodsList) + { + // 判断是否开启同步设置 + $setting = SettingModel::getItem('basic', $this->wxappId); + if ($setting['is_shopping'] == false) { + return false; + } + // 整理商品列表 + $productList = $this->getProductListToAdd($goodsList); + // 执行api请求 + $status = $this->ApiDriver->addList($user['open_id'], $productList); + if ($status == false) { + $this->error = $this->ApiDriver->getError(); + return $status; + } + // 写入商品收藏记录 + $goodsIds = helper::getArrayColumn($goodsList, 'goods_id'); + $this->model()->add($user['user_id'], $goodsIds); + return $status; + } + + /** + * 删除好物圈商品收藏 + * @param $id + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function delete($id) + { + // 实例化模型 + $model = $this->model($id, ['user']); + // 执行api请求 + $status = $this->ApiDriver->delete($model['user']['open_id'], [[ + 'item_code' => $model['goods_id'], + 'sku_id' => $model['goods_id'], + ]]); + if ($status == false) { + $this->error = $this->ApiDriver->getError(); + } + // 删除商品收藏记录 + $model->setDelete(); + return true; + } + + /** + * 返回错误信息 + * @return mixed + */ + public function getError() + { + return $this->error; + } + + /** + * 实例化微信api驱动 + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + private function initApiDriver() + { + $config = WxappModel::getWxappCache($this->wxappId); + $this->ApiDriver = new WowShoping($config['app_id'], $config['app_secret']); + } + + /** + * 获取好物圈订单记录模型 + * @param int|null $id + * @param array $with + * @return ShopingModel|null + * @throws \think\exception\DbException + */ + private function model($id = null, $with = ['user']) + { + static $model; + if (!$model instanceof ShopingModel) { + $model = $id > 0 ? ShopingModel::detail($id, $with) : (new ShopingModel); + } + return $model; + } + + /** + * 整理商品列表 (用于添加收藏接口) + * @param $goodsList + * @return array + */ + private function getProductListToAdd(&$goodsList) + { + // 整理api参数 + $productList = []; + foreach ($goodsList as $goods) { + $imageList = []; // 商品图片 + foreach ($goods['image'] as $image) { + $imageList[] = $image['file_path']; + } + // sku信息 + $skuInfo = &$goods['sku'][0]; + $productList[] = [ + 'item_code' => $goods['goods_id'], + 'title' => $goods['goods_name'], + 'category_list' => [$goods['category']['name']], + 'image_list' => $imageList, + 'src_wxapp_path' => "/pages/goods/index?goods_id={$goods['goods_id']}", // 商品页面路径 + 'sku_info' => [ // 商品sku +// 'sku_id' => "{$goods['goods_id']}_{$skuInfo['spec_sku_id']}", + 'sku_id' => $goods['goods_id'], + 'price' => $skuInfo['goods_price'] * 100, + 'original_price' => $skuInfo['line_price'] * 100, // 划线价 + 'status' => 1, + ], + ]; + } + return $productList; + } + +} \ No newline at end of file diff --git a/source/application/common/service/wxapp/FormId.php b/source/application/common/service/wxapp/FormId.php new file mode 100644 index 0000000..957ffef --- /dev/null +++ b/source/application/common/service/wxapp/FormId.php @@ -0,0 +1,38 @@ + +// +---------------------------------------------------------------------- + +return [ + // +---------------------------------------------------------------------- + // | 应用设置 + // +---------------------------------------------------------------------- + + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 扩展函数文件 + 'extra_file_list' => [THINK_PATH . 'helper' . EXT], + // 默认输出类型 + 'default_return_type' => 'json', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'PRC', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => 'htmlspecialchars', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 默认模块名 + 'default_module' => 'store', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, + + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- + + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // URL伪静态后缀 + 'url_html_suffix' => '', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由 + 'url_route_on' => true, + // 路由使用完整匹配 + 'route_complete_match' => false, + // 路由配置文件(支持配置多个) + 'route_config_file' => ['route'], + // 是否强制使用路由 + 'url_route_must' => false, + // 域名部署 + 'url_domain_deploy' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 模板设置 + // +---------------------------------------------------------------------- + + 'template' => [ + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 模板路径 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + ], + + // 视图输出字符串内容替换 + 'view_replace_str' => [], + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 异常页面的模板文件 + 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '\\app\\common\\exception\\ExceptionHandler', + + // +---------------------------------------------------------------------- + // | 日志设置 + // +---------------------------------------------------------------------- + + 'log' => [ + // 日志记录方式,内置 file socket 支持扩展 + 'type' => 'File', + // 日志保存目录 + 'path' => LOG_PATH, + // 日志记录级别 + 'level' => [], + // error和sql日志单独记录 + 'apart_level' => ['begin', 'error', 'sql', 'yoshop-info'], + ], + + // +---------------------------------------------------------------------- + // | Trace设置 开启 app_trace 后 有效 + // +---------------------------------------------------------------------- + 'trace' => [ + // 内置Html Console 支持扩展 + 'type' => 'Html', + ], + + // +---------------------------------------------------------------------- + // | 缓存设置 + // +---------------------------------------------------------------------- + + 'cache' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => CACHE_PATH, + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + ], + + // +---------------------------------------------------------------------- + // | 会话设置 + // +---------------------------------------------------------------------- + + 'session' => [ + 'id' => '', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // SESSION 前缀 + 'prefix' => '', + // 驱动方式 支持redis memcache memcached + 'type' => '', + // 是否自动开启 SESSION + 'auto_start' => true, + ], + + // +---------------------------------------------------------------------- + // | Cookie设置 + // +---------------------------------------------------------------------- + 'cookie' => [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ], + + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page', + 'list_rows' => 15, + ], +]; diff --git a/source/application/database.php b/source/application/database.php new file mode 100644 index 0000000..bede4b2 --- /dev/null +++ b/source/application/database.php @@ -0,0 +1,54 @@ + '127.0.0.1', + 'database' => 'yoshop_pro', + 'username' => 'root', + 'password' => 'root', + 'port' => '3306', + 'charset' => 'utf8', +]; + +return [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => $config['host'], + // 数据库名 + 'database' => $config['database'], + // 用户名 + 'username' => $config['username'], + // 密码 + 'password' => $config['password'], + // 端口 + 'hostport' => $config['port'], + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => $config['charset'], + // 数据库表前缀 + 'prefix' => 'yoshop_', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'collection', + // 自动写入时间戳字段 + 'auto_timestamp' => true, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, +]; diff --git a/source/application/route.php b/source/application/route.php new file mode 100644 index 0000000..b46396b --- /dev/null +++ b/source/application/route.php @@ -0,0 +1,15 @@ + [ + 'name' => '\w+', + ], + '[hello]' => [ + ':id' => ['index/hello', ['method' => 'get'], ['id' => '\d+']], + ':name' => ['index/hello', ['method' => 'post']], + ], + +]; diff --git a/source/application/store/common.php b/source/application/store/common.php new file mode 100644 index 0000000..c6b068c --- /dev/null +++ b/source/application/store/common.php @@ -0,0 +1,20 @@ +checkPrivilege($url, $strict); + } catch (\Exception $e) { + return false; + } +} diff --git a/source/application/store/config.php b/source/application/store/config.php new file mode 100644 index 0000000..6ef0708 --- /dev/null +++ b/source/application/store/config.php @@ -0,0 +1,37 @@ + 'html', + + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => 'trim,htmlspecialchars', + + // 模板设置 + 'template' => [ + // layout布局 + 'layout_on' => true, + 'layout_name' => 'layouts/layout', + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'think', + // 模板路径 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DS, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}}', + // 标签库标签开始标记 + 'taglib_begin' => '{{', + // 标签库标签结束标记 + 'taglib_end' => '}}', + ], + + // 默认跳转页面对应的模板文件 + 'dispatch_error_tmpl' => 'layouts/error', + +]; diff --git a/source/application/store/controller/Controller.php b/source/application/store/controller/Controller.php new file mode 100644 index 0000000..2986356 --- /dev/null +++ b/source/application/store/controller/Controller.php @@ -0,0 +1,230 @@ +store = Session::get('yoshop_store'); + // 当前路由信息 + $this->getRouteinfo(); + // 验证登录状态 + $this->checkLogin(); + // 验证当前页面权限 + $this->checkPrivilege(); + // 全局layout + $this->layout(); + } + + /** + * 验证当前页面权限 + * @throws BaseException + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function checkPrivilege() + { + if ($this->routeUri === 'index/index') { + return true; + } + if (!Auth::getInstance()->checkPrivilege($this->routeUri)) { + throw new BaseException(['msg' => '很抱歉,没有访问权限']); + } + return true; + } + + /** + * 全局layout模板输出 + * @throws \think\exception\DbException + * @throws \Exception + */ + private function layout() + { + // 验证当前请求是否在白名单 + if (!in_array($this->routeUri, $this->notLayoutAction)) { + // 输出到view + $this->assign([ + 'base_url' => base_url(), // 当前域名 + 'store_url' => url('/store'), // 后台模块url + 'group' => $this->group, // 当前控制器分组 + 'menus' => $this->menus(), // 后台菜单 + 'store' => $this->store, // 商家登录信息 + 'setting' => Setting::getAll() ?: null, // 当前商城设置 + 'request' => Request::instance(), // Request对象 + 'version' => get_version(), // 系统版本号 + ]); + } + } + + /** + * 解析当前路由参数 (分组名称、控制器名称、方法名) + */ + protected function getRouteinfo() + { + // 控制器名称 + $this->controller = toUnderScore($this->request->controller()); + // 方法名称 + $this->action = $this->request->action(); + // 控制器分组 (用于定义所属模块) + $groupstr = strstr($this->controller, '.', true); + $this->group = $groupstr !== false ? $groupstr : $this->controller; + // 当前uri + $this->routeUri = $this->controller . '/' . $this->action; + } + + /** + * 后台菜单配置 + * @return mixed + * @throws \think\exception\DbException + */ + protected function menus() + { + static $menus = []; + if (empty($menus)) { + $menus = Menus::getInstance()->getMenus($this->routeUri, $this->group); + } + return $menus; + } + + /** + * 验证登录状态 + * @return bool + */ + private function checkLogin() + { + // 验证当前请求是否在白名单 + if (in_array($this->routeUri, $this->allowAllAction)) { + return true; + } + // 验证登录状态 + if (empty($this->store) + || (int)$this->store['is_login'] !== 1 + || !isset($this->store['wxapp']) + || empty($this->store['wxapp']) + ) { + $this->redirect('passport/login'); + return false; + } + return true; + } + + /** + * 获取当前wxapp_id + */ + protected function getWxappId() + { + return $this->store['wxapp']['wxapp_id']; + } + + /** + * 返回封装后的 API 数据到客户端 + * @param int $code + * @param string $msg + * @param string $url + * @param array $data + * @return array + */ + protected function renderJson($code = 1, $msg = '', $url = '', $data = []) + { + return compact('code', 'msg', 'url', 'data'); + } + + /** + * 返回操作成功json + * @param string $msg + * @param string $url + * @param array $data + * @return array + */ + protected function renderSuccess($msg = 'success', $url = '', $data = []) + { + return $this->renderJson(1, $msg, $url, $data); + } + + /** + * 返回操作失败json + * @param string $msg + * @param string $url + * @param array $data + * @return array|bool + */ + protected function renderError($msg = 'error', $url = '', $data = []) + { + if ($this->request->isAjax()) { + return $this->renderJson(0, $msg, $url, $data); + } + $this->error($msg); + return false; + } + + /** + * 获取post数据 (数组) + * @param $key + * @return mixed + */ + protected function postData($key = null) + { + return $this->request->post(is_null($key) ? '' : $key . '/a'); + } + + /** + * 获取post数据 (数组) + * @param $key + * @return mixed + */ + protected function getData($key = null) + { + return $this->request->get(is_null($key) ? '' : $key); + } + +} diff --git a/source/application/store/controller/Goods.php b/source/application/store/controller/Goods.php new file mode 100644 index 0000000..ae74845 --- /dev/null +++ b/source/application/store/controller/Goods.php @@ -0,0 +1,127 @@ +getList(array_merge(['status' => -1], $this->request->param())); + // 商品分类 + $catgory = CategoryModel::getCacheTree(); + return $this->fetch('index', compact('list', 'catgory')); + } + + /** + * 添加商品 + * @return array|mixed + * @throws \think\exception\PDOException + */ + public function add() + { + if (!$this->request->isAjax()) { + return $this->fetch( + 'add', + array_merge(GoodsService::getEditData(null, 'add'), []) + ); + } + $model = new GoodsModel; + if ($model->add($this->postData('goods'))) { + return $this->renderSuccess('添加成功', url('goods/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 一键复制 + * @param $goods_id + * @return array|mixed + * @throws \think\exception\PDOException + */ + public function copy($goods_id) + { + // 商品详情 + $model = GoodsModel::detail($goods_id); + if (!$this->request->isAjax()) { + return $this->fetch( + 'edit', + array_merge(GoodsService::getEditData($model, 'copy'), compact('model')) + ); + } + $model = new GoodsModel; + if ($model->add($this->postData('goods'))) { + return $this->renderSuccess('添加成功', url('goods/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 商品编辑 + * @param $goods_id + * @return array|bool|mixed + */ + public function edit($goods_id) + { + // 商品详情 + $model = GoodsModel::detail($goods_id); + if (!$this->request->isAjax()) { + return $this->fetch( + 'edit', + array_merge(GoodsService::getEditData($model), compact('model')) + ); + } + // 更新记录 + if ($model->edit($this->postData('goods'))) { + return $this->renderSuccess('更新成功', url('goods/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 修改商品状态 + * @param $goods_id + * @param boolean $state + * @return array + */ + public function state($goods_id, $state) + { + // 商品详情 + $model = GoodsModel::detail($goods_id); + if (!$model->setStatus($state)) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 删除商品 + * @param $goods_id + * @return array + */ + public function delete($goods_id) + { + // 商品详情 + $model = GoodsModel::detail($goods_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/store/controller/Index.php b/source/application/store/controller/Index.php new file mode 100644 index 0000000..75f9025 --- /dev/null +++ b/source/application/store/controller/Index.php @@ -0,0 +1,32 @@ +menus(); + $url = current(array_values($menus))['index']; + if ($url !== 'index/index') { + $this->redirect($url); + } + $model = new StoreModel; + return $this->fetch('index', ['data' => $model->getHomeData()]); + } + +} diff --git a/source/application/store/controller/Order.php b/source/application/store/controller/Order.php new file mode 100644 index 0000000..c1c4123 --- /dev/null +++ b/source/application/store/controller/Order.php @@ -0,0 +1,147 @@ +getList('待发货订单列表', 'delivery'); + } + + /** + * 待收货订单列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function receipt_list() + { + return $this->getList('待收货订单列表', 'receipt'); + } + + /** + * 待付款订单列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function pay_list() + { + return $this->getList('待付款订单列表', 'pay'); + } + + /** + * 已完成订单列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function complete_list() + { + return $this->getList('已完成订单列表', 'complete'); + } + + /** + * 已取消订单列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function cancel_list() + { + return $this->getList('已取消订单列表', 'cancel'); + } + + /** + * 全部订单列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function all_list() + { + return $this->getList('全部订单列表', 'all'); + } + + /** + * 订单详情 + * @param $order_id + * @return mixed + * @throws \think\exception\DbException + */ + public function detail($order_id) + { + // 订单详情 + $detail = OrderModel::detail($order_id); + // 物流公司列表 + $expressList = ExpressModel::getAll(); + // 门店店员列表 + $shopClerkList = (new ShopClerkModel)->getList(true); + return $this->fetch('detail', compact( + 'detail', + 'expressList', + 'shopClerkList' + )); + } + + /** + * 确认发货 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function delivery($order_id) + { + $model = OrderModel::detail($order_id); + if ($model->delivery($this->postData('order'))) { + return $this->renderSuccess('发货成功'); + } + return $this->renderError($model->getError() ?: '发货失败'); + } + + /** + * 修改订单价格 + * @param $order_id + * @return array + * @throws \think\exception\DbException + */ + public function updatePrice($order_id) + { + $model = OrderModel::detail($order_id); + if ($model->updatePrice($this->postData('order'))) { + return $this->renderSuccess('修改成功'); + } + return $this->renderError($model->getError() ?: '修改失败'); + } + + /** + * 订单列表 + * @param string $title + * @param string $dataType + * @return mixed + * @throws \think\exception\DbException + */ + private function getList($title, $dataType) + { + // 订单列表 + $model = new OrderModel; + $list = $model->getList($dataType, $this->request->param()); + // 自提门店列表 + $shopList = ShopModel::getAllList(); + return $this->fetch('index', compact('title', 'dataType', 'list', 'shopList')); + } + +} diff --git a/source/application/store/controller/Passport.php b/source/application/store/controller/Passport.php new file mode 100644 index 0000000..f6c8bb9 --- /dev/null +++ b/source/application/store/controller/Passport.php @@ -0,0 +1,48 @@ +request->isAjax()) { + $model = new StoreUser; + if ($model->login($this->postData('User'))) { + return $this->renderSuccess('登录成功', url('index/index')); + } + return $this->renderError($model->getError() ?: '登录失败'); + } + $this->view->engine->layout(false); + return $this->fetch('login', [ + // 系统版本号 + 'version' => get_version() + ]); + } + + /** + * 退出登录 + */ + public function logout() + { + Session::clear('yoshop_store'); + $this->redirect('passport/login'); + } + +} diff --git a/source/application/store/controller/Setting.php b/source/application/store/controller/Setting.php new file mode 100644 index 0000000..1367206 --- /dev/null +++ b/source/application/store/controller/Setting.php @@ -0,0 +1,132 @@ +updateEvent('store'); + } + + /** + * 交易设置 + * @return mixed + * @throws \think\exception\DbException + */ + public function trade() + { + return $this->updateEvent('trade'); + } + + /** + * 短信通知 + * @return mixed + * @throws \think\exception\DbException + */ + public function sms() + { + return $this->updateEvent('sms'); + } + + /** + * 模板消息 + * @return mixed + * @throws \think\exception\DbException + */ + public function tplMsg() + { + return $this->updateEvent('tplMsg'); + } + + /** + * 发送短信通知测试 + * @param $AccessKeyId + * @param $AccessKeySecret + * @param $sign + * @param $msg_type + * @param $template_code + * @param $accept_phone + * @return array + * @throws \think\Exception + */ + public function smsTest($AccessKeyId, $AccessKeySecret, $sign, $msg_type, $template_code, $accept_phone) + { + $SmsDriver = new SmsDriver([ + 'default' => 'aliyun', + 'engine' => [ + 'aliyun' => [ + 'AccessKeyId' => $AccessKeyId, + 'AccessKeySecret' => $AccessKeySecret, + 'sign' => $sign, + $msg_type => compact('template_code', 'accept_phone'), + ], + ], + ]); + $templateParams = []; + if ($msg_type === 'order_pay') { + $templateParams = ['order_no' => '2018071200000000']; + } + if ($SmsDriver->sendSms($msg_type, $templateParams, true)) { + return $this->renderSuccess('发送成功'); + } + return $this->renderError('发送失败 ' . $SmsDriver->getError()); + } + + /** + * 上传设置 + * @return mixed + * @throws \think\exception\DbException + */ + public function storage() + { + return $this->updateEvent('storage'); + } + + /** + * 小票打印设置 + * @return mixed + * @throws \think\exception\DbException + */ + public function printer() + { + // 获取打印机列表 + $printerList = PrinterModel::getAll(); + return $this->updateEvent('printer', compact('printerList')); + } + + /** + * 更新商城设置事件 + * @param $key + * @param $vars + * @return array|mixed + * @throws \think\exception\DbException + */ + private function updateEvent($key, $vars = []) + { + if (!$this->request->isAjax()) { + $vars['values'] = SettingModel::getItem($key); + return $this->fetch($key, $vars); + } + $model = new SettingModel; + if ($model->edit($key, $this->postData($key))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + +} diff --git a/source/application/store/controller/Shop.php b/source/application/store/controller/Shop.php new file mode 100644 index 0000000..9c8410d --- /dev/null +++ b/source/application/store/controller/Shop.php @@ -0,0 +1,90 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 腾讯地图坐标选取器 + * @return mixed + */ + public function getpoint() + { + $this->view->engine->layout(false); + return $this->fetch('getpoint'); + } + + /** + * 添加门店 + * @return array|bool|mixed + * @throws \Exception + */ + public function add() + { + $model = new ShopModel; + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + if ($model->add($this->postData('shop'))) { + return $this->renderSuccess('添加成功', url('shop/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑门店 + * @param $shop_id + * @return array|bool|mixed + * @throws \think\exception\DbException + */ + public function edit($shop_id) + { + // 门店详情 + $model = ShopModel::detail($shop_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 新增记录 + if ($model->edit($this->postData('shop'))) { + return $this->renderSuccess('更新成功', url('shop/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除门店 + * @param $shop_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($shop_id) + { + // 门店详情 + $model = ShopModel::detail($shop_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/Upload.php b/source/application/store/controller/Upload.php new file mode 100644 index 0000000..e5484d8 --- /dev/null +++ b/source/application/store/controller/Upload.php @@ -0,0 +1,88 @@ +config = SettingModel::getItem('storage'); + } + + /** + * 图片上传接口 + * @param int $group_id + * @return array + * @throws \think\Exception + */ + public function image($group_id = -1) + { + // 实例化存储驱动 + $StorageDriver = new StorageDriver($this->config); + // 设置上传文件的信息 + $StorageDriver->setUploadFile('iFile'); + // 上传图片 + if (!$StorageDriver->upload()) { + return json(['code' => 0, 'msg' => '图片上传失败' . $StorageDriver->getError()]); + } + + // 图片上传路径 + $fileName = $StorageDriver->getFileName(); + // 图片信息 + $fileInfo = $StorageDriver->getFileInfo(); + // 添加文件库记录 + $uploadFile = $this->addUploadFile($group_id, $fileName, $fileInfo, 'image'); + // 图片上传成功 + return json(['code' => 1, 'msg' => '图片上传成功', 'data' => $uploadFile]); + } + + /** + * 添加文件库上传记录 + * @param $group_id + * @param $fileName + * @param $fileInfo + * @param $fileType + * @return UploadFile + */ + private function addUploadFile($group_id, $fileName, $fileInfo, $fileType) + { + // 存储引擎 + $storage = $this->config['default']; + // 存储域名 + $fileUrl = isset($this->config['engine'][$storage]['domain']) + ? $this->config['engine'][$storage]['domain'] : ''; + // 添加文件库记录 + $model = new UploadFile; + $model->add([ + 'group_id' => $group_id > 0 ? (int)$group_id : 0, + 'storage' => $storage, + 'file_url' => $fileUrl, + 'file_name' => $fileName, + 'file_size' => $fileInfo['size'], + 'file_type' => $fileType, + 'extension' => pathinfo($fileInfo['name'], PATHINFO_EXTENSION), + ]); + return $model; + } + +} diff --git a/source/application/store/controller/User.php b/source/application/store/controller/User.php new file mode 100644 index 0000000..f10cef9 --- /dev/null +++ b/source/application/store/controller/User.php @@ -0,0 +1,83 @@ +getList($nickName, $gender, $grade); + // 会员等级列表 + $gradeList = GradeModel::getUsableList(); + return $this->fetch('index', compact('list', 'gradeList')); + } + + /** + * 删除用户 + * @param $user_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($user_id) + { + // 用户详情 + $model = UserModel::detail($user_id); + if ($model->setDelete()) { + return $this->renderSuccess('删除成功'); + } + return $this->renderError($model->getError() ?: '删除失败'); + } + + /** + * 用户充值 + * @param $user_id + * @param int $source 充值类型 + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function recharge($user_id, $source) + { + // 用户详情 + $model = UserModel::detail($user_id); + if ($model->recharge($this->store['user']['user_name'], $source, $this->postData('recharge'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 修改会员等级 + * @param $user_id + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function grade($user_id) + { + // 用户详情 + $model = UserModel::detail($user_id); + if ($model->updateGrade($this->postData('grade'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + +} diff --git a/source/application/store/controller/Wxapp.php b/source/application/store/controller/Wxapp.php new file mode 100644 index 0000000..b752b99 --- /dev/null +++ b/source/application/store/controller/Wxapp.php @@ -0,0 +1,52 @@ +request->isAjax()) { + return $this->fetch('setting', compact('model')); + } + // 更新小程序设置 + if ($model->edit($this->postData('wxapp'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 导航栏设置 + * @return array|mixed + * @throws \think\exception\DbException + */ + public function tabbar() + { + $model = WxappNavbarModel::detail(); + if (!$this->request->isAjax()) { + return $this->fetch('tabbar', compact('model')); + } + $data = $this->postData('tabbar'); + if (!$model->edit($data)) { + return $this->renderError('更新失败'); + } + return $this->renderSuccess('更新成功'); + } + +} diff --git a/source/application/store/controller/apps/bargain/Active.php b/source/application/store/controller/apps/bargain/Active.php new file mode 100644 index 0000000..a7182e4 --- /dev/null +++ b/source/application/store/controller/apps/bargain/Active.php @@ -0,0 +1,86 @@ +getList($search); + return $this->fetch('index', compact('list')); + } + + /** + * 新增砍价活动 + * @return array|bool|mixed + */ + public function add() + { + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + $model = new ActiveModel; + // 新增记录 + if ($model->add($this->postData('active'))) { + return $this->renderSuccess('添加成功', url('apps.bargain.active/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 更新砍价活动 + * @param $active_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($active_id) + { + // 砍价活动详情 + $model = ActiveModel::detail($active_id); + if (!$this->request->isAjax()) { + // 获取商品详情 + $goods = GoodsModel::detail($model['goods_id']); + return $this->fetch('edit', compact('model', 'goods')); + } + // 更新记录 + if ($model->edit($this->postData('active'))) { + return $this->renderSuccess('更新成功', url('apps.bargain.active/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除砍价活动 + * @param $active_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($active_id) + { + // 砍价活动详情 + $model = ActiveModel::detail($active_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/bargain/Setting.php b/source/application/store/controller/apps/bargain/Setting.php new file mode 100644 index 0000000..815b75d --- /dev/null +++ b/source/application/store/controller/apps/bargain/Setting.php @@ -0,0 +1,33 @@ +request->isAjax()) { + $values = SettingModel::getItem('basic'); + return $this->fetch('index', compact('values')); + } + $model = new SettingModel; + if ($model->edit('basic', $this->postData('basic'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/bargain/Task.php b/source/application/store/controller/apps/bargain/Task.php new file mode 100644 index 0000000..545a258 --- /dev/null +++ b/source/application/store/controller/apps/bargain/Task.php @@ -0,0 +1,60 @@ +getList($search); + return $this->fetch('index', compact('list')); + } + + /** + * 砍价榜 + * @param $task_id + * @return mixed + * @throws \think\exception\DbException + */ + public function help($task_id) + { + $model = new TaskHelpModel; + $list = $model->getList($task_id); + return $this->fetch('help', compact('list')); + } + + /** + * 删除砍价任务 + * @param $task_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($task_id) + { + // 砍价活动详情 + $model = TaskModel::detail($task_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/dealer/Apply.php b/source/application/store/controller/apps/dealer/Apply.php new file mode 100644 index 0000000..bfbefb5 --- /dev/null +++ b/source/application/store/controller/apps/dealer/Apply.php @@ -0,0 +1,46 @@ +fetch('index', [ + 'list' => $model->getList($search), + ]); + } + + /** + * 分销商审核 + * @param $apply_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function submit($apply_id) + { + $model = ApplyModel::detail($apply_id); + if ($model->submit($this->postData('apply'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/dealer/Order.php b/source/application/store/controller/apps/dealer/Order.php new file mode 100644 index 0000000..9b6664c --- /dev/null +++ b/source/application/store/controller/apps/dealer/Order.php @@ -0,0 +1,29 @@ +getList($user_id, $is_settled); + return $this->fetch('index', compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/dealer/Setting.php b/source/application/store/controller/apps/dealer/Setting.php new file mode 100644 index 0000000..38bd1e1 --- /dev/null +++ b/source/application/store/controller/apps/dealer/Setting.php @@ -0,0 +1,59 @@ +request->isAjax()) { + $data = SettingModel::getAll(); + // 购买指定商品成为分销商:商品列表 + $goodsList = (new GoodsModel)->getListByIds($data['condition']['values']['become__buy_goods_ids']); + return $this->fetch('index', compact('data', 'goodsList')); + } + $model = new SettingModel; + if ($model->edit($this->postData('setting'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 分销海报 + * @return array|mixed + * @throws \think\exception\PDOException + */ + public function qrcode() + { + if (!$this->request->isAjax()) { + $data = SettingModel::getItem('qrcode'); + return $this->fetch('qrcode', [ + 'data' => json_encode($data, JSON_UNESCAPED_UNICODE) + ]); + } + $model = new SettingModel; + if ($model->edit(['qrcode' => $this->postData('qrcode')])) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/dealer/User.php b/source/application/store/controller/apps/dealer/User.php new file mode 100644 index 0000000..315d682 --- /dev/null +++ b/source/application/store/controller/apps/dealer/User.php @@ -0,0 +1,108 @@ +fetch('index', [ + 'list' => $model->getList($search), + 'basicSetting' => SettingModel::getItem('basic'), + ]); + } + + /** + * 分销商用户列表 + * @param string $user_id + * @param int $level + * @return mixed + * @throws \think\exception\DbException + */ + public function fans($user_id, $level = -1) + { + $model = new RefereeModel; + return $this->fetch('fans', [ + 'list' => $model->getList($user_id, $level), + 'basicSetting' => SettingModel::getItem('basic'), + ]); + } + + /** + * 编辑分销商 + * @param $dealer_id + * @return mixed + * @throws \think\exception\DbException + */ + public function edit($dealer_id) + { + $model = UserModel::detail($dealer_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + if ($model->edit($this->postData('model'))) { + return $this->renderSuccess('更新成功', url('apps.dealer.user/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除分销商 + * @param $dealer_id + * @return array|bool + * @throws \think\exception\DbException + */ + public function delete($dealer_id) + { + $model = UserModel::detail($dealer_id); + if (!$model->setDelete()) { + return $this->renderError('删除失败'); + } + return $this->renderSuccess('删除成功'); + } + + /** + * 分销商二维码 + * @param $dealer_id + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function qrcode($dealer_id) + { + $model = UserModel::detail($dealer_id); + $Qrcode = new Poster($model); + $this->redirect($Qrcode->getImage()); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/dealer/Withdraw.php b/source/application/store/controller/apps/dealer/Withdraw.php new file mode 100644 index 0000000..4aa53d4 --- /dev/null +++ b/source/application/store/controller/apps/dealer/Withdraw.php @@ -0,0 +1,79 @@ +fetch('index', [ + 'list' => $model->getList($user_id, $apply_status, $pay_type, $search) + ]); + } + + /** + * 提现审核 + * @param $id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function submit($id) + { + $model = WithdrawModel::detail($id); + if ($model->submit($this->postData('withdraw'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 确认打款 + * @param $id + * @return array + * @throws \think\exception\DbException + */ + public function money($id) + { + $model = WithdrawModel::detail($id); + if ($model->money()) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 分销商提现:微信支付企业付款 + * @param $id + * @return array|bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function wechat_pay($id) + { + $model = WithdrawModel::detail($id); + if ($model->wechatPay()) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/sharing/Active.php b/source/application/store/controller/apps/sharing/Active.php new file mode 100644 index 0000000..ff167c4 --- /dev/null +++ b/source/application/store/controller/apps/sharing/Active.php @@ -0,0 +1,42 @@ +getList($active_id); + return $this->fetch('index', compact('list')); + } + + /** + * + * @param $active_id + * @return mixed + * @throws \think\exception\DbException + */ + public function users($active_id) + { + $model = new ActiveUsersModel; + $list = $model->getList($active_id); + return $this->fetch('users', compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/sharing/Category.php b/source/application/store/controller/apps/sharing/Category.php new file mode 100644 index 0000000..dadaa6a --- /dev/null +++ b/source/application/store/controller/apps/sharing/Category.php @@ -0,0 +1,82 @@ +getCacheTree(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加商品分类 + * @return array|mixed + */ + public function add() + { + $model = new CategoryModel; + if (!$this->request->isAjax()) { + // 获取所有地区 + $list = $model->getCacheTree(); + return $this->fetch('add', compact('list')); + } + // 新增记录 + if ($model->add($this->postData('category'))) { + return $this->renderSuccess('添加成功', url('apps.sharing.category/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑商品分类 + * @param $category_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($category_id) + { + // 模板详情 + $model = CategoryModel::detail($category_id); + if (!$this->request->isAjax()) { + // 获取所有地区 + $list = $model->getCacheTree(); + return $this->fetch('edit', compact('model', 'list')); + } + // 更新记录 + if ($model->edit($this->postData('category'))) { + return $this->renderSuccess('更新成功', url('apps.sharing.category/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除商品分类 + * @param $category_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($category_id) + { + $model = CategoryModel::detail($category_id); + if (!$model->remove($category_id)) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/store/controller/apps/sharing/Comment.php b/source/application/store/controller/apps/sharing/Comment.php new file mode 100644 index 0000000..0e5fa7a --- /dev/null +++ b/source/application/store/controller/apps/sharing/Comment.php @@ -0,0 +1,62 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 评价详情 + * @param $comment_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function detail($comment_id) + { + // 评价详情 + $model = CommentModel::detail($comment_id); + if (!$this->request->isAjax()) { + return $this->fetch('detail', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('comment'))) { + return $this->renderSuccess('更新成功', url('apps.sharing.comment/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除评价 + * @param $comment_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($comment_id) + { + $model = CommentModel::get($comment_id); + if (!$model->setDelete()) { + return $this->renderError('删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/sharing/Goods.php b/source/application/store/controller/apps/sharing/Goods.php new file mode 100644 index 0000000..bfe7a33 --- /dev/null +++ b/source/application/store/controller/apps/sharing/Goods.php @@ -0,0 +1,184 @@ +getList(array_merge(['status' => -1], $this->request->param())); + // 商品分类 + $catgory = CategoryModel::getCacheTree(); + return $this->fetch('index', compact('list', 'catgory')); + } + + /** + * 添加商品 + * @return array|mixed + * @throws \think\exception\PDOException + */ + public function add() + { + if (!$this->request->isAjax()) { + // 商品分类 + $catgory = CategoryModel::getCacheTree(); + // 配送模板 + $delivery = DeliveryModel::getAll(); + // 会员等级列表 + $gradeList = GradeModel::getUsableList(); + return $this->fetch('add', compact('catgory', 'delivery', 'gradeList')); + } + $model = new GoodsModel; + if ($model->add($this->postData('goods'))) { + return $this->renderSuccess('添加成功', url('apps.sharing.goods/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 复制主商城商品 + * @param $goods_id + * @return array|mixed + * @throws \think\exception\PDOException + */ + public function copy_master($goods_id) + { + // 商品详情 + $model = \app\store\model\Goods::detail($goods_id); + if (!$model || $model['is_delete']) { + return $this->renderError('商品信息不存在'); + } + if (!$this->request->isAjax()) { + // 商品分类 + $catgory = CategoryModel::getCacheTree(); + // 配送模板 + $delivery = DeliveryModel::getAll(); + // 商品sku数据 + $specData = 'null'; + if ($model['spec_type'] == 20) { + $specData = json_encode($model->getManySpecData($model['spec_rel'], $model['sku']), JSON_UNESCAPED_SLASHES); + } + // 会员等级列表 + $gradeList = GradeModel::getUsableList(); + return $this->fetch('copy_master', compact('model', 'catgory', 'delivery', 'specData', 'gradeList')); + } + // 新增拼团商品 + $model = new GoodsModel; + if ($model->add($this->postData('goods'))) { + return $this->renderSuccess('添加成功', url('apps.sharing.goods/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 一键复制 + * @param $goods_id + * @return array|mixed + * @throws \think\exception\PDOException + */ + public function copy($goods_id) + { + // 商品详情 + $model = GoodsModel::detail($goods_id); + if (!$this->request->isAjax()) { + // 商品分类 + $catgory = CategoryModel::getCacheTree(); + // 配送模板 + $delivery = DeliveryModel::getAll(); + // 商品sku数据 + $specData = 'null'; + if ($model['spec_type'] == 20) { + $specData = json_encode($model->getManySpecData($model['spec_rel'], $model['sku']), JSON_UNESCAPED_SLASHES); + } + // 会员等级列表 + $gradeList = GradeModel::getUsableList(); + return $this->fetch('edit', compact('model', 'catgory', 'delivery', 'specData', 'gradeList')); + } + $model = new GoodsModel; + if ($model->add($this->postData('goods'))) { + return $this->renderSuccess('添加成功', url('apps.sharing.goods/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 商品编辑 + * @param $goods_id + * @return array|mixed + * @throws \think\exception\PDOException + */ + public function edit($goods_id) + { + // 商品详情 + $model = GoodsModel::detail($goods_id); + if (!$this->request->isAjax()) { + // 商品分类 + $catgory = CategoryModel::getCacheTree(); + // 配送模板 + $delivery = DeliveryModel::getAll(); + // 商品sku数据 + $specData = 'null'; + if ($model['spec_type'] == 20) { + $specData = json_encode($model->getManySpecData($model['spec_rel'], $model['sku']), JSON_UNESCAPED_SLASHES); + } + // 会员等级列表 + $gradeList = GradeModel::getUsableList(); + return $this->fetch('edit', compact('model', 'catgory', 'delivery', 'specData', 'gradeList')); + } + // 更新记录 + if ($model->edit($this->postData('goods'))) { + return $this->renderSuccess('更新成功', url('apps.sharing.goods/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 修改商品状态 + * @param $goods_id + * @param boolean $state + * @return array + */ + public function state($goods_id, $state) + { + // 商品详情 + $model = GoodsModel::detail($goods_id); + if (!$model->setStatus($state)) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 删除商品 + * @param $goods_id + * @return array + */ + public function delete($goods_id) + { + // 商品详情 + $model = GoodsModel::detail($goods_id); + if (!$model->setDelete()) { + return $this->renderError('删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/store/controller/apps/sharing/Order.php b/source/application/store/controller/apps/sharing/Order.php new file mode 100644 index 0000000..8b06509 --- /dev/null +++ b/source/application/store/controller/apps/sharing/Order.php @@ -0,0 +1,87 @@ +getList($dataType, $this->request->param()); + // 自提门店列表 + $shopList = ShopModel::getAllList(); + return $this->fetch('index', compact('dataType', 'list', 'shopList')); + } + + /** + * 订单详情 + * @param $order_id + * @return mixed + * @throws \think\exception\DbException + */ + public function detail($order_id) + { + // 订单详情 + $detail = OrderModel::detail($order_id); + // 物流公司列表 + $expressList = ExpressModel::getAll(); + // 门店店员列表 + $shopClerkList = (new ShopClerkModel)->getList(true); + return $this->fetch('detail', compact( + 'detail', + 'expressList', + 'shopClerkList' + )); + } + + /** + * 确认发货 + * @param $order_id + * @return array + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function delivery($order_id) + { + $model = OrderModel::detail($order_id); + if ($model->delivery($this->postData('order'))) { + return $this->renderSuccess('发货成功'); + } + return $this->renderError($model->getError() ?: '发货失败'); + } + + /** + * 修改订单价格 + * @param $order_id + * @return array + * @throws \think\exception\DbException + */ + public function updatePrice($order_id) + { + $model = OrderModel::detail($order_id); + if ($model->updatePrice($this->postData('order'))) { + return $this->renderSuccess('修改成功'); + } + return $this->renderError($model->getError() ?: '修改失败'); + } + +} diff --git a/source/application/store/controller/apps/sharing/Setting.php b/source/application/store/controller/apps/sharing/Setting.php new file mode 100644 index 0000000..8a8f94f --- /dev/null +++ b/source/application/store/controller/apps/sharing/Setting.php @@ -0,0 +1,33 @@ +request->isAjax()) { + $values = SettingModel::getItem('basic'); + return $this->fetch('index', compact('values')); + } + $model = new SettingModel; + if ($model->edit('basic', $this->postData('basic'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/sharing/order/Operate.php b/source/application/store/controller/apps/sharing/order/Operate.php new file mode 100644 index 0000000..640aaa5 --- /dev/null +++ b/source/application/store/controller/apps/sharing/order/Operate.php @@ -0,0 +1,118 @@ +model = new OrderModel; + } + + /** + * 订单导出 + * @param string $dataType + * @throws \think\exception\DbException + */ + public function export($dataType) + { + return $this->model->exportList($dataType, $this->request->param()); + } + + /** + * 批量发货 + * @return array|bool|mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function batchDelivery() + { + if (!$this->request->isAjax()) { + return $this->fetch('batchDelivery', [ + 'express_list' => ExpressModel::getAll() + ]); + } + if ($this->model->batchDelivery($this->postData('order'))) { + return $this->renderSuccess('发货成功'); + } + return $this->renderError($this->model->getError() ?: '发货失败'); + } + +// /** +// * 批量发货模板 +// */ +// public function deliveryTpl() +// { +// return $this->model->deliveryTpl(); +// } + + /** + * 审核:用户取消订单 + * @param $order_id + * @return array|bool + * @throws \think\exception\DbException + */ + public function confirmCancel($order_id) + { + $model = OrderModel::detail($order_id); + if ($model->confirmCancel($this->postData('order'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 拼团失败手动退款 + * @param $order_id + * @return array|bool + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function refund($order_id) + { + $model = OrderModel::detail($order_id); + if ($model->refund()) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 门店自提核销 + * @param $order_id + * @return array|bool + * @throws \think\exception\DbException + */ + public function extract($order_id) + { + $model = OrderModel::detail($order_id); + $data = $this->postData('order'); + if ($model->verificationOrder($data['extract_clerk_id'])) { + return $this->renderSuccess('核销成功'); + } + return $this->renderError($model->getError() ?: '核销失败'); + } + +} diff --git a/source/application/store/controller/apps/sharing/order/Refund.php b/source/application/store/controller/apps/sharing/order/Refund.php new file mode 100644 index 0000000..00addc1 --- /dev/null +++ b/source/application/store/controller/apps/sharing/order/Refund.php @@ -0,0 +1,82 @@ +getList($this->getData()); + return $this->fetch('index', compact('list')); + } + + /** + * 售后单详情 + * @param $order_refund_id + * @return mixed + * @throws \think\exception\DbException + */ + public function detail($order_refund_id) + { + // 售后单详情 + $detail = OrderRefundModel::detail($order_refund_id); + // 订单详情 + $order = OrderModel::detail($detail['order_id']); + // 退货地址 + $address = (new ReturnAddressModel)->getAll(); + return $this->fetch('detail', compact('detail', 'order', 'address')); + } + + /** + * 商家审核 + * @param $order_refund_id + * @return array|bool + * @throws \think\exception\DbException + */ + public function audit($order_refund_id) + { + if (!$this->request->isAjax()) { + return false; + } + $model = OrderRefundModel::detail($order_refund_id); + if ($model->audit($this->postData('refund'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 确认收货并退款 + * @param $order_refund_id + * @return array|bool + * @throws \think\exception\DbException + */ + public function receipt($order_refund_id) + { + if (!$this->request->isAjax()) { + return false; + } + $model = OrderRefundModel::detail($order_refund_id); + if ($model->receipt($this->postData('refund'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/sharp/Active.php b/source/application/store/controller/apps/sharp/Active.php new file mode 100644 index 0000000..7434c8b --- /dev/null +++ b/source/application/store/controller/apps/sharp/Active.php @@ -0,0 +1,78 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 新增活动会场 + * @return array|bool|mixed + * @throws \Exception + */ + public function add() + { + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + $model = new ActiveModel; + // 新增记录 + if ($model->add($this->postData('active'))) { + return $this->renderSuccess('添加成功', url('apps.sharp.active/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 修改活动状态 + * @param $active_id + * @param $state + * @return array|bool + * @throws \think\exception\DbException + */ + public function state($active_id, $state) + { + // 活动详情 + $model = ActiveModel::detail($active_id); + if (!$model->setStatus($state)) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 删除活动会场 + * @param $active_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($active_id) + { + // 活动会场详情 + $model = ActiveModel::detail($active_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/sharp/ActiveTime.php b/source/application/store/controller/apps/sharp/ActiveTime.php new file mode 100644 index 0000000..cafa791 --- /dev/null +++ b/source/application/store/controller/apps/sharp/ActiveTime.php @@ -0,0 +1,120 @@ +getList($active_id); + return $this->fetch('index', compact('list')); + } + + /** + * 新增活动会场 + * @param $active_id + * @return array|bool|mixed + * @throws \think\exception\DbException + */ + + /** + * 新增活动会场 + * @param $active_id + * @return array|bool|mixed + * @throws \think\exception\DbException + */ + public function add($active_id) + { + // 活动详情 + $active = ActiveModel::detail($active_id); + // 已存在的场次 + $model = new ActiveTimeModel; + $existTimes = $model->getActiveTimeData($active_id); + if (!$this->request->isAjax()) { + return $this->fetch('add', compact('active', 'existTimes')); + } + // 新增记录 + if ($model->add($active_id, $this->postData('active'))) { + $url = url('apps.sharp.active_time/index', ['active_id' => $active_id]); + return $this->renderSuccess('添加成功', $url); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑活动会场 + * @param $id + * @return array|bool|mixed + * @throws \think\exception\DbException + * @throws \Exception + */ + public function edit($id) + { + // 场次详情 + $model = ActiveTimeModel::detail($id, ['active']); + // 当前场次关联的商品 + $goodsList = $model->getGoodsListByActiveTimeId($id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model', 'goodsList')); + } + // 新增记录 + if ($model->edit($this->postData('active'))) { + $url = url('apps.sharp.active_time/index', ['active_id' => $model['active_id']]); + return $this->renderSuccess('更新成功', $url); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 修改场次状态 + * @param $id + * @param $state + * @return array|bool + * @throws \think\exception\DbException + */ + public function state($id, $state) + { + // 场次详情 + $model = ActiveTimeModel::detail($id); + if (!$model->setStatus($state)) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 删除活动场次 + * @param $id + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function delete($id) + { + // 场次详情 + $model = ActiveTimeModel::detail($id); + if (!$model->onDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/sharp/Goods.php b/source/application/store/controller/apps/sharp/Goods.php new file mode 100644 index 0000000..855e108 --- /dev/null +++ b/source/application/store/controller/apps/sharp/Goods.php @@ -0,0 +1,124 @@ +getList($search); + return $this->fetch('index', compact('list')); + } + + /** + * 添加秒杀商品 + * @param int $step + * @param null $goods_id + * @return array|bool|mixed + * @throws \Exception + */ + public function add($step = 1, $goods_id = null) + { + if ($step == 2) { + return $this->step2($goods_id); + } + return $this->step1(); + } + + /** + * 添加秒杀商品:步骤1 + * @return mixed + */ + private function step1() + { + return $this->fetch('step1'); + } + + /** + * 添加秒杀商品:步骤2 + * @param $goodsId + * @return array|bool|mixed + * @throws \Exception + */ + private function step2($goodsId) + { + $model = new SharpGoodsModel; + // 验证商品ID能否被添加 + if (!$model->validateGoodsId($goodsId)) { + $this->renderError($model->getError()); + } + // 商品信息 + $goods = GoodsModel::detail($goodsId); + $specData = GoodsService::getSpecData($goods); + // 填写商品信息页面 + if (!$this->request->isAjax()) { + return $this->fetch('step2', compact('goods', 'specData')); + } + // 表单提交 + if ($model->add($goods, $this->postData('goods'))) { + return $this->renderSuccess('添加成功', url('apps.sharp.goods/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑秒杀商品 + * @param $sharp_goods_id + * @return array|bool|mixed + * @throws \think\exception\DbException + * @throws \Exception + */ + public function edit($sharp_goods_id) + { + // 秒杀商品详情 + $model = SharpGoodsModel::detail($sharp_goods_id, ['sku']); + // 商品信息 + $goods = GoodsModel::detail($model['goods_id']); + // 商品多规格信息 + $specData = $model->getSpecData($goods, $model['sku']); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model', 'goods', 'specData')); + } + // 更新记录 + if ($model->edit($goods, $this->postData('goods'))) { + return $this->renderSuccess('更新成功', url('apps.sharp.goods/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除秒杀商品 + * @param $sharp_goods_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($sharp_goods_id) + { + // 秒杀商品详情 + $model = SharpGoodsModel::detail($sharp_goods_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/sharp/Setting.php b/source/application/store/controller/apps/sharp/Setting.php new file mode 100644 index 0000000..8c506a1 --- /dev/null +++ b/source/application/store/controller/apps/sharp/Setting.php @@ -0,0 +1,33 @@ +request->isAjax()) { + $values = SettingModel::getItem('basic'); + return $this->fetch('index', compact('values')); + } + $model = new SettingModel; + if ($model->edit('basic', $this->postData('basic'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/wow/Order.php b/source/application/store/controller/apps/wow/Order.php new file mode 100644 index 0000000..91c85ed --- /dev/null +++ b/source/application/store/controller/apps/wow/Order.php @@ -0,0 +1,44 @@ +getList($search); + return $this->fetch('index', compact('list')); + } + + /** + * 取消同步 + * @param $id + * @return array|bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function delete($id) + { + // 删除微信好物圈收藏 + $WechatWow = new WowOrderService($this->getWxappId()); + $WechatWow->delete($id); + return $this->renderSuccess('操作成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/wow/Setting.php b/source/application/store/controller/apps/wow/Setting.php new file mode 100644 index 0000000..5623f67 --- /dev/null +++ b/source/application/store/controller/apps/wow/Setting.php @@ -0,0 +1,33 @@ +request->isAjax()) { + $values = SettingModel::getItem('basic'); + return $this->fetch('index', compact('values')); + } + $model = new SettingModel; + if ($model->edit('basic', $this->postData('basic'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/apps/wow/Shoping.php b/source/application/store/controller/apps/wow/Shoping.php new file mode 100644 index 0000000..f147db0 --- /dev/null +++ b/source/application/store/controller/apps/wow/Shoping.php @@ -0,0 +1,44 @@ +getList($search); + return $this->fetch('index', compact('list')); + } + + /** + * 取消同步 + * @param $id + * @return array|bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function delete($id) + { + // 删除微信好物圈收藏 + $WechatWow = new WowService($this->getWxappId()); + $WechatWow->delete($id); + return $this->renderSuccess('操作成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/content/Article.php b/source/application/store/controller/content/Article.php new file mode 100644 index 0000000..425d280 --- /dev/null +++ b/source/application/store/controller/content/Article.php @@ -0,0 +1,85 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加文章 + * @return array|mixed + */ + public function add() + { + $model = new ArticleModel; + if (!$this->request->isAjax()) { + // 文章分类 + $catgory = CategoryModel::getAll(); + return $this->fetch('add', compact('catgory')); + } + // 新增记录 + if ($model->add($this->postData('article'))) { + return $this->renderSuccess('添加成功', url('content.article/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 更新文章 + * @param $article_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($article_id) + { + // 文章详情 + $model = ArticleModel::detail($article_id); + if (!$this->request->isAjax()) { + // 文章分类 + $catgory = CategoryModel::getAll(); + return $this->fetch('edit', compact('model', 'catgory')); + } + // 更新记录 + if ($model->edit($this->postData('article'))) { + return $this->renderSuccess('更新成功', url('content.article/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除文章 + * @param $article_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($article_id) + { + // 文章详情 + $model = ArticleModel::detail($article_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/content/Files.php b/source/application/store/controller/content/Files.php new file mode 100644 index 0000000..e5a472b --- /dev/null +++ b/source/application/store/controller/content/Files.php @@ -0,0 +1,88 @@ +getList(-1, '', 0); + return $this->fetch('index', compact('list')); + } + + /** + * 回收站列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function recycle() + { + $model = new UploadFileModel; + $list = $model->getList(-1, '', 1); + return $this->fetch('recycle', compact('list')); + } + + /** + * 移入回收站 + * @param $file_id + * @return array + * @throws \think\exception\DbException + */ + public function recovery($file_id) + { + // 文章详情 + $model = UploadFileModel::detail($file_id); + if (!$model->setRecycle(true)) { + return $this->renderError($model->getError() ?: '操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 移出回收站 + * @param $file_id + * @return array + * @throws \think\exception\DbException + */ + public function restore($file_id) + { + // 商品详情 + $model = UploadFileModel::detail($file_id); + if (!$model->setRecycle(false)) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + + /** + * 删除文件 + * @param $file_id + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function delete($file_id) + { + // 商品详情 + $model = UploadFileModel::detail($file_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '操作失败'); + } + return $this->renderSuccess('操作成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/content/article/Category.php b/source/application/store/controller/content/article/Category.php new file mode 100644 index 0000000..9e6ca07 --- /dev/null +++ b/source/application/store/controller/content/article/Category.php @@ -0,0 +1,78 @@ +getAll(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加文章分类 + * @return array|mixed + */ + public function add() + { + $model = new CategoryModel; + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + if ($model->add($this->postData('category'))) { + return $this->renderSuccess('添加成功', url('content.article.category/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑文章分类 + * @param $category_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($category_id) + { + // 分类详情 + $model = CategoryModel::detail($category_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('category'))) { + return $this->renderSuccess('更新成功', url('content.article.category/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除文章分类 + * @param $category_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($category_id) + { + $model = CategoryModel::detail($category_id); + if (!$model->remove($category_id)) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/store/controller/content/files/Group.php b/source/application/store/controller/content/files/Group.php new file mode 100644 index 0000000..1fff87c --- /dev/null +++ b/source/application/store/controller/content/files/Group.php @@ -0,0 +1,81 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加文件分组 + * @return array|mixed + */ + public function add() + { + $model = new GroupModel; + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + if ($model->add($this->postData('group'))) { + return $this->renderSuccess('添加成功', url('content.files.group/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑文件分组 + * @param $group_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($group_id) + { + // 分组详情 + $model = GroupModel::detail($group_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('group'))) { + return $this->renderSuccess('更新成功', url('content.files.group/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除文件分组 + * @param $group_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($group_id) + { + $model = GroupModel::detail($group_id); + if (!$model->remove()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/store/controller/data/Goods.php b/source/application/store/controller/data/Goods.php new file mode 100644 index 0000000..09f83e4 --- /dev/null +++ b/source/application/store/controller/data/Goods.php @@ -0,0 +1,47 @@ +model = new GoodsModel; + $this->view->engine->layout(false); + } + + /** + * 商品列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function lists() + { + // 商品分类 + $catgory = CategoryModel::getCacheTree(); + // 商品列表 + $list = $this->model->getList($this->request->param()); + return $this->fetch('list', compact('list', 'catgory')); + } + +} diff --git a/source/application/store/controller/data/Shop.php b/source/application/store/controller/data/Shop.php new file mode 100644 index 0000000..83eaa15 --- /dev/null +++ b/source/application/store/controller/data/Shop.php @@ -0,0 +1,39 @@ +model = new ShopModel; + $this->view->engine->layout(false); + } + + /** + * 门店列表 + * @param null $status + * @return mixed + * @throws \think\exception\DbException + */ + public function lists($status = null) + { + $list = $this->model->getList($status); + return $this->fetch('list', compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/data/User.php b/source/application/store/controller/data/User.php new file mode 100644 index 0000000..0554db4 --- /dev/null +++ b/source/application/store/controller/data/User.php @@ -0,0 +1,50 @@ +model = new UserModel; + $this->view->engine->layout(false); + } + + /** + * 用户列表 + * @return mixed + * @param string $nickName 昵称 + * @param int $gender 性别 + * @param int $grade 会员等级 + * @throws \think\exception\DbException + */ + public function lists($nickName = '', $gender = null, $grade = null) + { + // 会员等级列表 + $gradeList = GradeModel::getUsableList(); + // 用户列表 + $list = $this->model->getList($nickName, $gender, $grade); + return $this->fetch('list', compact('list', 'gradeList')); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/data/bargain/Goods.php b/source/application/store/controller/data/bargain/Goods.php new file mode 100644 index 0000000..5c6cd8d --- /dev/null +++ b/source/application/store/controller/data/bargain/Goods.php @@ -0,0 +1,43 @@ +view->engine->layout(false); + } + + /** + * 商品列表 + * @param string $search + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function lists($search = '') + { + $model = new ActiveModel; + $list = $model->getList($search); + return $this->fetch('list', compact('list')); + } + +} diff --git a/source/application/store/controller/data/sharing/Goods.php b/source/application/store/controller/data/sharing/Goods.php new file mode 100644 index 0000000..be07ac8 --- /dev/null +++ b/source/application/store/controller/data/sharing/Goods.php @@ -0,0 +1,47 @@ +model = new GoodsModel; + $this->view->engine->layout(false); + } + + /** + * 商品列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function lists() + { + // 商品分类 + $catgory = CategoryModel::getCacheTree(); + // 商品列表 + $list = $this->model->getList($this->request->param()); + return $this->fetch('list', compact('list', 'catgory')); + } + +} diff --git a/source/application/store/controller/data/sharp/Goods.php b/source/application/store/controller/data/sharp/Goods.php new file mode 100644 index 0000000..39f4954 --- /dev/null +++ b/source/application/store/controller/data/sharp/Goods.php @@ -0,0 +1,43 @@ +view->engine->layout(false); + } + + /** + * 商品列表 + * @param string $search + * @return mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function lists($search = '') + { + $model = new GoodsModel; + $list = $model->getList($search); + return $this->fetch('list', compact('list')); + } + +} diff --git a/source/application/store/controller/goods/Category.php b/source/application/store/controller/goods/Category.php new file mode 100644 index 0000000..2ceb76d --- /dev/null +++ b/source/application/store/controller/goods/Category.php @@ -0,0 +1,83 @@ +getCacheTree(); + return $this->fetch('index', compact('list')); + } + + /** + * 删除商品分类 + * @param $category_id + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function delete($category_id) + { + $model = CategoryModel::get($category_id); + if (!$model->remove($category_id)) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + + /** + * 添加商品分类 + * @return array|mixed + */ + public function add() + { + $model = new CategoryModel; + if (!$this->request->isAjax()) { + // 获取所有地区 + $list = $model->getCacheTree(); + return $this->fetch('add', compact('list')); + } + // 新增记录 + if ($model->add($this->postData('category'))) { + return $this->renderSuccess('添加成功', url('goods.category/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑商品分类 + * @param $category_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($category_id) + { + // 模板详情 + $model = CategoryModel::get($category_id, ['image']); + if (!$this->request->isAjax()) { + // 获取所有地区 + $list = $model->getCacheTree(); + return $this->fetch('edit', compact('model', 'list')); + } + // 更新记录 + if ($model->edit($this->postData('category'))) { + return $this->renderSuccess('更新成功', url('goods.category/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + +} diff --git a/source/application/store/controller/goods/Comment.php b/source/application/store/controller/goods/Comment.php new file mode 100644 index 0000000..2f9cf09 --- /dev/null +++ b/source/application/store/controller/goods/Comment.php @@ -0,0 +1,62 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 评价详情 + * @param $comment_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function detail($comment_id) + { + // 评价详情 + $model = CommentModel::detail($comment_id); + if (!$this->request->isAjax()) { + return $this->fetch('detail', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('comment'))) { + return $this->renderSuccess('更新成功', url('goods.comment/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除评价 + * @param $comment_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($comment_id) + { + $model = CommentModel::get($comment_id); + if (!$model->setDelete()) { + return $this->renderError('删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/goods/Spec.php b/source/application/store/controller/goods/Spec.php new file mode 100644 index 0000000..874ff45 --- /dev/null +++ b/source/application/store/controller/goods/Spec.php @@ -0,0 +1,93 @@ +SpecModel = new SpecModel; + $this->SpecValueModel = new SpecValueModel; + } + + /** + * 添加规则组 + * @param $spec_name + * @param $spec_value + * @return array + */ + public function addSpec($spec_name, $spec_value) + { + // 判断规格组是否存在 + if (!$specId = $this->SpecModel->getSpecIdByName($spec_name)) { + // 新增规格组and规则值 + if ($this->SpecModel->add($spec_name) + && $this->SpecValueModel->add($this->SpecModel['spec_id'], $spec_value)) + return $this->renderSuccess('', '', [ + 'spec_id' => (int)$this->SpecModel['spec_id'], + 'spec_value_id' => (int)$this->SpecValueModel['spec_value_id'], + ]); + return $this->renderError(); + } + // 判断规格值是否存在 + if ($specValueId = $this->SpecValueModel->getSpecValueIdByName($specId, $spec_value)) { + return $this->renderSuccess('', '', [ + 'spec_id' => (int)$specId, + 'spec_value_id' => (int)$specValueId, + ]); + } + // 添加规则值 + if ($this->SpecValueModel->add($specId, $spec_value)) + return $this->renderSuccess('', '', [ + 'spec_id' => (int)$specId, + 'spec_value_id' => (int)$this->SpecValueModel['spec_value_id'], + ]); + return $this->renderError(); + } + + /** + * 添加规格值 + * @param $spec_id + * @param $spec_value + * @return array + */ + public function addSpecValue($spec_id, $spec_value) + { + // 判断规格值是否存在 + if ($specValueId = $this->SpecValueModel->getSpecValueIdByName($spec_id, $spec_value)) { + return $this->renderSuccess('', '', [ + 'spec_value_id' => (int)$specValueId, + ]); + } + // 添加规则值 + if ($this->SpecValueModel->add($spec_id, $spec_value)) + return $this->renderSuccess('', '', [ + 'spec_value_id' => (int)$this->SpecValueModel['spec_value_id'], + ]); + return $this->renderError(); + } + +} diff --git a/source/application/store/controller/market/Basic.php b/source/application/store/controller/market/Basic.php new file mode 100644 index 0000000..82c5dd6 --- /dev/null +++ b/source/application/store/controller/market/Basic.php @@ -0,0 +1,39 @@ +request->isAjax()) { + $values = SettingModel::getItem('full_free'); + return $this->fetch('full_free', [ + 'goodsList' => (new Goods)->getListByIds($values['notin_goods']), + 'regionData' => RegionModel::getCacheTree(), // 所有地区 + 'values' => $values + ]); + } + $model = new SettingModel; + if ($model->edit('full_free', $this->postData('model'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/market/Coupon.php b/source/application/store/controller/market/Coupon.php new file mode 100644 index 0000000..bd3ac1c --- /dev/null +++ b/source/application/store/controller/market/Coupon.php @@ -0,0 +1,108 @@ +model = new CouponModel; + } + + /** + * 优惠券列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function index() + { + $list = $this->model->getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加优惠券 + * @return array|mixed + */ + public function add() + { + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + if ($this->model->add($this->postData('coupon'))) { + return $this->renderSuccess('添加成功', url('market.coupon/index')); + } + return $this->renderError($this->model->getError() ?: '添加失败'); + } + + /** + * 更新优惠券 + * @param $coupon_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($coupon_id) + { + // 优惠券详情 + $model = CouponModel::detail($coupon_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('coupon'))) { + return $this->renderSuccess('更新成功', url('market.coupon/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除优惠券 + * @param $coupon_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function delete($coupon_id) + { + // 优惠券详情 + $model = CouponModel::detail($coupon_id); + // 更新记录 + if ($model->setDelete()) { + return $this->renderSuccess('删除成功', url('market.coupon/index')); + } + return $this->renderError($model->getError() ?: '删除成功'); + } + + /** + * 领取记录 + * @return mixed + * @throws \think\exception\DbException + */ + public function receive() + { + $model = new UserCouponModel; + $list = $model->getList(); + return $this->fetch('receive', compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/market/Points.php b/source/application/store/controller/market/Points.php new file mode 100644 index 0000000..236a510 --- /dev/null +++ b/source/application/store/controller/market/Points.php @@ -0,0 +1,47 @@ +request->isAjax()) { + $values = SettingModel::getItem('points'); + return $this->fetch('setting', compact('values')); + } + $model = new SettingModel; + if ($model->edit('points', $this->postData('points'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 积分明细 + * @return mixed + * @throws \think\exception\DbException + */ + public function log() + { + // 积分明细列表 + $model = new PointsLogModel; + $list = $model->getList($this->request->param()); + return $this->fetch('log', compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/market/Push.php b/source/application/store/controller/market/Push.php new file mode 100644 index 0000000..896efc3 --- /dev/null +++ b/source/application/store/controller/market/Push.php @@ -0,0 +1,46 @@ +request->isAjax()) { + return $this->fetch('send'); + } + // 执行发送 + $MessageService = new MessageService; + $MessageService->send($this->postData('send')); + return $this->renderSuccess('', '', [ + 'stateSet' => $MessageService->getStateSet() + ]); + } + + /** + * 活跃用户列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function user() + { + $list = (new FormidModel)->getUserList(); + return $this->fetch('user', compact('list')); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/market/Recharge.php b/source/application/store/controller/market/Recharge.php new file mode 100644 index 0000000..59c3ba5 --- /dev/null +++ b/source/application/store/controller/market/Recharge.php @@ -0,0 +1,28 @@ +request->isAjax()) { + $values = SettingModel::getItem('recharge'); + return $this->fetch('setting', ['values' => $values]); + } + $model = new SettingModel; + if ($model->edit('recharge', $this->postData('recharge'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/market/recharge/Plan.php b/source/application/store/controller/market/recharge/Plan.php new file mode 100644 index 0000000..d9a61f6 --- /dev/null +++ b/source/application/store/controller/market/recharge/Plan.php @@ -0,0 +1,95 @@ +model = new PlanModel; + } + + /** + * 充值套餐列表 + * @return mixed + * @throws \think\exception\DbException + */ + public function index() + { + $list = $this->model->getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加充值套餐 + * @return array|mixed + */ + public function add() + { + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + if ($this->model->add($this->postData('plan'))) { + return $this->renderSuccess('添加成功', url('market.recharge.plan/index')); + } + return $this->renderError($this->model->getError() ?: '添加失败'); + } + + /** + * 更新充值套餐 + * @param $plan_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($plan_id) + { + // 充值套餐详情 + $model = PlanModel::detail($plan_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('plan'))) { + return $this->renderSuccess('更新成功', url('market.recharge.plan/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除充值套餐 + * @param $plan_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function delete($plan_id) + { + // 套餐详情 + $model = PlanModel::detail($plan_id); + // 更新记录 + if ($model->setDelete()) { + return $this->renderSuccess('删除成功', url('market.recharge.plan/index')); + } + return $this->renderError($model->getError() ?: '删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/order/Operate.php b/source/application/store/controller/order/Operate.php new file mode 100644 index 0000000..773490a --- /dev/null +++ b/source/application/store/controller/order/Operate.php @@ -0,0 +1,102 @@ +model = new OrderModel; + } + + /** + * 订单导出 + * @param string $dataType + * @throws \think\exception\DbException + */ + public function export($dataType) + { + return $this->model->exportList($dataType, $this->request->param()); + } + + /** + * 批量发货 + * @return array|bool|mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function batchDelivery() + { + if (!$this->request->isAjax()) { + return $this->fetch('batchDelivery', [ + 'express_list' => ExpressModel::getAll() + ]); + } + if ($this->model->batchDelivery($this->postData('order'))) { + return $this->renderSuccess('发货成功'); + } + return $this->renderError($this->model->getError() ?: '发货失败'); + } + + /** + * 批量发货模板 + */ + public function deliveryTpl() + { + return $this->model->deliveryTpl(); + } + + /** + * 审核:用户取消订单 + * @param $order_id + * @return array|bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function confirmCancel($order_id) + { + $model = OrderModel::detail($order_id); + if ($model->confirmCancel($this->postData('order'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 门店自提核销 + * @param $order_id + * @return array|bool + * @throws \think\exception\DbException + */ + public function extract($order_id) + { + $model = OrderModel::detail($order_id); + $data = $this->postData('order'); + if ($model->verificationOrder($data['extract_clerk_id'])) { + return $this->renderSuccess('核销成功'); + } + return $this->renderError($model->getError() ?: '核销失败'); + } + +} diff --git a/source/application/store/controller/order/Refund.php b/source/application/store/controller/order/Refund.php new file mode 100644 index 0000000..83ef7d1 --- /dev/null +++ b/source/application/store/controller/order/Refund.php @@ -0,0 +1,82 @@ +getList($this->getData()); + return $this->fetch('index', compact('list')); + } + + /** + * 售后单详情 + * @param $order_refund_id + * @return mixed + * @throws \think\exception\DbException + */ + public function detail($order_refund_id) + { + // 售后单详情 + $detail = OrderRefundModel::detail($order_refund_id); + // 订单详情 + $order = OrderModel::detail($detail['order_id']); + // 退货地址 + $address = (new ReturnAddressModel)->getAll(); + return $this->fetch('detail', compact('detail', 'order', 'address')); + } + + /** + * 商家审核 + * @param $order_refund_id + * @return array|bool + * @throws \think\exception\DbException + */ + public function audit($order_refund_id) + { + if (!$this->request->isAjax()) { + return false; + } + $model = OrderRefundModel::detail($order_refund_id); + if ($model->audit($this->postData('refund'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + + /** + * 确认收货并退款 + * @param $order_refund_id + * @return array|bool + * @throws \think\exception\DbException + */ + public function receipt($order_refund_id) + { + if (!$this->request->isAjax()) { + return false; + } + $model = OrderRefundModel::detail($order_refund_id); + if ($model->receipt($this->postData('refund'))) { + return $this->renderSuccess('操作成功'); + } + return $this->renderError($model->getError() ?: '操作失败'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/setting/Address.php b/source/application/store/controller/setting/Address.php new file mode 100644 index 0000000..c5efa4e --- /dev/null +++ b/source/application/store/controller/setting/Address.php @@ -0,0 +1,79 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加退货地址 + * @return array|mixed + */ + public function add() + { + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + $model = new ReturnAddressModel; + if ($model->add($this->postData('address'))) { + return $this->renderSuccess('添加成功', url('setting.address/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑退货地址 + * @param $address_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($address_id) + { + // 模板详情 + $model = ReturnAddressModel::detail($address_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('address'))) { + return $this->renderSuccess('更新成功', url('setting.address/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除退货地址 + * @param $address_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($address_id) + { + $model = ReturnAddressModel::detail($address_id); + if (!$model->remove()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/store/controller/setting/Cache.php b/source/application/store/controller/setting/Cache.php new file mode 100644 index 0000000..6ad76ae --- /dev/null +++ b/source/application/store/controller/setting/Cache.php @@ -0,0 +1,137 @@ +request->isAjax()) { + $data = $this->postData('cache'); + $this->rmCache($data['keys']); + return $this->renderSuccess('操作成功'); + } + return $this->fetch('clear', [ + 'cacheList' => $this->getItems() + ]); + } + + /** + * 数据缓存项目 + * @return array + */ + private function getItems() + { + $wxapp_id = $this->store['wxapp']['wxapp_id']; + return [ + 'category' => [ + 'type' => 'cache', + 'key' => 'category_' . $wxapp_id, + 'name' => '商品分类' + ], + 'setting' => [ + 'type' => 'cache', + 'key' => 'setting_' . $wxapp_id, + 'name' => '商城设置' + ], + 'wxapp' => [ + 'type' => 'cache', + 'key' => 'wxapp_' . $wxapp_id, + 'name' => '小程序设置' + ], + 'dealer' => [ + 'type' => 'cache', + 'key' => 'dealer_setting_' . $wxapp_id, + 'name' => '分销设置' + ], + 'sharing' => [ + 'type' => 'cache', + 'key' => 'sharing_setting_' . $wxapp_id, + 'name' => '拼团设置' + ], + 'temp' => [ + 'type' => 'file', + 'name' => '临时图片', + 'dirPath' => [ + 'web' => WEB_PATH . 'temp/' . $wxapp_id . '/', + 'runtime' => RUNTIME_PATH . '/' . 'image/' . $wxapp_id . '/' + ] + ], + ]; + } + + /** + * 删除缓存 + * @param $keys + */ + private function rmCache($keys) + { + $cacheList = $this->getItems(); + $keys = array_intersect(array_keys($cacheList), $keys); + foreach ($keys as $key) { + $item = $cacheList[$key]; + if ($item['type'] === 'cache') { + Driver::has($item['key']) && Driver::rm($item['key']); + } elseif ($item['type'] === 'file') { + $this->deltree($item['dirPath']); + } + } + } + + /** + * 删除目录下所有文件 + * @param $dirPath + * @return bool + */ + private function deltree($dirPath) + { + if (is_array($dirPath)) { + foreach ($dirPath as $path) + $this->deleteFolder($path); + } else { + return $this->deleteFolder($dirPath); + } + return true; + } + + /** + * 递归删除指定目录下所有文件 + * @param $path + * @return bool + */ + private function deleteFolder($path) + { + if (!is_dir($path)) + return false; + // 扫描一个文件夹内的所有文件夹和文件 + foreach (scandir($path) as $val) { + // 排除目录中的.和.. + if (!in_array($val, ['.', '..'])) { + // 如果是目录则递归子目录,继续操作 + if (is_dir($path . $val)) { + // 子目录中操作删除文件夹和文件 + $this->deleteFolder($path . $val . DS); + // 目录清空后删除空文件夹 + rmdir($path . $val . DS); + } else { + // 如果是文件直接删除 + unlink($path . $val); + } + } + } + return true; + } + +} diff --git a/source/application/store/controller/setting/Delivery.php b/source/application/store/controller/setting/Delivery.php new file mode 100644 index 0000000..79e4355 --- /dev/null +++ b/source/application/store/controller/setting/Delivery.php @@ -0,0 +1,96 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 删除模板 + * @param $delivery_id + * @return array + * @throws \think\Exception + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function delete($delivery_id) + { + $model = DeliveryModel::detail($delivery_id); + if (!$model->remove()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + + /** + * 添加配送模板 + * @return array|mixed + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function add() + { + if (!$this->request->isAjax()) { + // 获取所有地区 + $regionData = json_encode(RegionModel::getCacheTree()); + // 地区总数 + $cityCount = RegionModel::getCacheCounts()['city']; + return $this->fetch('add', compact('regionData', 'cityCount')); + } + // 新增记录 + $model = new DeliveryModel; + if ($model->add($this->postData('delivery'))) { + return $this->renderSuccess('添加成功', url('setting.delivery/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑配送模板 + * @param $delivery_id + * @return array|mixed + * @throws \think\Exception + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function edit($delivery_id) + { + // 模板详情 + $model = DeliveryModel::detail($delivery_id); + if (!$this->request->isAjax()) { + // 获取所有地区 + $regionData = json_encode(RegionModel::getCacheTree()); + // 地区总数 + $cityCount = RegionModel::getCacheCounts()['city']; + // 获取配送区域及运费设置项 + $formData = json_encode($model->getFormList()); + return $this->fetch('add', compact('model', 'regionData', 'cityCount', 'formData')); + } + // 更新记录 + if ($model->edit($this->postData('delivery'))) { + return $this->renderSuccess('更新成功', url('setting.delivery/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + +} diff --git a/source/application/store/controller/setting/Express.php b/source/application/store/controller/setting/Express.php new file mode 100644 index 0000000..e82a849 --- /dev/null +++ b/source/application/store/controller/setting/Express.php @@ -0,0 +1,89 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 删除物流公司 + * @param $express_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($express_id) + { + $model = ExpressModel::detail($express_id); + if (!$model->remove()) { + $error = $model->getError() ?: '删除失败'; + return $this->renderError($error); + } + return $this->renderSuccess('删除成功'); + } + + /** + * 添加物流公司 + * @return array|mixed + */ + public function add() + { + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + $model = new ExpressModel; + if ($model->add($this->postData('express'))) { + return $this->renderSuccess('添加成功', url('setting.express/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑物流公司 + * @param $express_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($express_id) + { + // 模板详情 + $model = ExpressModel::detail($express_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('express'))) { + return $this->renderSuccess('更新成功', url('setting.express/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 物流公司编码表 + * @return mixed + */ + public function company() + { + return $this->fetch('company'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/setting/Help.php b/source/application/store/controller/setting/Help.php new file mode 100644 index 0000000..5ebaaab --- /dev/null +++ b/source/application/store/controller/setting/Help.php @@ -0,0 +1,19 @@ +fetch('tplMsg'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/setting/Printer.php b/source/application/store/controller/setting/Printer.php new file mode 100644 index 0000000..30914a7 --- /dev/null +++ b/source/application/store/controller/setting/Printer.php @@ -0,0 +1,99 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加打印机 + * @return array|mixed + */ + public function add() + { + $model = new PrinterModel; + if (!$this->request->isAjax()) { + // 打印机类型列表 + $printerType = $model::getPrinterTypeList(); + return $this->fetch('add', compact('printerType')); + } + // 新增记录 + if ($model->add($this->postData('printer'))) { + return $this->renderSuccess('添加成功', url('setting.printer/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑打印机 + * @param $printer_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($printer_id) + { + // 模板详情 + $model = PrinterModel::detail($printer_id); + if (!$this->request->isAjax()) { + // 打印机类型列表 + $printerType = $model::getPrinterTypeList(); + return $this->fetch('edit', compact('model', 'printerType')); + } + // 更新记录 + if ($model->edit($this->postData('printer'))) { + return $this->renderSuccess('更新成功', url('setting.printer/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除打印机 + * @param $printer_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($printer_id) + { + $model = PrinterModel::detail($printer_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + + /** + * 测试打印接口 + * @param int $order_id + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function test($order_id = 180) + { + // 订单信息 + $order = \app\store\model\Order::detail($order_id); + // 实例化打印机驱动 + $Printer = new \app\common\service\order\Printer(); + $Printer->printTicket($order, \app\common\enum\OrderStatus::ORDER_PAYMENT); + } + + +} \ No newline at end of file diff --git a/source/application/store/controller/shop/Clerk.php b/source/application/store/controller/shop/Clerk.php new file mode 100644 index 0000000..b79c00f --- /dev/null +++ b/source/application/store/controller/shop/Clerk.php @@ -0,0 +1,91 @@ +getList(-1, $shop_id, $search); + // 门店列表 + $shopList = (new ShopModel)->getList(); + return $this->fetch('index', compact('list', 'shopList')); + } + + /** + * 添加店员 + * @return array|bool|mixed + * @throws \Exception + */ + public function add() + { + $model = new ClerkModel; + if (!$this->request->isAjax()) { + // 门店列表 + $shopList = (new ShopModel)->getList(); + return $this->fetch('add', compact('shopList')); + } + // 新增记录 + if ($model->add($this->postData('clerk'))) { + return $this->renderSuccess('添加成功', url('shop.clerk/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑店员 + * @param $clerk_id + * @return array|bool|mixed + * @throws \think\exception\DbException + */ + public function edit($clerk_id) + { + // 店员详情 + $model = ClerkModel::detail($clerk_id); + if (!$this->request->isAjax()) { + // 门店列表 + $shopList = (new ShopModel)->getList(); + return $this->fetch('edit', compact('model', 'shopList')); + } + // 新增记录 + if ($model->edit($this->postData('clerk'))) { + return $this->renderSuccess('更新成功', url('shop.clerk/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除店员 + * @param $clerk_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($clerk_id) + { + // 店员详情 + $model = ClerkModel::detail($clerk_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/shop/Order.php b/source/application/store/controller/shop/Order.php new file mode 100644 index 0000000..78a61ff --- /dev/null +++ b/source/application/store/controller/shop/Order.php @@ -0,0 +1,33 @@ +getList($shop_id, $search); + // 门店列表 + $shopList = (new ShopModel)->getList(); + return $this->fetch('index', compact('list', 'shopList')); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/statistics/Data.php b/source/application/store/controller/statistics/Data.php new file mode 100644 index 0000000..8629466 --- /dev/null +++ b/source/application/store/controller/statistics/Data.php @@ -0,0 +1,63 @@ +statisticsDataService = new StatisticsDataService; + } + + /** + * 数据统计主页 + * @return mixed + * @throws \think\Exception + */ + public function index() + { + return $this->fetch('index', [ + // 数据概况 + 'survey' => $this->statisticsDataService->getSurveyData(), + // 近七日交易走势 + 'echarts7days' => $this->statisticsDataService->getTransactionTrend(), + // 商品销售榜 + 'goodsRanking' => $this->statisticsDataService->getGoodsRanking(), + // 用户消费榜 + 'userExpendRanking' => $this->statisticsDataService->geUserExpendRanking(), + ]); + } + + /** + * 数据概况API + * @param null $startDate + * @param null $endDate + * @return array + * @throws \think\Exception + */ + public function survey($startDate = null, $endDate = null) + { + return $this->renderSuccess('', '', + $this->statisticsDataService->getSurveyData($startDate, $endDate)); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/store/Role.php b/source/application/store/controller/store/Role.php new file mode 100644 index 0000000..6365243 --- /dev/null +++ b/source/application/store/controller/store/Role.php @@ -0,0 +1,100 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加角色 + * @return array|mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function add() + { + $model = new RoleModel; + if (!$this->request->isAjax()) { + // 权限列表 + $accessList = (new AccessModel)->getJsTree(); + // 角色列表 + $roleList = $model->getList(); + return $this->fetch('add', compact('accessList', 'roleList')); + } + // 新增记录 + if ($model->add($this->postData('role'))) { + return $this->renderSuccess('添加成功', url('store.role/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 更新角色 + * @param $role_id + * @return array|mixed + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function edit($role_id) + { + // 角色详情 + $model = RoleModel::detail($role_id); + if (!$this->request->isAjax()) { + // 权限列表 + $accessList = (new AccessModel)->getJsTree($model['role_id']); + // 角色列表 + $roleList = $model->getList(); + return $this->fetch('edit', compact('model', 'accessList', 'roleList')); + } + // 更新记录 + if ($model->edit($this->postData('role'))) { + return $this->renderSuccess('更新成功', url('store.role/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除角色 + * @param $role_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($role_id) + { + // 角色详情 + $model = RoleModel::detail($role_id); + if (!$model->remove()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/store/controller/store/User.php b/source/application/store/controller/store/User.php new file mode 100644 index 0000000..781d139 --- /dev/null +++ b/source/application/store/controller/store/User.php @@ -0,0 +1,115 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加管理员 + * @return array|mixed + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function add() + { + $model = new StoreUserModel; + if (!$this->request->isAjax()) { + // 角色列表 + $roleList = (new RoleModel)->getList(); + return $this->fetch('add', compact('roleList')); + } + // 新增记录 + if ($model->add($this->postData('user'))) { + return $this->renderSuccess('添加成功', url('store.user/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 更新管理员 + * @param $user_id + * @return array|mixed + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit($user_id) + { + // 管理员详情 + $model = StoreUserModel::detail($user_id); + $model['roleIds'] = UserRole::getRoleIds($model['store_user_id']); + if (!$this->request->isAjax()) { + return $this->fetch('edit', [ + 'model' => $model, + // 角色列表 + 'roleList' => (new RoleModel)->getList(), + // 所有角色id + 'roleIds' => UserRole::getRoleIds($model['store_user_id']), + ]); + } + // 更新记录 + if ($model->edit($this->postData('user'))) { + return $this->renderSuccess('更新成功', url('store.user/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除管理员 + * @param $user_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($user_id) + { + // 管理员详情 + $model = StoreUserModel::detail($user_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + + /** + * 更新当前管理员信息 + * @return array|mixed + * @throws \think\exception\DbException + */ + public function renew() + { + // 管理员详情 + $model = StoreUserModel::detail($this->store['user']['store_user_id']); + if ($this->request->isAjax()) { + if ($model->renew($this->postData('user'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + return $this->fetch('renew', compact('model')); + } +} diff --git a/source/application/store/controller/upload/Library.php b/source/application/store/controller/upload/Library.php new file mode 100644 index 0000000..ec9675c --- /dev/null +++ b/source/application/store/controller/upload/Library.php @@ -0,0 +1,104 @@ +getList($type); + // 文件列表 + $file_list = (new UploadFileModel)->getlist(intval($group_id), $type, 0); + return $this->renderSuccess('success', '', compact('group_list', 'file_list')); + } + + /** + * 新增分组 + * @param $group_name + * @param string $group_type + * @return array + */ + public function addGroup($group_name, $group_type = 'image') + { + $model = new UploadGroupModel; + if ($model->add(compact('group_name', 'group_type'))) { + $group_id = $model->getLastInsID(); + return $this->renderSuccess('添加成功', '', compact('group_id', 'group_name')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑分组 + * @param $group_id + * @param $group_name + * @return array + * @throws \think\exception\DbException + */ + public function editGroup($group_id, $group_name) + { + $model = UploadGroupModel::detail($group_id); + if ($model->edit(compact('group_name'))) { + return $this->renderSuccess('修改成功'); + } + return $this->renderError($model->getError() ?: '修改失败'); + } + + /** + * 删除分组 + * @param $group_id + * @return array + * @throws \think\exception\DbException + */ + public function deleteGroup($group_id) + { + $model = UploadGroupModel::detail($group_id); + if ($model->remove()) { + return $this->renderSuccess('删除成功'); + } + return $this->renderError($model->getError() ?: '删除失败'); + } + + /** + * 批量删除文件 + * @param $fileIds + * @return array + */ + public function deleteFiles($fileIds) + { + $model = new UploadFileModel; + if ($model->softDelete($fileIds)) { + return $this->renderSuccess('删除成功'); + } + return $this->renderError($model->getError() ?: '删除失败'); + } + + /** + * 批量移动文件分组 + * @param $group_id + * @param $fileIds + * @return array + */ + public function moveFiles($group_id, $fileIds) + { + $model = new UploadFileModel; + if ($model->moveGroup($group_id, $fileIds) !== false) { + return $this->renderSuccess('移动成功'); + } + return $this->renderError($model->getError() ?: '移动失败'); + } +} diff --git a/source/application/store/controller/user/Balance.php b/source/application/store/controller/user/Balance.php new file mode 100644 index 0000000..841b083 --- /dev/null +++ b/source/application/store/controller/user/Balance.php @@ -0,0 +1,31 @@ +fetch('log', [ + // 充值记录列表 + 'list' => $model->getList($this->request->param()), + // 属性集 + 'attributes' => $model::getAttributes(), + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/user/Grade.php b/source/application/store/controller/user/Grade.php new file mode 100644 index 0000000..cac6056 --- /dev/null +++ b/source/application/store/controller/user/Grade.php @@ -0,0 +1,81 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加等级 + * @return array|bool|mixed + * @throws \Exception + */ + public function add() + { + $model = new GradeModel; + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + if ($model->add($this->postData('grade'))) { + return $this->renderSuccess('添加成功', url('user.grade/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 编辑会员等级 + * @param $grade_id + * @return array|bool|mixed + * @throws \think\exception\DbException + */ + public function edit($grade_id) + { + // 会员等级详情 + $model = GradeModel::detail($grade_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 新增记录 + if ($model->edit($this->postData('grade'))) { + return $this->renderSuccess('更新成功', url('user.grade/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除会员等级 + * @param $grade_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($grade_id) + { + // 会员等级详情 + $model = GradeModel::detail($grade_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/user/Recharge.php b/source/application/store/controller/user/Recharge.php new file mode 100644 index 0000000..7de910b --- /dev/null +++ b/source/application/store/controller/user/Recharge.php @@ -0,0 +1,31 @@ +fetch('order', [ + // 充值记录列表 + 'list' => $model->getList($this->request->param()), + // 属性集 + 'attributes' => $model::getAttributes(), + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/controller/wxapp/Help.php b/source/application/store/controller/wxapp/Help.php new file mode 100644 index 0000000..1196ec2 --- /dev/null +++ b/source/application/store/controller/wxapp/Help.php @@ -0,0 +1,82 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 添加帮助 + * @return array|mixed + */ + public function add() + { + $model = new WxappHelpModel; + if (!$this->request->isAjax()) { + return $this->fetch('add'); + } + // 新增记录 + if ($model->add($this->postData('help'))) { + return $this->renderSuccess('添加成功', url('wxapp.help/index')); + } + return $this->renderError($model->getError() ?: '添加失败'); + } + + /** + * 更新帮助 + * @param $help_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($help_id) + { + // 帮助详情 + $model = WxappHelpModel::detail($help_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', compact('model')); + } + // 更新记录 + if ($model->edit($this->postData('help'))) { + return $this->renderSuccess('更新成功', url('wxapp.help/index')); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + + /** + * 删除帮助 + * @param $help_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($help_id) + { + // 帮助详情 + $model = WxappHelpModel::detail($help_id); + if (!$model->remove()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + +} diff --git a/source/application/store/controller/wxapp/Page.php b/source/application/store/controller/wxapp/Page.php new file mode 100644 index 0000000..35ee006 --- /dev/null +++ b/source/application/store/controller/wxapp/Page.php @@ -0,0 +1,145 @@ +getList(); + return $this->fetch('index', compact('list')); + } + + /** + * 新增页面 + * @return array|mixed + */ + public function add() + { + $model = new WxappPageModel; + if (!$this->request->isAjax()) { + return $this->fetch('edit', [ + 'defaultData' => json_encode($model->getDefaultItems()), + 'jsonData' => json_encode(['page' => $model->getDefaultPage(), 'items' => []]), + 'opts' => json_encode([ + 'catgory' => CategoryModel::getCacheTree(), + 'sharingCatgory' => SharingCategoryModel::getCacheTree(), + 'articleCatgory' => ArticleCategoryModel::getALL(), + ]) + ]); + } + // 接收post数据 + $post = $this->request->post('data', null, null); + if (!$model->add(json_decode($post, true))) { + return $this->renderError('添加失败'); + } + return $this->renderSuccess('添加成功', url('wxapp.page/index')); + } + + /** + * 编辑页面 + * @param $page_id + * @return array|mixed + * @throws \think\exception\DbException + */ + public function edit($page_id) + { + $model = WxappPageModel::detail($page_id); + if (!$this->request->isAjax()) { + return $this->fetch('edit', [ + 'defaultData' => json_encode($model->getDefaultItems()), + 'jsonData' => json_encode($model['page_data']), + 'opts' => json_encode([ + 'catgory' => CategoryModel::getCacheTree(), + 'sharingCatgory' => SharingCategoryModel::getCacheTree(), + 'articleCatgory' => ArticleCategoryModel::getALL(), + ]) + ]); + } + // 接收post数据 + $post = $this->request->post('data', null, null); + if (!$model->edit(json_decode($post, true))) { + return $this->renderError('更新失败'); + } + return $this->renderSuccess('更新成功'); + } + + /** + * 删除页面 + * @param $page_id + * @return array + * @throws \think\exception\DbException + */ + public function delete($page_id) + { + // 帮助详情 + $model = WxappPageModel::detail($page_id); + if (!$model->setDelete()) { + return $this->renderError($model->getError() ?: '删除失败'); + } + return $this->renderSuccess('删除成功'); + } + + /** + * 设置默认首页 + * @param $page_id + * @return array + * @throws \think\exception\DbException + */ + public function setHome($page_id) + { + // 帮助详情 + $model = WxappPageModel::detail($page_id); + if (!$model->setHome()) { + return $this->renderError($model->getError() ?: '设置失败'); + } + return $this->renderSuccess('设置成功'); + } + + /** + * 分类模板 + * @return array|mixed + * @throws \think\exception\DbException + */ + public function category() + { + $model = WxappCategoryModel::detail(); + if ($this->request->isAjax()) { + if ($model->edit($this->postData('category'))) { + return $this->renderSuccess('更新成功'); + } + return $this->renderError($model->getError() ?: '更新失败'); + } + return $this->fetch('category', compact('model')); + } + + /** + * 页面链接 + * @return mixed + */ + public function links() + { + return $this->fetch('links'); + } + +} diff --git a/source/application/store/extra/menus.php b/source/application/store/extra/menus.php new file mode 100644 index 0000000..a1af21e --- /dev/null +++ b/source/application/store/extra/menus.php @@ -0,0 +1,651 @@ + [ + * 'name' => '首页', // 菜单名称 + * 'icon' => 'icon-home', // 图标 (class) + * 'index' => 'index/index', // 链接 + * ], + */ +return [ + 'index' => [ + 'name' => '首页', + 'icon' => 'icon-home', + 'index' => 'index/index', + ], + 'store' => [ + 'name' => '管理员', + 'icon' => 'icon-guanliyuan', + 'index' => 'store.user/index', + 'submenu' => [ + [ + 'name' => '管理员列表', + 'index' => 'store.user/index', + 'uris' => [ + 'store.user/index', + 'store.user/add', + 'store.user/edit', + 'store.user/delete', + ], + ], + [ + 'name' => '角色管理', + 'index' => 'store.role/index', + 'uris' => [ + 'store.role/index', + 'store.role/add', + 'store.role/edit', + 'store.role/delete', + ], + ], + ] + ], + 'goods' => [ + 'name' => '商品管理', + 'icon' => 'icon-goods', + 'index' => 'goods/index', + 'submenu' => [ + [ + 'name' => '商品列表', + 'index' => 'goods/index', + 'uris' => [ + 'goods/index', + 'goods/add', + 'goods/edit', + 'goods/copy' + ], + ], + [ + 'name' => '商品分类', + 'index' => 'goods.category/index', + 'uris' => [ + 'goods.category/index', + 'goods.category/add', + 'goods.category/edit', + ], + ], + [ + 'name' => '商品评价', + 'index' => 'goods.comment/index', + 'uris' => [ + 'goods.comment/index', + 'goods.comment/detail', + ], + ] + ], + ], + 'order' => [ + 'name' => '订单管理', + 'icon' => 'icon-order', + 'index' => 'order/all_list', + 'submenu' => [ + [ + 'name' => '全部订单', + 'index' => 'order/all_list', + ], + [ + 'name' => '待发货', + 'index' => 'order/delivery_list', + ], + [ + 'name' => '待收货', + 'index' => 'order/receipt_list', + ], + [ + 'name' => '待付款', + 'index' => 'order/pay_list', + ], + [ + 'name' => '已完成', + 'index' => 'order/complete_list', + + ], + [ + 'name' => '已取消', + 'index' => 'order/cancel_list', + ], + [ + 'name' => '售后管理', + 'index' => 'order.refund/index', + 'uris' => [ + 'order.refund/index', + 'order.refund/detail', + ] + ], + ] + ], + 'user' => [ + 'name' => '用户管理', + 'icon' => 'icon-user', + 'index' => 'user/index', + 'submenu' => [ + [ + 'name' => '用户列表', + 'index' => 'user/index', + ], + [ + 'name' => '会员等级', + 'active' => true, + 'submenu' => [ + [ + 'name' => '等级管理', + 'index' => 'user.grade/index', + 'uris' => [ + 'user.grade/index', + 'user.grade/add', + 'user.grade/edit', + 'user.grade/delete', + ] + ], + ] + ], + [ + 'name' => '余额记录', + 'active' => true, + 'submenu' => [ + [ + 'name' => '充值记录', + 'index' => 'user.recharge/order', + ], + [ + 'name' => '余额明细', + 'index' => 'user.balance/log', + ], + ] + ], + ] + ], + 'shop' => [ + 'name' => '门店管理', + 'icon' => 'icon-shop', + 'index' => 'shop/index', + 'submenu' => [ + [ + 'name' => '门店管理', + 'active' => true, + 'index' => 'shop/index', + 'submenu' => [ + [ + 'name' => '门店列表', + 'index' => 'shop/index', + 'uris' => [ + 'shop/index', + 'shop/add', + 'shop/edit', + ] + ], + [ + 'name' => '店员管理', + 'index' => 'shop.clerk/index', + 'uris' => [ + 'shop.clerk/index', + 'shop.clerk/add', + 'shop.clerk/edit', + ] + ], + ] + ], + [ + 'name' => '订单核销记录', + 'index' => 'shop.order/index', + ] + ] + ], + 'content' => [ + 'name' => '内容管理', + 'icon' => 'icon-wenzhang', + 'index' => 'content.article/index', + 'submenu' => [ + [ + 'name' => '文章管理', + 'active' => true, + 'submenu' => [ + [ + 'name' => '文章列表', + 'index' => 'content.article/index', + 'uris' => [ + 'content.article/index', + 'content.article/add', + 'content.article/edit', + ] + ], + [ + 'name' => '文章分类', + 'index' => 'content.article.category/index', + 'uris' => [ + 'content.article.category/index', + 'content.article.category/add', + 'content.article.category/edit', + ] + ], + ] + ], + [ + 'name' => '文件库管理', + 'submenu' => [ + [ + 'name' => '文件分组', + 'index' => 'content.files.group/index', + 'uris' => [ + 'content.files.group/index', + 'content.files.group/add', + 'content.files.group/edit', + ] + ], + [ + 'name' => '文件列表', + 'index' => 'content.files/index' + ], + [ + 'name' => '回收站', + 'index' => 'content.files/recycle', + ], + ] + ], + ] + ], + 'market' => [ + 'name' => '营销管理', + 'icon' => 'icon-marketing', + 'index' => 'market.coupon/index', + 'submenu' => [ + [ + 'name' => '优惠券', +// 'active' => true, + 'submenu' => [ + [ + 'name' => '优惠券列表', + 'index' => 'market.coupon/index', + 'uris' => [ + 'market.coupon/index', + 'market.coupon/add', + 'market.coupon/edit', + ] + ], + [ + 'name' => '领取记录', + 'index' => 'market.coupon/receive' + ], + ] + ], + [ + 'name' => '用户充值', + 'submenu' => [ + [ + 'name' => '充值套餐', + 'index' => 'market.recharge.plan/index', + 'uris' => [ + 'market.recharge.plan/index', + 'market.recharge.plan/add', + 'market.recharge.plan/edit', + ] + ], + [ + 'name' => '充值设置', + 'index' => 'market.recharge/setting' + ], + ] + ], + [ + 'name' => '积分管理', + 'submenu' => [ + [ + 'name' => '积分设置', + 'index' => 'market.points/setting' + ], + [ + 'name' => '积分明细', + 'index' => 'market.points/log' + ], + ] + ], + [ + 'name' => '消息推送', + 'submenu' => [ + [ + 'name' => '发送消息', + 'index' => 'market.push/send', + ], + [ + 'name' => '活跃用户', + 'index' => 'market.push/user', + ], +// [ +// 'name' => '发送日志', +// 'index' => 'market.push/log', +// ], + ] + ], + [ + 'name' => '满额包邮', + 'index' => 'market.basic/full_free', + ], + ], + ], + 'statistics' => [ + 'name' => '数据统计', + 'icon' => 'icon-qushitu', + 'index' => 'statistics.data/index', + ], + 'wxapp' => [ + 'name' => '小程序', + 'icon' => 'icon-wxapp', + 'color' => '#36b313', + 'index' => 'wxapp/setting', + 'submenu' => [ + [ + 'name' => '小程序设置', + 'index' => 'wxapp/setting', + ], + [ + 'name' => '页面管理', + 'active' => true, + 'submenu' => [ + [ + 'name' => '页面设计', + 'index' => 'wxapp.page/index', + 'uris' => [ + 'wxapp.page/index', + 'wxapp.page/add', + 'wxapp.page/edit', + ] + ], + [ + 'name' => '分类模板', + 'index' => 'wxapp.page/category' + ], + [ + 'name' => '页面链接', + 'index' => 'wxapp.page/links' + ] + ] + ], + [ + 'name' => '帮助中心', + 'index' => 'wxapp.help/index', + 'uris' => [ + 'wxapp.help/index', + 'wxapp.help/add', + 'wxapp.help/edit' + ] + ], + ], + ], + 'apps' => [ + 'name' => '应用中心', + 'icon' => 'icon-application', + 'is_svg' => true, // 多色图标 + 'index' => 'apps.dealer.apply/index', + 'submenu' => [ + [ + 'name' => '分销中心', + 'submenu' => [ + [ + 'name' => '入驻申请', + 'index' => 'apps.dealer.apply/index', + ], + [ + 'name' => '分销商用户', + 'index' => 'apps.dealer.user/index', + 'uris' => [ + 'apps.dealer.user/index', + 'apps.dealer.user/edit', + 'apps.dealer.user/fans', + ] + ], + [ + 'name' => '分销订单', + 'index' => 'apps.dealer.order/index', + ], + [ + 'name' => '提现申请', + 'index' => 'apps.dealer.withdraw/index', + ], + [ + 'name' => '分销设置', + 'index' => 'apps.dealer.setting/index', + ], + [ + 'name' => '分销海报', + 'index' => 'apps.dealer.setting/qrcode', + ], + ] + ], + [ + 'name' => '拼团管理', + 'submenu' => [ + [ + 'name' => '商品分类', + 'index' => 'apps.sharing.category/index', + 'uris' => [ + 'apps.sharing.category/index', + 'apps.sharing.category/add', + 'apps.sharing.category/edit', + ] + ], + [ + 'name' => '商品列表', + 'index' => 'apps.sharing.goods/index', + 'uris' => [ + 'apps.sharing.goods/index', + 'apps.sharing.goods/add', + 'apps.sharing.goods/edit', + 'apps.sharing.goods/copy', + 'apps.sharing.goods/copy_master', + ] + ], + [ + 'name' => '拼单管理', + 'index' => 'apps.sharing.active/index', + 'uris' => [ + 'apps.sharing.active/index', + 'apps.sharing.active/users', + ] + ], + [ + 'name' => '订单管理', + 'index' => 'apps.sharing.order/index', + 'uris' => [ + 'apps.sharing.order/index', + 'apps.sharing.order/detail', + 'apps.sharing.order.operate/batchdelivery' + ] + ], + [ + 'name' => '售后管理', + 'index' => 'apps.sharing.order.refund/index', + 'uris' => [ + 'apps.sharing.order.refund/index', + 'apps.sharing.order.refund/detail', + ] + ], + [ + 'name' => '商品评价', + 'index' => 'apps.sharing.comment/index', + 'uris' => [ + 'apps.sharing.comment/index', + 'apps.sharing.comment/detail', + ], + ], + [ + 'name' => '拼团设置', + 'index' => 'apps.sharing.setting/index' + ] + ] + ], + [ + 'name' => '砍价活动', + 'index' => 'apps.bargain.active/index', + 'submenu' => [ + [ + 'name' => '活动列表', + 'index' => 'apps.bargain.active/index', + 'uris' => [ + 'apps.bargain.active/index', + 'apps.bargain.active/add', + 'apps.bargain.active/edit', + 'apps.bargain.active/delete', + ], + ], + [ + 'name' => '砍价记录', + 'index' => 'apps.bargain.task/index', + 'uris' => [ + 'apps.bargain.task/index', + 'apps.bargain.task/add', + 'apps.bargain.task/edit', + 'apps.bargain.task/delete', + 'apps.bargain.task/help', + ], + ], + [ + 'name' => '砍价设置', + 'index' => 'apps.bargain.setting/index', + ] + ] + ], + [ + 'name' => '整点秒杀', + 'index' => 'apps.sharp.goods/index', + 'submenu' => [ + [ + 'name' => '秒杀商品', + 'index' => 'apps.sharp.goods/index', + 'uris' => [ + 'apps.sharp.goods/index', + 'apps.sharp.goods/add', + 'apps.sharp.goods/select', + 'apps.sharp.goods/edit', + 'apps.sharp.goods/delete', + ], + ], + [ + 'name' => '活动会场', + 'index' => 'apps.sharp.active/index', + 'uris' => [ + 'apps.sharp.active/index', + 'apps.sharp.active/add', + 'apps.sharp.active/edit', + 'apps.sharp.active/state', + 'apps.sharp.active/delete', + + 'apps.sharp.active_time/index', + 'apps.sharp.active_time/add', + 'apps.sharp.active_time/edit', + 'apps.sharp.active_time/state', + 'apps.sharp.active_time/delete', + ], + ], + [ + 'name' => '基础设置', + 'index' => 'apps.sharp.setting/index', + ] + ] + ], + [ + 'name' => '好物圈', + 'index' => 'apps.wow.order/index', + 'submenu' => [ + [ + 'name' => '商品收藏', + 'index' => 'apps.wow.shoping/index', + ], + [ + 'name' => '订单信息', + 'index' => 'apps.wow.order/index', + ], + [ + 'name' => '基础设置', + 'index' => 'apps.wow.setting/index', + ] + ] + ], + ] + ], + 'setting' => [ + 'name' => '设置', + 'icon' => 'icon-setting', + 'index' => 'setting/store', + 'submenu' => [ + [ + 'name' => '商城设置', + 'index' => 'setting/store', + ], + [ + 'name' => '交易设置', + 'index' => 'setting/trade', + ], + [ + 'name' => '运费模板', + 'index' => 'setting.delivery/index', + 'uris' => [ + 'setting.delivery/index', + 'setting.delivery/add', + 'setting.delivery/edit', + ], + ], + [ + 'name' => '物流公司', + 'index' => 'setting.express/index', + 'uris' => [ + 'setting.express/index', + 'setting.express/add', + 'setting.express/edit', + ], + ], + [ + 'name' => '短信通知', + 'index' => 'setting/sms' + ], + [ + 'name' => '模板消息', + 'index' => 'setting/tplmsg', + 'uris' => [ + 'setting/tplmsg', + 'setting.help/tplmsg' + + ], + ], + [ + 'name' => '退货地址', + 'index' => 'setting.address/index', + 'uris' => [ + 'setting.address/index', + 'setting.address/add', + 'setting.address/edit', + ], + ], + [ + 'name' => '上传设置', + 'index' => 'setting/storage', + ], + [ + 'name' => '小票打印机', + 'submenu' => [ + [ + 'name' => '打印机管理', + 'index' => 'setting.printer/index', + 'uris' => [ + 'setting.printer/index', + 'setting.printer/add', + 'setting.printer/edit' + ] + ], + [ + 'name' => '打印设置', + 'index' => 'setting/printer' + ] + ] + ], + [ + 'name' => '其他', + 'submenu' => [ + [ + 'name' => '清理缓存', + 'index' => 'setting.cache/clear' + ] + ] + ] + ], + ], +]; diff --git a/source/application/store/model/Article.php b/source/application/store/model/Article.php new file mode 100644 index 0000000..7e5eb9e --- /dev/null +++ b/source/application/store/model/Article.php @@ -0,0 +1,88 @@ +with(['image', 'category']) + ->where('is_delete', '=', 0) + ->order(['article_sort' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + + } + + /** + * 新增记录 + * @param $data + * @return false|int + */ + public function add($data) + { + if (empty($data['image_id'])) { + $this->error = '请上传封面图'; + return false; + } + if (empty($data['article_content'])) { + $this->error = '请输入文章内容'; + return false; + } + $data['wxapp_id'] = self::$wxapp_id; + return $this->allowField(true)->save($data); + } + + /** + * 更新记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + if (empty($data['image_id'])) { + $this->error = '请上传封面图'; + return false; + } + if (empty($data['article_content'])) { + $this->error = '请输入文章内容'; + return false; + } + return $this->allowField(true)->save($data) !== false; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]); + } + + /** + * 获取文章总数量 + * @param array $where + * @return int|string + */ + public static function getArticleTotal($where = []) + { + $model = new static; + !empty($where) && $model->where($where); + return $model->where('is_delete', '=', 0)->count(); + } + +} \ No newline at end of file diff --git a/source/application/store/model/Category.php b/source/application/store/model/Category.php new file mode 100644 index 0000000..fe9c4a9 --- /dev/null +++ b/source/application/store/model/Category.php @@ -0,0 +1,77 @@ +deleteCache(); + return $this->allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + // 验证:一级分类如果存在子类,则不允许移动 + if ($data['parent_id'] > 0 && static::hasSubCategory($this['category_id'])) { + $this->error = '该分类下存在子分类,不可以移动'; + return false; + } + $this->deleteCache(); + !array_key_exists('image_id', $data) && $data['image_id'] = 0; + return $this->allowField(true)->save($data) !== false; + } + + /** + * 删除商品分类 + * @param $categoryId + * @return bool|int + */ + public function remove($categoryId) + { + // 判断是否存在商品 + if ($goodsCount = (new Goods)->getGoodsTotal(['category_id' => $categoryId])) { + $this->error = '该分类下存在' . $goodsCount . '个商品,不允许删除'; + return false; + } + // 判断是否存在子分类 + if (static::hasSubCategory($categoryId)) { + $this->error = '该分类下存在子分类,请先删除'; + return false; + } + $this->deleteCache(); + return $this->delete(); + } + + /** + * 删除缓存 + * @return bool + */ + private function deleteCache() + { + return Cache::rm('category_' . static::$wxapp_id); + } + +} diff --git a/source/application/store/model/Comment.php b/source/application/store/model/Comment.php new file mode 100644 index 0000000..7559c12 --- /dev/null +++ b/source/application/store/model/Comment.php @@ -0,0 +1,32 @@ +save(['is_delete' => 1]); + } + + /** + * 获取评价总数量 + * @return int|string + */ + public function getCommentTotal() + { + return $this->where(['is_delete' => 0])->count(); + } + +} \ No newline at end of file diff --git a/source/application/store/model/CommentImage.php b/source/application/store/model/CommentImage.php new file mode 100644 index 0000000..f4704f0 --- /dev/null +++ b/source/application/store/model/CommentImage.php @@ -0,0 +1,14 @@ +where('is_delete', '=', 0) + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + + /** + * 添加新记录 + * @param $data + * @return false|int + */ + public function add($data) + { + $data['wxapp_id'] = self::$wxapp_id; + if ($data['expire_type'] == '20') { + $data['start_time'] = strtotime($data['start_time']); + $data['end_time'] = strtotime($data['end_time']); + } + return $this->allowField(true)->save($data); + } + + /** + * 更新记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + if ($data['expire_type'] == '20') { + $data['start_time'] = strtotime($data['start_time']); + $data['end_time'] = strtotime($data['end_time']); + } + return $this->allowField(true)->save($data) !== false; + } + + /** + * 删除记录 (软删除) + * @return bool|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]) !== false; + } + +} diff --git a/source/application/store/model/Delivery.php b/source/application/store/model/Delivery.php new file mode 100644 index 0000000..ded2745 --- /dev/null +++ b/source/application/store/model/Delivery.php @@ -0,0 +1,167 @@ +onValidate($data)) return false; + // 保存数据 + $data['wxapp_id'] = self::$wxapp_id; + if ($this->allowField(true)->save($data)) { + return $this->createDeliveryRule($data['rule']); + } + return false; + } + + /** + * 编辑记录 + * @param $data + * @return bool|int + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function edit($data) + { + // 表单验证 + if (!$this->onValidate($data)) return false; + // 保存数据 + if ($this->allowField(true)->save($data)) { + return $this->createDeliveryRule($data['rule']); + } + return false; + } + + /** + * 表单验证 + * @param $data + * @return bool + */ + private function onValidate($data) + { + if (!isset($data['rule']) || empty($data['rule'])) { + $this->error = '请选择可配送区域'; + return false; + } + foreach ($data['rule']['first'] as $value) { + if ((int)$value <= 0 || (int)$value != $value) { + $this->error = '首件或首重必须是正整数'; + return false; + } + } + foreach ($data['rule']['additional'] as $value) { + if ((int)$value <= 0 || (int)$value != $value) { + $this->error = '续件或续重必须是正整数'; + return false; + } + } + return true; + } + + /** + * 获取配送区域及运费设置项 + * @return array + */ + public function getFormList() + { + // 所有地区 + $regions = Region::getCacheAll(); + $list = []; + foreach ($this['rule'] as $rule) { + $citys = explode(',', $rule['region']); + $province = []; + foreach ($citys as $cityId) { + if (!isset($regions[$cityId])) continue; + !in_array($regions[$cityId]['pid'], $province) && $province[] = $regions[$cityId]['pid']; + } + $list[] = [ + 'first' => $rule['first'], + 'first_fee' => $rule['first_fee'], + 'additional' => $rule['additional'], + 'additional_fee' => $rule['additional_fee'], + 'province' => $province, + 'citys' => $citys, + ]; + } + return $list; + } + + /** + * 添加模板区域及运费 + * @param $data + * @return int + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + private function createDeliveryRule($data) + { + $save = []; + $connt = count($data['region']); + for ($i = 0; $i < $connt; $i++) { + $save[] = [ + 'region' => $data['region'][$i], + 'first' => $data['first'][$i], + 'first_fee' => $data['first_fee'][$i], + 'additional' => $data['additional'][$i], + 'additional_fee' => $data['additional_fee'][$i], + 'wxapp_id' => self::$wxapp_id + ]; + } + $this->rule()->delete(); + return $this->rule()->saveAll($save); + } + + /** + * 删除记录 + * @return int + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function remove() + { + // 验证运费模板是否被商品使用 + if (!$this->checkIsUseGoods($this['delivery_id'])) { + return false; + } + // 删除运费模板 + $this->rule()->delete(); + return $this->delete(); + } + + /** + * 验证运费模板是否被商品使用 + * @param int $deliveryId + * @return bool + * @throws \think\Exception + */ + private function checkIsUseGoods($deliveryId) + { + // 判断是否存在商品 + $goodsCount = (new GoodsModel)->where('delivery_id', '=', $deliveryId) + ->where('is_delete', '=', 0) + ->count(); + if ($goodsCount > 0) { + $this->error = '该模板被' . $goodsCount . '个商品使用,不允许删除'; + return false; + } + return true; + } + +} diff --git a/source/application/store/model/DeliveryRule.php b/source/application/store/model/DeliveryRule.php new file mode 100644 index 0000000..2fa7e42 --- /dev/null +++ b/source/application/store/model/DeliveryRule.php @@ -0,0 +1,55 @@ + $citys) { + $str .= self::$regionTree[$provinceId]['name']; + if (count($citys) !== count(self::$regionTree[$provinceId]['city'])) { + $cityStr = ''; + foreach ($citys as $cityId) + $cityStr .= self::$regionTree[$provinceId]['city'][$cityId]['name']; + $str .= ' (' . mb_substr($cityStr, 0, -1, 'utf-8') . ')'; + } + $str .= '、'; + } + return mb_substr($str, 0, -1, 'utf-8'); + } + +} diff --git a/source/application/store/model/Express.php b/source/application/store/model/Express.php new file mode 100644 index 0000000..d9dc09d --- /dev/null +++ b/source/application/store/model/Express.php @@ -0,0 +1,45 @@ +allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + return $this->allowField(true)->save($data); + } + + /** + * 删除记录 + * @return bool|int + */ + public function remove() + { + // 判断当前物流公司是否已被订单使用 + $Order = new Order; + if ($orderCount = $Order->where(['express_id' => $this['express_id']])->count()) { + $this->error = '当前物流公司已被' . $orderCount . '个订单使用,不允许删除'; + return false; + } + return $this->delete(); + } + +} \ No newline at end of file diff --git a/source/application/store/model/Goods.php b/source/application/store/model/Goods.php new file mode 100644 index 0000000..d2a5a6e --- /dev/null +++ b/source/application/store/model/Goods.php @@ -0,0 +1,149 @@ +error = '请上传商品图片'; + return false; + } + $data['content'] = isset($data['content']) ? $data['content'] : ''; + $data['wxapp_id'] = $data['sku']['wxapp_id'] = self::$wxapp_id; + + // 开启事务 + $this->startTrans(); + try { + // 添加商品 + $this->allowField(true)->save($data); + // 商品规格 + $this->addGoodsSpec($data); + // 商品图片 + $this->addGoodsImages($data['images']); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 添加商品图片 + * @param $images + * @return int + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + private function addGoodsImages($images) + { + $this->image()->delete(); + $data = array_map(function ($image_id) { + return [ + 'image_id' => $image_id, + 'wxapp_id' => self::$wxapp_id + ]; + }, $images); + return $this->image()->saveAll($data); + } + + /** + * 编辑商品 + * @param $data + * @return bool|mixed + */ + public function edit($data) + { + if (!isset($data['images']) || empty($data['images'])) { + $this->error = '请上传商品图片'; + return false; + } + $data['spec_type'] = isset($data['spec_type']) ? $data['spec_type'] : $this['spec_type']; + $data['content'] = isset($data['content']) ? $data['content'] : ''; + $data['wxapp_id'] = $data['sku']['wxapp_id'] = self::$wxapp_id; + return $this->transaction(function () use ($data) { + // 保存商品 + $this->allowField(true)->save($data); + // 商品规格 + $this->addGoodsSpec($data, true); + // 商品图片 + $this->addGoodsImages($data['images']); + return true; + }); + } + + /** + * 添加商品规格 + * @param $data + * @param $isUpdate + * @throws \Exception + */ + private function addGoodsSpec($data, $isUpdate = false) + { + // 更新模式: 先删除所有规格 + $model = new GoodsSku; + $isUpdate && $model->removeAll($this['goods_id']); + // 添加规格数据 + if ($data['spec_type'] == '10') { + // 单规格 + $this->sku()->save($data['sku']); + } else if ($data['spec_type'] == '20') { + // 添加商品与规格关系记录 + $model->addGoodsSpecRel($this['goods_id'], $data['spec_many']['spec_attr']); + // 添加商品sku + $model->addSkuList($this['goods_id'], $data['spec_many']['spec_list']); + } + } + + /** + * 修改商品状态 + * @param $state + * @return false|int + */ + public function setStatus($state) + { + return $this->allowField(true)->save(['goods_status' => $state ? 10 : 20]) !== false; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + if (!GoodsService::checkIsAllowDelete($this['goods_id'])) { + $this->error = '当前商品正在参与其他活动,不允许删除'; + return false; + } + return $this->allowField(true)->save(['is_delete' => 1]); + } + + /** + * 获取当前商品总数 + * @param array $where + * @return int|string + * @throws \think\Exception + */ + public function getGoodsTotal($where = []) + { + return $this->where('is_delete', '=', 0)->where($where)->count(); + } + +} diff --git a/source/application/store/model/GoodsImage.php b/source/application/store/model/GoodsImage.php new file mode 100644 index 0000000..01bb1bf --- /dev/null +++ b/source/application/store/model/GoodsImage.php @@ -0,0 +1,14 @@ + $item['spec_sku_id'], + 'goods_id' => $goods_id, + 'wxapp_id' => self::$wxapp_id, + ]); + } + return $this->allowField(true)->saveAll($data); + } + + /** + * 添加商品规格关系记录 + * @param $goods_id + * @param $spec_attr + * @return array|false + * @throws \Exception + */ + public function addGoodsSpecRel($goods_id, $spec_attr) + { + $data = []; + array_map(function ($val) use (&$data, $goods_id) { + array_map(function ($item) use (&$val, &$data, $goods_id) { + $data[] = [ + 'goods_id' => $goods_id, + 'spec_id' => $val['group_id'], + 'spec_value_id' => $item['item_id'], + 'wxapp_id' => self::$wxapp_id, + ]; + }, $val['spec_items']); + }, $spec_attr); + $model = new GoodsSpecRel; + return $model->saveAll($data); + } + + /** + * 移除指定商品的所有sku + * @param $goods_id + * @return int + */ + public function removeAll($goods_id) + { + $model = new GoodsSpecRel; + $model->where('goods_id','=', $goods_id)->delete(); + return $this->where('goods_id','=', $goods_id)->delete(); + } + +} diff --git a/source/application/store/model/GoodsSpecRel.php b/source/application/store/model/GoodsSpecRel.php new file mode 100644 index 0000000..c958492 --- /dev/null +++ b/source/application/store/model/GoodsSpecRel.php @@ -0,0 +1,14 @@ +setWhere($query); + // 获取数据列表 + return $this->with(['goods.image', 'address', 'user']) + ->alias('order') + ->field('order.*') + ->join('user', 'user.user_id = order.user_id') + ->where($this->transferDataType($dataType)) + ->where('order.is_delete', '=', 0) + ->order(['order.create_time' => 'desc']) + ->paginate(10, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 订单列表(全部) + * @param $dataType + * @param array $query + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListAll($dataType, $query = []) + { + // 检索查询条件 + !empty($query) && $this->setWhere($query); + // 获取数据列表 + return $this->with(['goods.image', 'address', 'user', 'extract', 'extract_shop']) + ->alias('order') + ->field('order.*') + ->join('user', 'user.user_id = order.user_id') + ->where($this->transferDataType($dataType)) + ->where('order.is_delete', '=', 0) + ->order(['order.create_time' => 'desc']) + ->select(); + } + + /** + * 订单导出 + * @param $dataType + * @param $query + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function exportList($dataType, $query) + { + // 获取订单列表 + $list = $this->getListAll($dataType, $query); + // 导出csv文件 + return (new Exportservice)->orderList($list); + } + + /** + * 批量发货模板 + */ + public function deliveryTpl() + { + return (new Exportservice)->deliveryTpl(); + } + + /** + * 设置检索查询条件 + * @param $query + */ + private function setWhere($query) + { + if (isset($query['search']) && !empty($query['search'])) { + $this->where('order_no|user.nickName', 'like', '%' . trim($query['search']) . '%'); + } + if (isset($query['start_time']) && !empty($query['start_time'])) { + $this->where('order.create_time', '>=', strtotime($query['start_time'])); + } + if (isset($query['end_time']) && !empty($query['end_time'])) { + $this->where('order.create_time', '<', strtotime($query['end_time']) + 86400); + } + if (isset($query['delivery_type']) && !empty($query['delivery_type'])) { + $query['delivery_type'] > -1 && $this->where('delivery_type', '=', $query['delivery_type']); + } + if (isset($query['extract_shop_id']) && !empty($query['extract_shop_id'])) { + $query['extract_shop_id'] > -1 && $this->where('extract_shop_id', '=', $query['extract_shop_id']); + } + // 用户id + if (isset($query['user_id']) && $query['user_id'] > 0) { + $this->where('order.user_id', '=', (int)$query['user_id']); + } + } + + /** + * 转义数据类型条件 + * @param $dataType + * @return array + */ + private function transferDataType($dataType) + { + // 数据类型 + $filter = []; + switch ($dataType) { + case 'delivery': + $filter = [ + 'pay_status' => 20, + 'delivery_status' => 10, + 'order_status' => ['in', [10, 21]] + ]; + break; + case 'receipt': + $filter = [ + 'pay_status' => 20, + 'delivery_status' => 20, + 'receipt_status' => 10 + ]; + break; + case 'pay': + $filter = ['pay_status' => 10, 'order_status' => 10]; + break; + case 'complete': + $filter = ['order_status' => 30]; + break; + case 'cancel': + $filter = ['order_status' => 20]; + break; + case 'all': + $filter = []; + break; + } + return $filter; + } + + /** + * 确认发货(单独订单) + * @param $data + * @return array|bool|false + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + * @throws \Exception + */ + public function delivery($data) + { + // 转义为订单列表 + $orderList = [$this]; + // 验证订单是否满足发货条件 + if (!$this->verifyDelivery($orderList)) { + return false; + } + // 整理更新的数据 + $updateList = [[ + 'order_id' => $this['order_id'], + 'express_id' => $data['express_id'], + 'express_no' => $data['express_no'] + ]]; + // 更新订单发货状态 + if ($status = $this->updateToDelivery($updateList)) { + // 获取已发货的订单 + $completed = self::detail($this['order_id'], ['user', 'address', 'goods', 'express']); + // 发送消息通知 + $this->sendDeliveryMessage([$completed]); + // 同步好物圈订单 + (new WowService($this['wxapp_id']))->update([$completed]); + } + return $status; + } + + /** + * 批量发货 + * @param $data + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function batchDelivery($data) + { + // 获取csv文件中的数据 + if (!$csvData = $this->getCsvData()) { + return false; + } + // 整理订单id集 + $orderNos = helper::getArrayColumn($csvData, 0); + // 获取订单列表数据 + $orderList = helper::arrayColumn2Key($this->getListByOrderNos($orderNos), 'order_no'); + // 验证订单是否存在 + $tempArr = array_values(array_diff($orderNos, array_keys($orderList))); + if (!empty($tempArr)) { + $this->error = "订单号[{$tempArr[0]}] 不存在!"; + return false; + } + // 整理物流单号 + $updateList = []; + foreach ($csvData as $item) { + $updateList[] = [ + 'order_id' => $orderList[$item[0]]['order_id'], + 'express_id' => $data['express_id'], + 'express_no' => $item[1], + ]; + } + // 验证订单是否满足发货条件 + if (!$this->verifyDelivery($orderList)) { + return false; + } + // 更新订单发货状态(批量) + if ($status = $this->updateToDelivery($updateList)) { + // 获取已发货的订单 + $completed = $this->getListByOrderNos($orderNos, ['user', 'address', 'goods', 'express']); + // 发送消息通知 + $this->sendDeliveryMessage($completed); + // 同步好物圈订单 + (new WowService(self::$wxapp_id))->update($completed); + } + return $status; + } + + /** + * 确认发货后发送消息通知 + * @param array|\think\Collection $orderList + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + private function sendDeliveryMessage($orderList) + { + // 实例化消息通知服务类 + $Service = new MessageService; + foreach ($orderList as $item) { + // 发送消息通知 + $Service->delivery($item, OrderTypeEnum::MASTER); + } + return true; + } + + /** + * 更新订单发货状态(批量) + * @param $orderList + * @return array|false + * @throws \Exception + */ + private function updateToDelivery($orderList) + { + $data = []; + foreach ($orderList as $item) { + $data[] = [ + 'order_id' => $item['order_id'], + 'express_no' => $item['express_no'], + 'express_id' => $item['express_id'], + 'delivery_status' => 20, + 'delivery_time' => time(), + ]; + } + return $this->isUpdate()->saveAll($data); + } + + /** + * 验证订单是否满足发货条件 + * @param $orderList + * @return bool + */ + private function verifyDelivery($orderList) + { + foreach ($orderList as $order) { + if ( + $order['pay_status']['value'] != 20 + || $order['delivery_type']['value'] != DeliveryTypeEnum::EXPRESS + || $order['delivery_status']['value'] != 10 + ) { + $this->error = "订单号[{$order['order_no']}] 不满足发货条件!"; + return false; + } + } + return true; + } + + /** + * 获取csv文件中的数据 + * @return array|bool + */ + private function getCsvData() + { + // 获取表单上传文件 例如上传了001.jpg + $file = \request()->file('iFile'); + if (empty($file)) { + $this->error = '请上传发货模板'; + return false; + } + // 设置区域信息 + setlocale(LC_ALL, 'zh_CN'); + // 打开上传的文件 + $csvFile = fopen($file->getInfo()['tmp_name'], 'r'); + // 忽略第一行(csv标题) + fgetcsv($csvFile); + // 遍历并记录订单信息 + $orderList = []; + while ($item = fgetcsv($csvFile)) { + if (!isset($item[0]) || empty($item[0]) || !isset($item[1]) || empty($item[1])) { + $this->error = '模板文件数据不合法'; + return false; + } + $orderList[] = $item; + } + if (empty($orderList)) { + $this->error = '模板文件中没有订单数据'; + return false; + } + return $orderList; + } + + /** + * 修改订单价格 + * @param $data + * @return bool + */ + public function updatePrice($data) + { + if ($this['pay_status']['value'] != 10) { + $this->error = '该订单不合法'; + return false; + } + // 实际付款金额 + $payPrice = bcadd($data['update_price'], $data['update_express_price'], 2); + if ($payPrice <= 0) { + $this->error = '订单实付款价格不能为0.00元'; + return false; + } + return $this->save([ + 'order_no' => $this->orderNo(), // 修改订单号, 否则微信支付提示重复 + 'order_price' => $data['update_price'], + 'pay_price' => $payPrice, + 'update_price' => helper::bcsub($data['update_price'], helper::bcsub($this['total_price'], $this['coupon_money'])), + 'express_price' => $data['update_express_price'] + ]) !== false; + } + + /** + * 审核:用户取消订单 + * @param $data + * @return bool|mixed + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function confirmCancel($data) + { + // 判断订单是否有效 + if ($this['pay_status']['value'] != 20) { + $this->error = '该订单不合法'; + return false; + } + // 订单取消事件 + $status = $this->transaction(function () use ($data) { + if ($data['is_cancel'] == true) { + // 执行退款操作 + (new RefundService)->execute($this); + // 回退商品库存 + FactoryStock::getFactory($this['order_source'])->backGoodsStock($this['goods'], true); + // 回退用户优惠券 + $this['coupon_id'] > 0 && UserCouponModel::setIsUse($this['coupon_id'], false); + // 回退用户积分 + $User = UserModel::detail($this['user_id']); + $describe = "订单取消:{$this['order_no']}"; + $this['points_num'] > 0 && $User->setIncPoints($this['points_num'], $describe); + } + // 更新订单状态 + return $this->save(['order_status' => $data['is_cancel'] ? 20 : 10]); + }); + if ($status == true) { + // 同步好物圈订单 + (new WowService(self::$wxapp_id))->update([$this]); + } + return $status; + } + + /** + * 获取已付款订单总数 (可指定某天) + * @param null $startDate + * @param null $endDate + * @return int|string + * @throws \think\Exception + */ + public function getPayOrderTotal($startDate = null, $endDate = null) + { + $filter = [ + 'pay_status' => PayStatusEnum::SUCCESS, + 'order_status' => ['<>', OrderStatusEnum::CANCELLED], + ]; + if (!is_null($startDate) && !is_null($endDate)) { + $filter['pay_time'] = [ + ['>=', strtotime($startDate)], + ['<', strtotime($endDate) + 86400], + ]; + } + return $this->getOrderTotal($filter); + } + + /** + * 获取订单总数量 + * @param array $filter + * @return int|string + * @throws \think\Exception + */ + private function getOrderTotal($filter = []) + { + return $this->where($filter) + ->where('is_delete', '=', 0) + ->count(); + } + + /** + * 获取某天的总销售额 + * @param null $startDate + * @param null $endDate + * @return float|int + */ + public function getOrderTotalPrice($startDate = null, $endDate = null) + { + if (!is_null($startDate) && !is_null($endDate)) { + $this->where('pay_time', '>=', strtotime($startDate)) + ->where('pay_time', '<', strtotime($endDate) + 86400); + } + return $this->where('pay_status', '=', 20) + ->where('order_status', '<>', 20) + ->where('is_delete', '=', 0) + ->sum('pay_price'); + } + + /** + * 获取某天的下单用户数 + * @param $day + * @return float|int + */ + public function getPayOrderUserTotal($day) + { + $startTime = strtotime($day); + $userIds = $this->distinct(true) + ->where('pay_time', '>=', $startTime) + ->where('pay_time', '<', $startTime + 86400) + ->where('pay_status', '=', 20) + ->where('is_delete', '=', 0) + ->column('user_id'); + return count($userIds); + } + +} diff --git a/source/application/store/model/OrderAddress.php b/source/application/store/model/OrderAddress.php new file mode 100644 index 0000000..a6393d3 --- /dev/null +++ b/source/application/store/model/OrderAddress.php @@ -0,0 +1,15 @@ +where('order.order_no', 'like', "%{$query['order_no']}%"); + } + // 查询条件:起始日期 + if (isset($query['start_time']) && !empty($query['start_time'])) { + $this->where('m.create_time', '>=', strtotime($query['start_time'])); + } + // 查询条件:截止日期 + if (isset($query['end_time']) && !empty($query['end_time'])) { + $this->where('m.create_time', '<', strtotime($query['end_time']) + 86400); + } + // 售后类型 + if (isset($query['type']) && $query['type'] > 0) { + $this->where('m.type', '=', $query['type']); + } + // 处理状态 + if (isset($query['state']) && is_numeric($query['state'])) { + $this->where('m.status', '=', $query['state']); + } + // 获取列表数据 + return $this->alias('m') + ->field('m.*, order.order_no') + ->with(['order_goods.image', 'orderMaster', 'user']) + ->join('order', 'order.order_id = m.order_id') + ->order(['m.create_time' => 'desc']) + ->paginate(10, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 商家审核 + * @param $data + * @return bool + * @throws \think\exception\PDOException + */ + public function audit($data) + { + if ($data['is_agree'] == 20 && empty($data['refuse_desc'])) { + $this->error = '请输入拒绝原因'; + return false; + } + if ($data['is_agree'] == 10 && empty($data['address_id'])) { + $this->error = '请选择退货地址'; + return false; + } + $this->startTrans(); + try { + // 拒绝申请, 标记售后单状态为已拒绝 + $data['is_agree'] == 20 && $data['status'] = 10; + // 同意换货申请, 标记售后单状态为已完成 + $data['is_agree'] == 10 && $this['type']['value'] == 20 && $data['status'] = 20; + // 更新退款单状态 + $this->allowField(true)->save($data); + // 同意售后申请, 记录退货地址 + if ($data['is_agree'] == 10) { + $model = new OrderRefundAddress; + $model->add($this['order_refund_id'], $data['address_id']); + } + // 订单详情 + $order = Order::detail($this['order_id']); + // 发送模板消息 + (new MessageService)->refund(self::detail($this['order_refund_id']), $order['order_no'], OrderTypeEnum::MASTER); + // 事务提交 + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 确认收货并退款 + * @param $data + * @return bool + * @throws \think\exception\DbException + */ + public function receipt($data) + { + // 订单详情 + $order = Order::detail($this['order_id']); + if ($data['refund_money'] > min($order['pay_price'], $this['order_goods']['total_pay_price'])) { + $this->error = '退款金额不能大于商品实付款金额'; + return false; + } + $this->transaction(function () use ($order, $data) { + // 更新售后单状态 + $this->allowField(true)->save([ + 'refund_money' => $data['refund_money'], + 'is_receipt' => 1, + 'status' => 20 + ]); + // 消减用户的实际消费金额 + // 条件:判断订单是否已结算 + if ($order['is_settled'] == true) { + (new UserModel)->setDecUserExpend($order['user_id'], $data['refund_money']); + } + // 执行原路退款 + (new RefundService)->execute($order, $data['refund_money']); + // 发送模板消息 + (new MessageService)->refund(self::detail($this['order_refund_id']), $order['order_no'], OrderTypeEnum::MASTER); + }); + return true; + } + +} \ No newline at end of file diff --git a/source/application/store/model/OrderRefundAddress.php b/source/application/store/model/OrderRefundAddress.php new file mode 100644 index 0000000..7b1fa74 --- /dev/null +++ b/source/application/store/model/OrderRefundAddress.php @@ -0,0 +1,33 @@ +save([ + 'order_refund_id' => $order_refund_id, + 'name' => $detail['name'], + 'phone' => $detail['phone'], + 'detail' => $detail['detail'], + 'wxapp_id' => self::$wxapp_id + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/OrderRefundImage.php b/source/application/store/model/OrderRefundImage.php new file mode 100644 index 0000000..635d155 --- /dev/null +++ b/source/application/store/model/OrderRefundImage.php @@ -0,0 +1,10 @@ +allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + $data['printer_config'] = $data[$data['printer_type']]; + return $this->allowField(true)->save($data); + } + + /** + * 删除记录 + * @return bool|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/Region.php b/source/application/store/model/Region.php new file mode 100644 index 0000000..dca2c32 --- /dev/null +++ b/source/application/store/model/Region.php @@ -0,0 +1,15 @@ +order(['sort' => 'asc']) + ->where('is_delete', '=', 0) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 获取全部收货地址 + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getAll() + { + return $this->order(['sort' => 'asc']) + ->where('is_delete', '=', 0) + ->select(); + } + + /** + * 添加新记录 + * @param $data + * @return false|int + */ + public function add($data) + { + $data['wxapp_id'] = self::$wxapp_id; + return $this->allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + return $this->allowField(true)->save($data); + } + + /** + * 删除记录 + * @return bool|int + */ + public function remove() + { + return $this->save(['is_delete' => 1]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/Setting.php b/source/application/store/model/Setting.php new file mode 100644 index 0000000..ca9d366 --- /dev/null +++ b/source/application/store/model/Setting.php @@ -0,0 +1,95 @@ +validValues($key, $values)) { + return false; + } + // 删除系统设置缓存 + Cache::rm('setting_' . self::$wxapp_id); + return $model->save([ + 'key' => $key, + 'describe' => SettingEnum::data()[$key]['describe'], + 'values' => $values, + 'wxapp_id' => self::$wxapp_id, + ]) !== false; + } + + /** + * 数据验证 + * @param $key + * @param $values + * @return bool + */ + private function validValues($key, $values) + { + $callback = [ + 'store' => function ($values) { + return $this->validStore($values); + }, + 'printer' => function ($values) { + return $this->validPrinter($values); + }, + ]; + // 验证商城设置 + return isset($callback[$key]) ? $callback[$key]($values) : true; + } + + /** + * 验证商城设置 + * @param $values + * @return bool + */ + private function validStore($values) + { + if (!isset($values['delivery_type']) || empty($values['delivery_type'])) { + $this->error = '配送方式至少选择一个'; + return false; + } + return true; + } + + /** + * 验证小票打印机设置 + * @param $values + * @return bool + */ + private function validPrinter($values) + { + if ($values['is_open'] == false) { + return true; + } + if (!$values['printer_id']) { + $this->error = '请选择订单打印机'; + return false; + } + if (empty($values['order_status'])) { + $this->error = '请选择订单打印方式'; + return false; + } + return true; + } + +} diff --git a/source/application/store/model/Spec.php b/source/application/store/model/Spec.php new file mode 100644 index 0000000..25a52e5 --- /dev/null +++ b/source/application/store/model/Spec.php @@ -0,0 +1,35 @@ +value('spec_id'); + } + + /** + * 新增规格组 + * @param $spec_name + * @return false|int + */ + public function add($spec_name) + { + $wxapp_id = self::$wxapp_id; + return $this->save(compact('spec_name', 'wxapp_id')); + } + +} diff --git a/source/application/store/model/SpecValue.php b/source/application/store/model/SpecValue.php new file mode 100644 index 0000000..093383a --- /dev/null +++ b/source/application/store/model/SpecValue.php @@ -0,0 +1,38 @@ +value('spec_value_id'); + } + + /** + * 新增规格值 + * @param $spec_id + * @param $spec_value + * @return false|int + */ + public function add($spec_id, $spec_value) + { + $wxapp_id = self::$wxapp_id; + return $this->save(compact('spec_value', 'spec_id', 'wxapp_id')); + } + +} diff --git a/source/application/store/model/Store.php b/source/application/store/model/Store.php new file mode 100644 index 0000000..0c221cb --- /dev/null +++ b/source/application/store/model/Store.php @@ -0,0 +1,196 @@ +GoodsModel = new Goods; + $this->OrderModel = new Order; + $this->UserModel = new User; + } + + /** + * 后台首页数据 + * @return array + * @throws \think\Exception + */ + public function getHomeData() + { + $today = date('Y-m-d'); + $yesterday = date('Y-m-d', strtotime('-1 day')); + // 最近七天日期 + $lately7days = $this->getLately7days(); + $data = [ + 'widget-card' => [ + // 商品总量 + 'goods_total' => $this->getGoodsTotal(), + // 用户总量 + 'user_total' => $this->getUserTotal(), + // 订单总量 + 'order_total' => $this->getOrderTotal(), + // 评价总量 + 'comment_total' => $this->getCommentTotal() + ], + 'widget-outline' => [ + // 销售额(元) + 'order_total_price' => [ + 'tday' => $this->getOrderTotalPrice($today), + 'ytd' => $this->getOrderTotalPrice($yesterday) + ], + // 支付订单数 + 'order_total' => [ + 'tday' => $this->getOrderTotal($today), + 'ytd' => $this->getOrderTotal($yesterday) + ], + // 新增用户数 + 'new_user_total' => [ + 'tday' => $this->getUserTotal($today), + 'ytd' => $this->getUserTotal($yesterday) + ], + // 下单用户数 + 'order_user_total' => [ + 'tday' => $this->getPayOrderUserTotal($today), + 'ytd' => $this->getPayOrderUserTotal($yesterday) + ] + ], + 'widget-echarts' => [ + // 最近七天日期 + 'date' => helper::jsonEncode($lately7days), + 'order_total' => helper::jsonEncode($this->getOrderTotalByDate($lately7days)), + 'order_total_price' => helper::jsonEncode($this->getOrderTotalPriceByDate($lately7days)) + ] + ]; + + + return $data; + } + + /** + * 最近七天日期 + */ + private function getLately7days() + { + // 获取当前周几 + $date = []; + for ($i = 0; $i < 7; $i++) { + $date[] = date('Y-m-d', strtotime('-' . $i . ' days')); + } + return array_reverse($date); + } + + /** + * 获取商品总量 + * @return string + * @throws \think\Exception + */ + private function getGoodsTotal() + { + return number_format($this->GoodsModel->getGoodsTotal()); + } + + /** + * 获取用户总量 + * @param null $day + * @return string + * @throws \think\Exception + */ + private function getUserTotal($day = null) + { + return number_format($this->UserModel->getUserTotal($day)); + } + + /** + * 获取订单总量 + * @param null $day + * @return string + * @throws \think\Exception + */ + private function getOrderTotal($day = null) + { + return number_format($this->OrderModel->getPayOrderTotal($day, $day)); + } + + /** + * 获取订单总量 (指定日期) + * @param $days + * @return array + * @throws \think\Exception + */ + private function getOrderTotalByDate($days) + { + $data = []; + foreach ($days as $day) { + $data[] = $this->getOrderTotal($day); + } + return $data; + } + + /** + * 获取评价总量 + * @return string + */ + private function getCommentTotal() + { + $model = new Comment; + return number_format($model->getCommentTotal()); + } + + /** + * 获取某天的总销售额 + * @param null $day + * @return string + */ + private function getOrderTotalPrice($day = null) + { + return helper::number2($this->OrderModel->getOrderTotalPrice($day, $day)); + } + + /** + * 获取订单总量 (指定日期) + * @param $days + * @return array + */ + private function getOrderTotalPriceByDate($days) + { + $data = []; + foreach ($days as $day) { + $data[] = $this->getOrderTotalPrice($day); + } + return $data; + } + + /** + * 获取某天的下单用户数 + * @param $day + * @return float|int + */ + private function getPayOrderUserTotal($day) + { + return number_format($this->OrderModel->getPayOrderUserTotal($day)); + } + +} \ No newline at end of file diff --git a/source/application/store/model/UploadFile.php b/source/application/store/model/UploadFile.php new file mode 100644 index 0000000..b3030c9 --- /dev/null +++ b/source/application/store/model/UploadFile.php @@ -0,0 +1,92 @@ +where('group_id', '=', (int)$groupId); + // 文件类型 + !empty($fileType) && $this->where('file_type', '=', trim($fileType)); + // 是否在回收站 + $isRecycle > -1 && $this->where('is_recycle', '=', (int)$isRecycle); + // 查询列表数据 + return $this->with(['upload_group']) + ->where(['is_user' => 0, 'is_delete' => 0]) + ->order(['file_id' => 'desc']) + ->paginate(32, false, [ + 'query' => Request::instance()->request() + ]); + } + + /** + * 移入|移出回收站 + * @param bool $isRecycle + * @return false|int + */ + public function setRecycle($isRecycle = true) + { + return $this->save(['is_recycle' => (int)$isRecycle]); + } + + /** + * 删除文件 + * @return false|int + * @throws \think\Exception + */ + public function setDelete() + { + // 存储配置信息 + $config = SettingModel::getItem('storage'); + // 实例化存储驱动 + $StorageDriver = new StorageDriver($config, $this['storage']); + // 删除文件 + if (!$StorageDriver->delete($this['file_name'])) { + $this->error = '文件删除失败:' . $StorageDriver->getError(); + return false; + } + return $this->save(['is_delete' => 1]); + } + + /** + * 批量软删除 + * @param $fileIds + * @return $this + */ + public function softDelete($fileIds) + { + return $this->where('file_id', 'in', $fileIds)->update(['is_recycle' => 1]); + } + + /** + * 批量移动文件分组 + * @param $group_id + * @param $fileIds + * @return $this + */ + public function moveGroup($group_id, $fileIds) + { + return $this->where('file_id', 'in', $fileIds)->update(compact('group_id')); + } + +} diff --git a/source/application/store/model/UploadFileUsed.php b/source/application/store/model/UploadFileUsed.php new file mode 100644 index 0000000..e6018d9 --- /dev/null +++ b/source/application/store/model/UploadFileUsed.php @@ -0,0 +1,38 @@ +save($data); + } + + /** + * 移除记录 + * @param $from_type + * @param $file_id + * @param null $from_id + * @return int + */ + public function remove($from_type, $file_id, $from_id = null) + { + $where = compact('from_type', 'file_id'); + !is_null($from_id) && $where['from_id'] = $from_id; + return $this->where($where)->delete(); + } +} diff --git a/source/application/store/model/UploadGroup.php b/source/application/store/model/UploadGroup.php new file mode 100644 index 0000000..a6e57b2 --- /dev/null +++ b/source/application/store/model/UploadGroup.php @@ -0,0 +1,66 @@ +where('group_type', '=', trim($groupType)); + return $this->where('is_delete', '=', 0) + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->select(); + } + + /** + * 添加新记录 + * @param $data + * @return false|int + */ + public function add($data) + { + return $this->save(array_merge([ + 'wxapp_id' => self::$wxapp_id, + 'sort' => 100 + ], $data)); + } + + /** + * 更新记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + return $this->allowField(true)->save($data) !== false; + } + + /** + * 删除记录 + * @return int + */ + public function remove() + { + // 更新该分组下的所有文件 + (new UploadFile)->where('group_id', '=', $this['group_id']) + ->update(['group_id' => 0]); + // 删除分组 + return $this->save(['is_delete' => 1]); + } + +} diff --git a/source/application/store/model/User.php b/source/application/store/model/User.php new file mode 100644 index 0000000..d6cab13 --- /dev/null +++ b/source/application/store/model/User.php @@ -0,0 +1,208 @@ +where('create_time', '>=', $startTime) + ->where('create_time', '<', $startTime + 86400); + } + return $this->where('is_delete', '=', '0')->count(); + } + + /** + * 获取用户列表 + * @param string $nickName 昵称 + * @param int $gender 性别 + * @param int $grade 会员等级 + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList($nickName = '', $gender = -1, $grade = null) + { + // 检索:微信昵称 + !empty($nickName) && $this->where('nickName', 'like', "%$nickName%"); + // 检索:性别 + if ($gender !== '' && $gender > -1) { + $this->where('gender', '=', (int)$gender); + } + // 检索:会员等级 + $grade > 0 && $this->where('grade_id', '=', (int)$grade); + // 获取用户列表 + return $this->with(['grade']) + ->where('is_delete', '=', '0') + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 删除用户 + * @return bool|mixed + */ + public function setDelete() + { + // 判断是否为分销商 + if (DealerUserModel::isDealerUser($this['user_id'])) { + $this->error = '当前用户为分销商,不可删除'; + return false; + } + return $this->transaction(function () { + // 删除用户推荐关系 + (new DealerUserModel)->onDeleteReferee($this['user_id']); + // 标记为已删除 + return $this->save(['is_delete' => 1]); + }); + } + + /** + * 用户充值 + * @param string $storeUserName 当前操作人用户名 + * @param int $source 充值类型 + * @param array $data post数据 + * @return bool + */ + public function recharge($storeUserName, $source, $data) + { + if ($source == 0) { + return $this->rechargeToBalance($storeUserName, $data['balance']); + } elseif ($source == 1) { + return $this->rechargeToPoints($storeUserName, $data['points']); + } + return false; + } + + /** + * 用户充值:余额 + * @param $storeUserName + * @param $data + * @return bool + */ + private function rechargeToBalance($storeUserName, $data) + { + if (!isset($data['money']) || $data['money'] === '' || $data['money'] < 0) { + $this->error = '请输入正确的金额'; + return false; + } + // 判断充值方式,计算最终金额 + if ($data['mode'] === 'inc') { + $diffMoney = $data['money']; + } elseif ($data['mode'] === 'dec') { + $diffMoney = -$data['money']; + } else { + $diffMoney = helper::bcsub($data['money'], $this['balance']); + } + // 更新记录 + $this->transaction(function () use ($storeUserName, $data, $diffMoney) { + // 更新账户余额 + $this->setInc('balance', $diffMoney); + // 新增余额变动记录 + BalanceLogModel::add(SceneEnum::ADMIN, [ + 'user_id' => $this['user_id'], + 'money' => $diffMoney, + 'remark' => $data['remark'], + ], [$storeUserName]); + }); + return true; + } + + /** + * 用户充值:积分 + * @param $storeUserName + * @param $data + * @return bool + */ + private function rechargeToPoints($storeUserName, $data) + { + if (!isset($data['value']) || $data['value'] === '' || $data['value'] < 0) { + $this->error = '请输入正确的积分数量'; + return false; + } + // 判断充值方式,计算最终积分 + if ($data['mode'] === 'inc') { + $diffMoney = $data['value']; + } elseif ($data['mode'] === 'dec') { + $diffMoney = -$data['value']; + } else { + $diffMoney = $data['value'] - $this['points']; + } + // 更新记录 + $this->transaction(function () use ($storeUserName, $data, $diffMoney) { + // 更新账户积分 + $this->setInc('points', $diffMoney); + // 新增积分变动记录 + PointsLogModel::add([ + 'user_id' => $this['user_id'], + 'value' => $diffMoney, + 'describe' => "后台管理员 [{$storeUserName}] 操作", + 'remark' => $data['remark'], + ]); + }); + return true; + } + + /** + * 修改用户等级 + * @param $data + * @return mixed + */ + public function updateGrade($data) + { + // 变更前的等级id + $oldGradeId = $this['grade_id']; + return $this->transaction(function () use ($oldGradeId, $data) { + // 更新用户的等级 + $status = $this->save(['grade_id' => $data['grade_id']]); + // 新增用户等级修改记录 + if ($status) { + (new GradeLogModel)->record([ + 'user_id' => $this['user_id'], + 'old_grade_id' => $oldGradeId, + 'new_grade_id' => $data['grade_id'], + 'change_type' => ChangeTypeEnum::ADMIN_USER, + 'remark' => $data['remark'] + ]); + } + return $status !== false; + }); + } + + /** + * 消减用户的实际消费金额 + * @param $userId + * @param $expendMoney + * @return int|true + * @throws \think\Exception + */ + public function setDecUserExpend($userId, $expendMoney) + { + return $this->where(['user_id' => $userId])->setDec('expend_money', $expendMoney); + } + +} diff --git a/source/application/store/model/UserAddress.php b/source/application/store/model/UserAddress.php new file mode 100644 index 0000000..7db84c0 --- /dev/null +++ b/source/application/store/model/UserAddress.php @@ -0,0 +1,15 @@ +with(['user']) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/Wxapp.php b/source/application/store/model/Wxapp.php new file mode 100644 index 0000000..9c99b24 --- /dev/null +++ b/source/application/store/model/Wxapp.php @@ -0,0 +1,111 @@ +startTrans(); + try { + // 删除wxapp缓存 + self::deleteCache(); + // 写入微信支付证书文件 + $this->writeCertPemFiles($data['cert_pem'], $data['key_pem']); + // 更新小程序设置 + $this->allowField(true)->save($data); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 写入cert证书文件 + * @param string $cert_pem + * @param string $key_pem + * @return bool + */ + private function writeCertPemFiles($cert_pem = '', $key_pem = '') + { + if (empty($cert_pem) || empty($key_pem)) { + return false; + } + // 证书目录 + $filePath = APP_PATH . 'common/library/wechat/cert/' . self::$wxapp_id . '/'; + // 目录不存在则自动创建 + if (!is_dir($filePath)) { + mkdir($filePath, 0755, true); + } + // 写入cert.pem文件 + if (!empty($cert_pem)) { + file_put_contents($filePath . 'cert.pem', $cert_pem); + } + // 写入key.pem文件 + if (!empty($key_pem)) { + file_put_contents($filePath . 'key.pem', $key_pem); + } + return true; + } + + /** + * 记录图片信息 + * @param $wxapp_id + * @param $oldFileId + * @param $newFileName + * @param $fromType + * @return int|mixed + */ + private function uploadImage($wxapp_id, $oldFileId, $newFileName, $fromType) + { +// $UploadFile = new UploadFile; + $UploadFileUsed = new UploadFileUsed; + if ($oldFileId > 0) { + // 获取原图片path + $oldFileName = UploadFile::getFileName($oldFileId); + // 新文件与原来路径一致, 代表用户未修改, 不做更新 + if ($newFileName === $oldFileName) + return $oldFileId; + // 删除原文件使用记录 + $UploadFileUsed->remove('service', $oldFileId); + } + // 删除图片 + if (empty($newFileName)) return 0; + // 查询新文件file_id + $fileId = UploadFile::getFildIdByName($newFileName); + // 添加文件使用记录 + $UploadFileUsed->add([ + 'file_id' => $fileId, + 'wxapp_id' => $wxapp_id, + 'from_type' => $fromType + ]); + return $fileId; + } + + /** + * 删除wxapp缓存 + * @return bool + */ + public static function deleteCache() + { + return Cache::rm('wxapp_' . self::$wxapp_id); + } + +} diff --git a/source/application/store/model/WxappCategory.php b/source/application/store/model/WxappCategory.php new file mode 100644 index 0000000..29984c6 --- /dev/null +++ b/source/application/store/model/WxappCategory.php @@ -0,0 +1,24 @@ +allowField(true)->save($data) !== false; + } + +} \ No newline at end of file diff --git a/source/application/store/model/WxappHelp.php b/source/application/store/model/WxappHelp.php new file mode 100644 index 0000000..7ee0b5b --- /dev/null +++ b/source/application/store/model/WxappHelp.php @@ -0,0 +1,43 @@ +allowField(true)->save($data); + } + + /** + * 更新记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + return $this->allowField(true)->save($data) !== false; + } + + /** + * 删除记录 + * @return int + */ + public function remove() { + return $this->delete(); + } + +} diff --git a/source/application/store/model/WxappNavbar.php b/source/application/store/model/WxappNavbar.php new file mode 100644 index 0000000..347db7f --- /dev/null +++ b/source/application/store/model/WxappNavbar.php @@ -0,0 +1,26 @@ +save($data) !== false; + } + +} diff --git a/source/application/store/model/WxappPage.php b/source/application/store/model/WxappPage.php new file mode 100644 index 0000000..0369561 --- /dev/null +++ b/source/application/store/model/WxappPage.php @@ -0,0 +1,87 @@ +where(['is_delete' => 0])->order(['create_time' => 'desc'])->select(); + } + + /** + * 新增页面 + * @param $data + * @return bool + */ + public function add($data) + { + // 删除wxapp缓存 + Wxapp::deleteCache(); + return $this->save([ + 'page_type' => 20, + 'page_name' => $data['page']['params']['name'], + 'page_data' => $data, + 'wxapp_id' => self::$wxapp_id + ]); + } + + /** + * 更新页面 + * @param $data + * @return bool + */ + public function edit($data) + { + // 删除wxapp缓存 + Wxapp::deleteCache(); + // 保存数据 + return $this->save([ + 'page_name' => $data['page']['params']['name'], + 'page_data' => $data + ]) !== false; + } + + /** + * 删除记录 + * @return int + */ + public function setDelete() + { + if ($this['page_type'] == 10) { + $this->error = '默认首页不可以删除'; + return false; + } + // 删除wxapp缓存 + Wxapp::deleteCache(); + return $this->save(['is_delete' => 1]); + } + + /** + * 设为默认首页 + * @return int + */ + public function setHome() + { + // 取消原默认首页 + $this->where(['page_type' => 10])->update(['page_type' => 20]); + // 删除wxapp缓存 + Wxapp::deleteCache(); + return $this->save(['page_type' => 10]); + } + +} diff --git a/source/application/store/model/article/Category.php b/source/application/store/model/article/Category.php new file mode 100644 index 0000000..e96970f --- /dev/null +++ b/source/application/store/model/article/Category.php @@ -0,0 +1,76 @@ +deleteCache(); + return $this->allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + $this->deleteCache(); + return $this->allowField(true)->save($data); + } + + /** + * 删除商品分类 + * @param $category_id + * @return bool|int + */ + public function remove($category_id) + { + // 判断是否存在文章 + $articleCount = ArticleModel::getArticleTotal(['category_id' => $category_id]); + if ($articleCount > 0) { + $this->error = '该分类下存在' . $articleCount . '个文章,不允许删除'; + return false; + } + $this->deleteCache(); + return $this->delete(); + } + + /** + * 删除缓存 + * @return bool + */ + private function deleteCache() + { + return Cache::rm('article_category_' . self::$wxapp_id); + } + +} diff --git a/source/application/store/model/bargain/Active.php b/source/application/store/model/bargain/Active.php new file mode 100644 index 0000000..dfa949c --- /dev/null +++ b/source/application/store/model/bargain/Active.php @@ -0,0 +1,130 @@ +setBaseQuery($this->alias, [ + ['goods', 'goods_id'], + ]); + // 检索查询条件 + if (!empty($search)) { + $this->where('goods.goods_name', 'like', "%{$search}%"); + } + // 获取活动列表 + $list = $this->where("{$this->alias}.is_delete", '=', 0) + ->order(["{$this->alias}.sort" => 'asc', "{$this->alias}.create_time" => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + if (!$list->isEmpty()) { + // 设置商品数据 + $list = GoodsService::setGoodsData($list); + } + return $list; + } + + /** + * 新增记录 + * @param $data + * @return bool|int + */ + public function add($data) + { + if (!$this->onValidate($data, 'add')) { + return false; + } + $data = $this->createData($data); + return $this->allowField(true)->save($data) !== false; + } + + /** + * 更新记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + if (!$this->onValidate($data, 'edit')) { + return false; + } + $data = $this->createData($data); + return $this->allowField(true)->save($data) !== false; + } + + private function createData($data) + { + $data['wxapp_id'] = static::$wxapp_id; + $data['start_time'] = strtotime($data['start_time']); + $data['end_time'] = strtotime($data['end_time']); + return $data; + } + + /** + * 表单验证 + * @param $data + * @param string $scene + * @return bool + */ + private function onValidate($data, $scene = 'add') + { + if ($scene === 'add') { + if (!isset($data['goods_id']) || empty($data['goods_id'])) { + $this->error = '请选择商品'; + return false; + } + } + // 验证活动时间 + if (empty($data['start_time']) || empty($data['end_time'])) { + $this->error = '请选择活动的开始时间与截止时间'; + return false; + } + $data['start_time'] = strtotime($data['start_time']); + $data['end_time'] = strtotime($data['end_time']); + if ($data['end_time'] <= $data['start_time']) { + $this->error = '活动结束时间必须大于开始时间'; + return false; + } + return true; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->allowField(true)->save(['is_delete' => 1]); + } + + /** + * 商品ID是否存在 + * @param $goodsId + * @return bool + */ + public static function isExistGoodsId($goodsId) + { + return !!(new static)->where('goods_id', '=', $goodsId) + ->where('is_delete', '=', 0) + ->value('active_id'); + } + +} \ No newline at end of file diff --git a/source/application/store/model/bargain/Setting.php b/source/application/store/model/bargain/Setting.php new file mode 100644 index 0000000..195295c --- /dev/null +++ b/source/application/store/model/bargain/Setting.php @@ -0,0 +1,43 @@ + '基础设置', + ]; + + /** + * 更新系统设置 + * @param $key + * @param $values + * @return bool + * @throws \think\exception\DbException + */ + public function edit($key, $values) + { + $model = self::detail($key) ?: $this; + // 删除系统设置缓存 + Cache::rm('bargain_setting_' . self::$wxapp_id); + return $model->save([ + 'key' => $key, + 'describe' => $this->describe[$key], + 'values' => $values, + 'wxapp_id' => self::$wxapp_id, + ]) !== false; + } + +} \ No newline at end of file diff --git a/source/application/store/model/bargain/Task.php b/source/application/store/model/bargain/Task.php new file mode 100644 index 0000000..b0acc6d --- /dev/null +++ b/source/application/store/model/bargain/Task.php @@ -0,0 +1,59 @@ +setBaseQuery($this->alias, [ + ['goods', 'goods_id'], + ['user', 'user_id'], + ]); + // 检索查询条件 + if (!empty($search)) { + $this->where(function ($query) use ($search) { + $query->whereOr('goods.goods_name', 'like', "%{$search}%"); + $query->whereOr('user.nickName', 'like', "%{$search}%"); + }); + } + // 获取活动列表 + $list = $this->with(['user']) + ->where("{$this->alias}.is_delete", '=', 0) + ->order(["{$this->alias}.create_time" => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + if (!$list->isEmpty()) { + // 设置商品数据 + $list = GoodsService::setGoodsData($list); + } + return $list; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->allowField(true)->save(['is_delete' => 1]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/bargain/TaskHelp.php b/source/application/store/model/bargain/TaskHelp.php new file mode 100644 index 0000000..43a1383 --- /dev/null +++ b/source/application/store/model/bargain/TaskHelp.php @@ -0,0 +1,31 @@ +with(['user']) + ->where('task_id', '=', $task_id) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + return $list; + } + +} \ No newline at end of file diff --git a/source/application/store/model/dealer/Apply.php b/source/application/store/model/dealer/Apply.php new file mode 100644 index 0000000..af7bb44 --- /dev/null +++ b/source/application/store/model/dealer/Apply.php @@ -0,0 +1,69 @@ +alias('apply') + ->field('apply.*, user.nickName, user.avatarUrl') + ->with(['referee']) + ->join('user', 'user.user_id = apply.user_id') + ->order(['apply.create_time' => 'desc']); + // 查询条件 + !empty($search) && $this->where('user.nickName|apply.real_name|apply.mobile', 'like', "%$search%"); + // 获取列表数据 + return $this->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 分销商入驻审核 + * @param $data + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function submit($data) + { + if ($data['apply_status'] == '30' && empty($data['reject_reason'])) { + $this->error = '请填写驳回原因'; + return false; + } + $this->startTrans(); + if ($data['apply_status'] == '20') { + // 新增分销商用户 + User::add($this['user_id'], [ + 'real_name' => $this['real_name'], + 'mobile' => $this['mobile'], + 'referee_id' => $this['referee_id'], + ]); + } + // 更新申请记录 + $data['audit_time'] = time(); + $this->allowField(true)->save($data); + // 发送模板消息 + (new Message)->dealer($this); + $this->commit(); + return true; + } + +} \ No newline at end of file diff --git a/source/application/store/model/dealer/Capital.php b/source/application/store/model/dealer/Capital.php new file mode 100644 index 0000000..7822c5a --- /dev/null +++ b/source/application/store/model/dealer/Capital.php @@ -0,0 +1,15 @@ + 1 && $this->where('first_user_id|second_user_id|third_user_id', '=', $user_id); + $is_settled > -1 && $this->where('is_settled', '=', !!$is_settled); + !empty($search) && $this->where('user.nickName', 'like', "%{$search}%"); + // 获取分销商订单列表 + $data = $this->with([ + 'dealer_first.user', + 'dealer_second.user', + 'dealer_third.user' + ]) + ->order(['create_time' => 'desc']) + ->paginate(10, false, [ + 'query' => \request()->request() + ]); + if ($data->isEmpty()) { + return $data; + } + // 获取订单的主信息 + $with = ['goods' => ['image', 'refund'], 'address', 'user']; + return OrderService::getOrderList($data, 'order_master', $with); + } + +} \ No newline at end of file diff --git a/source/application/store/model/dealer/Referee.php b/source/application/store/model/dealer/Referee.php new file mode 100644 index 0000000..13a94a8 --- /dev/null +++ b/source/application/store/model/dealer/Referee.php @@ -0,0 +1,76 @@ + -1 && $this->where('m.level', '=', $level); + return $this->alias('m') + ->join('user', 'user.user_id = m.user_id') + ->where('m.dealer_id', '=', $dealerId) + ->where('user.is_delete', '=', 0) + ->column('m.user_id'); + } + + /** + * 获取指定用户的推荐人列表 + * @param $userId + * @return false|\PDOStatement|string|\think\Collection + */ + public static function getRefereeList($userId) + { + return (new static)->with(['dealer1'])->where('user_id', '=', $userId)->select(); + } + + /** + * 清空下级成员推荐关系 + * @param $dealerId + * @param int $level + * @return int + */ + public function onClearTeam($dealerId, $level = -1) + { + $level > -1 && $this->where('level', '=', $level); + return $this->where('dealer_id', '=', $dealerId)->delete(); + } + + /** + * 清空上级推荐关系 + * @param $userId + * @param int $level + * @return int + */ + public function onClearReferee($userId, $level = -1) + { + $level > -1 && $this->where('level', '=', $level); + return $this->where('user_id', '=', $userId)->delete(); + } + + /** + * 清空2-3级推荐人的关系记录 + * @param $teamIds + * @return int + */ + public function onClearTop($teamIds) + { + return $this->where('user_id', 'in', $teamIds) + ->where('level', 'in', [2, 3]) + ->delete(); + } + +} \ No newline at end of file diff --git a/source/application/store/model/dealer/Setting.php b/source/application/store/model/dealer/Setting.php new file mode 100644 index 0000000..237ffbf --- /dev/null +++ b/source/application/store/model/dealer/Setting.php @@ -0,0 +1,111 @@ + '基础设置', + 'condition' => '分销商条件', + 'commission' => '佣金设置', + 'settlement' => '结算', + 'words' => '自定义文字', + 'license' => '申请协议', + 'background' => '页面背景图', + 'template_msg' => '模板消息', + 'qrcode' => '分销海报', + ]; + + /** + * 更新系统设置 + * @param $data + * @return bool + * @throws \think\exception\PDOException + */ + public function edit($data) + { + $this->startTrans(); + try { + foreach ($data as $key => $values) + $this->saveValues($key, $values); + $this->commit(); + // 删除系统设置缓存 + Cache::rm('dealer_setting_' . self::$wxapp_id); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 保存设置项 + * @param $key + * @param $values + * @return false|int + * @throws BaseException + * @throws \think\exception\DbException + */ + private function saveValues($key, $values) + { + $model = self::detail($key) ?: new self; + // 数据验证 + if (!$this->validValues($key, $values)) { + throw new BaseException(['msg' => $this->error]); + } + return $model->save([ + 'key' => $key, + 'describe' => $this->describe[$key], + 'values' => $values, + 'wxapp_id' => self::$wxapp_id, + ]); + } + + /** + * 数据验证 + * @param $key + * @param $values + * @return bool + */ + private function validValues($key, $values) + { + if ($key === 'settlement') { + // 验证结算方式 + return $this->validSettlement($values); + } +// if ($key === 'condition') { +// // 验证分销商条件 +// return $this->validCondition($values); +// } + return true; + } + + /** + * 验证结算方式 + * @param $values + * @return bool + */ + private function validSettlement($values) + { + if (!isset($values['pay_type']) || empty($values['pay_type'])) { + $this->error = '请设置 结算-提现方式'; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/store/model/dealer/User.php b/source/application/store/model/dealer/User.php new file mode 100644 index 0000000..856a08a --- /dev/null +++ b/source/application/store/model/dealer/User.php @@ -0,0 +1,165 @@ +alias('dealer') + ->field('dealer.*, user.nickName, user.avatarUrl') + ->with(['referee']) + ->join('user', 'user.user_id = dealer.user_id') + ->where('dealer.is_delete', '=', 0) + ->order(['dealer.create_time' => 'desc']); + // 查询条件 + !empty($search) && $this->where('user.nickName|dealer.real_name|dealer.mobile', 'like', "%$search%"); + // 获取列表数据 + return $this->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 编辑分销商用户 + * @param $data + * @return bool + */ + public function edit($data) + { + return $this->allowField(true)->save($data) !== false; + } + + /** + * 删除分销商用户 + * @return mixed + */ + public function setDelete() + { + return $this->transaction(function () { + // 获取一级团队成员ID集 + $RefereeModel = new RefereeModel; + $team1Ids = $RefereeModel->getTeamUserIds($this['user_id'], 1); + if (!empty($team1Ids)) { + // 一级团队成员归属到平台 + $this->setFromplatform($team1Ids); + // 一级推荐人ID + $referee1Id = RefereeModel::getRefereeUserId($this['user_id'], 1, true); + if ($referee1Id > 0) { + // 一级推荐人的成员数量(二级) + $this->setDecTeamNum($referee1Id, 2, count($team1Ids)); + // 一级推荐人的成员数量(三级) + $team2Ids = $RefereeModel->getTeamUserIds($this['user_id'], 2); + !empty($team2Ids) && $this->setDecTeamNum($referee1Id, 3, count($team2Ids)); + // 二级推荐人的成员数量(三级) + $referee2Id = RefereeModel::getRefereeUserId($this['user_id'], 2, true); + $referee2Id > 0 && $this->setDecTeamNum($referee2Id, 3, count($team1Ids)); + // 清空分销商下级成员与上级推荐人的关系记录 + $RefereeModel->onClearTop(array_merge($team1Ids, $team2Ids)); + } + } + // 清空下级推荐记录 + $RefereeModel->onClearTeam($this['user_id']); + // 标记当前分销商记录为已删除 + return $this->delete(); + }); + } + + /** + * 一级团队成员归属到平台 + * @param $userIds + * @return false|int + */ + private function setFromplatform($userIds) + { + return $this->isUpdate(true)->save( + ['referee_id' => 0], + ['user_id' => ['in', $userIds], 'is_delete' => 0] + ); + } + + /** + * 删除用户的上级推荐关系 + * @param $userId + * @return bool + * @throws \think\Exception + */ + public function onDeleteReferee($userId) + { + // 获取推荐人列表 + $list = RefereeModel::getRefereeList($userId); + if (!$list->isEmpty()) { + // 递减推荐人的下级成员数量 + foreach ($list as $item) { + $item['dealer1'] && $this->setDecTeamNum($item['dealer_id'], $item['level'], 1); + } + // 清空上级推荐关系 + (new RefereeModel)->onClearReferee($userId); + } + return true; + } + + /** + * 递减分销商成员数量 + * @param $dealerId + * @param $level + * @param $number + * @return int|true + * @throws \think\Exception + */ + private function setDecTeamNum($dealerId, $level, $number) + { + $field = [1 => 'first_num', 2 => 'second_num', 3 => 'third_num']; + return $this->where('user_id', '=', $dealerId) + ->where('is_delete', '=', 0) + ->setDec($field[$level], $number); + } + + /** + * 提现打款成功:累积提现佣金 + * @param $user_id + * @param $money + * @return false|int + * @throws \think\exception\DbException + */ + public static function totalMoney($user_id, $money) + { + $model = self::detail($user_id); + return $model->save([ + 'freeze_money' => $model['freeze_money'] - $money, + 'total_money' => $model['total_money'] + $money, + ]); + } + + /** + * 提现驳回:解冻分销商资金 + * @param $user_id + * @param $money + * @return false|int + * @throws \think\exception\DbException + */ + public static function backFreezeMoney($user_id, $money) + { + $model = self::detail($user_id); + return $model->save([ + 'money' => $model['money'] + $money, + 'freeze_money' => $model['freeze_money'] - $money, + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/dealer/Withdraw.php b/source/application/store/model/dealer/Withdraw.php new file mode 100644 index 0000000..a9c591a --- /dev/null +++ b/source/application/store/model/dealer/Withdraw.php @@ -0,0 +1,152 @@ + 0 ? date('Y-m-d H:i:s', $value) : 0; + } + + /** + * 获取器:打款方式 + * @param $value + * @return mixed + */ + public function getPayTypeAttr($value) + { + return ['text' => $this->payType[$value], 'value' => $value]; + } + + /** + * 获取分销商提现列表 + * @param null $user_id + * @param int $apply_status + * @param int $pay_type + * @param string $search + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList($user_id = null, $apply_status = -1, $pay_type = -1, $search = '') + { + // 构建查询规则 + $this->alias('withdraw') + ->with(['user']) + ->field('withdraw.*, dealer.real_name, dealer.mobile, user.nickName, user.avatarUrl') + ->join('user', 'user.user_id = withdraw.user_id') + ->join('dealer_user dealer', 'dealer.user_id = withdraw.user_id') + ->order(['withdraw.create_time' => 'desc']); + // 查询条件 + $user_id > 0 && $this->where('withdraw.user_id', '=', $user_id); + !empty($search) && $this->where('dealer.real_name|dealer.mobile', 'like', "%$search%"); + $apply_status > 0 && $this->where('withdraw.apply_status', '=', $apply_status); + $pay_type > 0 && $this->where('withdraw.pay_type', '=', $pay_type); + // 获取列表数据 + return $this->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 分销商提现审核 + * @param $data + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function submit($data) + { + if ($data['apply_status'] == '30' && empty($data['reject_reason'])) { + $this->error = '请填写驳回原因'; + return false; + } + // 更新申请记录 + $data['audit_time'] = time(); + $this->allowField(true)->save($data); + // 提现驳回:解冻分销商资金 + $data['apply_status'] == '30' && User::backFreezeMoney($this['user_id'], $this['money']); + // 发送模板消息 + (new Message)->withdraw($this); + return true; + } + + /** + * 确认已打款 + * @return bool + * @throws \think\exception\PDOException + */ + public function money() + { + $this->startTrans(); + try { + // 更新申请状态 + $this->allowField(true)->save([ + 'apply_status' => 40, + 'audit_time' => time(), + ]); + // 更新分销商累积提现佣金 + User::totalMoney($this['user_id'], $this['money']); + // 记录分销商资金明细 + Capital::add([ + 'user_id' => $this['user_id'], + 'flow_type' => 20, + 'money' => -$this['money'], + 'describe' => '申请提现', + ]); + // 发送模板消息 + (new Message)->withdraw($this); + // 事务提交 + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 分销商提现:微信支付企业付款 + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function wechatPay() + { + // 微信用户信息 + $user = $this['user']['user']; + // 生成付款订单号 + $orderNO = OrderService::createOrderNo(); + // 付款描述 + $desc = '分销商提现付款'; + // 微信支付api:企业付款到零钱 + $wxConfig = WxappModel::getWxappCache(); + $WxPay = new WxPay($wxConfig); + // 请求付款api + if ($WxPay->transfers($orderNO, $user['open_id'], $this['money'], $desc)) { + // 确认已打款 + $this->money(); + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/source/application/store/model/recharge/Order.php b/source/application/store/model/recharge/Order.php new file mode 100644 index 0000000..7a9d169 --- /dev/null +++ b/source/application/store/model/recharge/Order.php @@ -0,0 +1,61 @@ +setQueryWhere($query); + // 获取列表数据 + return $this->with(['user', 'order_plan']) + ->alias('order') + ->field('order.*') + ->join('user', 'user.user_id = order.user_id') + ->order(['order.create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + + /** + * 设置查询条件 + * @param $query + */ + private function setQueryWhere($query) + { + // 设置默认的检索数据 + $params = $this->setQueryDefaultValue($query, [ + 'user_id' => 0, + 'recharge_type' => '-1', + 'pay_status' => '-1', + ]); + // 用户ID + $params['user_id'] > 0 && $this->where('order.user_id', '=', $params['user_id']); + // 用户昵称/订单号 + !empty($params['search']) && $this->where('order.order_no|user.nickName', 'like', "%{$params['search']}%"); + // 充值方式 + $params['recharge_type'] > -1 && $this->where('order.recharge_type', '=', (int)$params['recharge_type']); + // 支付状态 + $params['pay_status'] > -1 && $this->where('order.pay_status', '=', (int)$params['pay_status']); + // 起始时间 + !empty($params['start_time']) && $this->where('order.create_time', '>=', strtotime($params['start_time'])); + // 截止时间 + !empty($params['end_time']) && $this->where('order.create_time', '<', strtotime($params['end_time']) + 86400); + } + +} \ No newline at end of file diff --git a/source/application/store/model/recharge/OrderPlan.php b/source/application/store/model/recharge/OrderPlan.php new file mode 100644 index 0000000..047fe56 --- /dev/null +++ b/source/application/store/model/recharge/OrderPlan.php @@ -0,0 +1,15 @@ +where('is_delete', '=', 0) + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + + /** + * 添加新记录 + * @param $data + * @return false|int + */ + public function add($data) + { + $data['wxapp_id'] = self::$wxapp_id; + return $this->allowField(true)->save($data); + } + + /** + * 更新记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + return $this->allowField(true)->save($data) !== false; + } + + /** + * 删除记录 (软删除) + * @return bool|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]) !== false; + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharing/Active.php b/source/application/store/model/sharing/Active.php new file mode 100644 index 0000000..ed35fb8 --- /dev/null +++ b/source/application/store/model/sharing/Active.php @@ -0,0 +1,30 @@ + 0 && $this->where('active_id', '=', $active_id); + return $this->with(['goods.image.file', 'user']) + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} diff --git a/source/application/store/model/sharing/ActiveUsers.php b/source/application/store/model/sharing/ActiveUsers.php new file mode 100644 index 0000000..7e543a3 --- /dev/null +++ b/source/application/store/model/sharing/ActiveUsers.php @@ -0,0 +1,30 @@ +with(['sharingOrder.address', 'user']) + ->where('active_id', '=', $active_id) + ->order(['create_time' => 'asc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} diff --git a/source/application/store/model/sharing/Category.php b/source/application/store/model/sharing/Category.php new file mode 100644 index 0000000..949394d --- /dev/null +++ b/source/application/store/model/sharing/Category.php @@ -0,0 +1,79 @@ +deleteCache(); + return $this->allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return bool|int + */ + public function edit($data) + { + $this->deleteCache(); + return $this->allowField(true)->save($data); + } + + /** + * 删除商品分类 + * @param $category_id + * @return bool|int + */ + public function remove($category_id) + { + // 判断是否存在商品 + if ($goodsCount = (new Goods)->getGoodsTotal(['category_id' => $category_id])) { + $this->error = '该分类下存在' . $goodsCount . '个商品,不允许删除'; + return false; + } + // 判断是否存在子分类 + if ((new self)->where(['parent_id' => $category_id])->count()) { + $this->error = '该分类下存在子分类,请先删除'; + return false; + } + $this->deleteCache(); + return $this->delete(); + } + + /** + * 删除缓存 + * @return bool + */ + private function deleteCache() + { + return Cache::rm('sharing_category_' . self::$wxapp_id); + } + +} diff --git a/source/application/store/model/sharing/Comment.php b/source/application/store/model/sharing/Comment.php new file mode 100644 index 0000000..8681910 --- /dev/null +++ b/source/application/store/model/sharing/Comment.php @@ -0,0 +1,91 @@ +save(['is_delete' => 1]); + } + + /** + * 获取评价总数量 + * @return int|string + */ + public function getCommentTotal() + { + return $this->where(['is_delete' => 0])->count(); + } + + /** + * 更新记录 + * @param $data + * @return bool + * @throws \think\exception\PDOException + */ + public function edit($data) + { + // 开启事务 + $this->startTrans(); + try { + // 删除评价图片 + $this->image()->delete(); + // 添加评论图片 + isset($data['images']) && $this->addCommentImages($data['images']); + // 是否为图片评价 + $data['is_picture'] = !$this->image()->select()->isEmpty(); + // 更新评论记录 + $this->allowField(true)->save($data); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 添加评论图片 + * @param $images + * @return int + */ + private function addCommentImages($images) + { + $data = array_map(function ($image_id) { + return [ + 'image_id' => $image_id, + 'wxapp_id' => self::$wxapp_id + ]; + }, $images); + return $this->image()->saveAll($data); + } + + /** + * 获取评价列表 + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList() + { + return $this->with(['user', 'orderM', 'OrderGoods']) + ->where('is_delete', '=', 0) + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharing/CommentImage.php b/source/application/store/model/sharing/CommentImage.php new file mode 100644 index 0000000..98d4f3e --- /dev/null +++ b/source/application/store/model/sharing/CommentImage.php @@ -0,0 +1,14 @@ +error = '请上传商品图片'; + return false; + } + $data['content'] = isset($data['content']) ? $data['content'] : ''; + $data['wxapp_id'] = $data['sku']['wxapp_id'] = self::$wxapp_id; + // 开启事务 + $this->startTrans(); + try { + // 添加商品 + $this->allowField(true)->save($data); + // 商品规格 + $this->addGoodsSpec($data); + // 商品图片 + $this->addGoodsImages($data['images']); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 添加商品图片 + * @param $images + * @return int + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + private function addGoodsImages($images) + { + $this->image()->delete(); + $data = array_map(function ($image_id) { + return [ + 'image_id' => $image_id, + 'wxapp_id' => self::$wxapp_id + ]; + }, $images); + return $this->image()->saveAll($data); + } + + /** + * 编辑商品 + * @param $data + * @return bool + * @throws \think\exception\PDOException + */ + public function edit($data) + { + if (!isset($data['images']) || empty($data['images'])) { + $this->error = '请上传商品图片'; + return false; + } + $data['content'] = isset($data['content']) ? $data['content'] : ''; + $data['wxapp_id'] = $data['sku']['wxapp_id'] = self::$wxapp_id; + // 开启事务 + $this->startTrans(); + try { + // 保存商品 + $this->allowField(true)->save($data); + // 商品规格 + $this->addGoodsSpec($data, true); + // 商品图片 + $this->addGoodsImages($data['images']); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->rollback(); + $this->error = $e->getMessage(); + return false; + } + } + + /** + * 添加商品规格 + * @param $data + * @param $isUpdate + * @throws \Exception + */ + private function addGoodsSpec($data, $isUpdate = false) + { + // 更新模式: 先删除所有规格 + $model = new GoodsSku; + $isUpdate && $model->removeAll($this['goods_id']); + // 添加规格数据 + if ($data['spec_type'] == '10') { + // 单规格 + $this->sku()->save($data['sku']); + } else if ($data['spec_type'] == '20') { + // 添加商品与规格关系记录 + $model->addGoodsSpecRel($this['goods_id'], $data['spec_many']['spec_attr']); + // 添加商品sku + $model->addSkuList($this['goods_id'], $data['spec_many']['spec_list']); + } + } + + /** + * 修改商品状态 + * @param $state + * @return false|int + */ + public function setStatus($state) + { + return $this->allowField(true)->save(['goods_status' => $state ? 10 : 20]) !== false; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->allowField(true)->save(['is_delete' => 1]); + } + + /** + * 获取当前商品总数 + * @param array $where + * @return int|string + * @throws \think\Exception + */ + public function getGoodsTotal($where = []) + { + return $this->where('is_delete', '=', 0)->where($where)->count(); + } + +} diff --git a/source/application/store/model/sharing/GoodsImage.php b/source/application/store/model/sharing/GoodsImage.php new file mode 100644 index 0000000..ecde950 --- /dev/null +++ b/source/application/store/model/sharing/GoodsImage.php @@ -0,0 +1,14 @@ + $item['spec_sku_id'], + 'goods_id' => $goods_id, + 'wxapp_id' => self::$wxapp_id, + ]); + } + return $this->allowField(true)->saveAll($data); + } + + /** + * 添加商品规格关系记录 + * @param $goods_id + * @param $spec_attr + * @return array|false + * @throws \Exception + */ + public function addGoodsSpecRel($goods_id, $spec_attr) + { + $data = []; + array_map(function ($val) use (&$data, $goods_id) { + array_map(function ($item) use (&$val, &$data, $goods_id) { + $data[] = [ + 'goods_id' => $goods_id, + 'spec_id' => $val['group_id'], + 'spec_value_id' => $item['item_id'], + 'wxapp_id' => self::$wxapp_id, + ]; + }, $val['spec_items']); + }, $spec_attr); + $model = new GoodsSpecRel; + return $model->saveAll($data); + } + + /** + * 移除指定商品的所有sku + * @param $goods_id + * @return int + */ + public function removeAll($goods_id) + { + $model = new GoodsSpecRel; + $model->where('goods_id','=', $goods_id)->delete(); + return $this->where('goods_id','=', $goods_id)->delete(); + } + +} diff --git a/source/application/store/model/sharing/GoodsSpecRel.php b/source/application/store/model/sharing/GoodsSpecRel.php new file mode 100644 index 0000000..8014186 --- /dev/null +++ b/source/application/store/model/sharing/GoodsSpecRel.php @@ -0,0 +1,14 @@ +setWhere($query); + // 获取数据列表 + return $this->with(['active', 'goods.image', 'address', 'user']) + ->alias('order') + ->field('order.*, active.status as active_status') + ->join('user', 'user.user_id = order.user_id', 'LEFT') + ->join('sharing_active active', 'order.active_id = active.active_id', 'LEFT') + ->where($this->transferDataType($dataType)) + ->where('order.is_delete', '=', 0) + ->order(['order.create_time' => 'desc']) + ->paginate(10, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 订单列表(全部) + * @param $dataType + * @param array $query + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListAll($dataType, $query = []) + { + // 检索查询条件 + !empty($query) && $this->setWhere($query); + // 获取数据列表 + return $this->with(['goods.image', 'address', 'user', 'extract', 'extract_shop']) + ->alias('order') + ->field('order.*, active.status as active_status') + ->join('sharing_active active', 'order.active_id = active.active_id', 'LEFT') + ->where($this->transferDataType($dataType)) + ->where('order.is_delete', '=', 0) + ->order(['create_time' => 'desc']) + ->select(); + } + + /** + * 订单导出 + * @param $dataType + * @param $query + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function exportList($dataType, $query) + { + // 获取订单列表 + $list = $this->getListAll($dataType, $query); + // 导出csv文件 + return (new Exportservice)->orderList($list); + } + + /** + * 批量发货模板 + */ + public function deliveryTpl() + { + return (new Exportservice)->deliveryTpl(); + } + + /** + * 设置检索查询条件 + * @param $query + */ + private function setWhere($query) + { + if (isset($query['search']) && !empty($query['search'])) { + $this->where('order_no|user.nickName', 'like', '%' . trim($query['search']) . '%'); + } + if (isset($query['start_time']) && !empty($query['start_time'])) { + $this->where('order.create_time', '>=', strtotime($query['start_time'])); + } + if (isset($query['end_time']) && !empty($query['end_time'])) { + $this->where('order.create_time', '<', strtotime($query['end_time']) + 86400); + } + if (isset($query['active_id']) && $query['active_id'] > 0) { + $this->where('order.active_id', '=', (int)$query['active_id']); + } + if (isset($query['delivery_type']) && !empty($query['delivery_type'])) { + $query['delivery_type'] > -1 && $this->where('delivery_type', '=', $query['delivery_type']); + } + if (isset($query['extract_shop_id']) && !empty($query['extract_shop_id'])) { + $query['extract_shop_id'] > -1 && $this->where('extract_shop_id', '=', $query['extract_shop_id']); + } + } + + /** + * 转义数据类型条件 + * @param $dataType + * @return array + */ + private function transferDataType($dataType) + { + // 数据类型 + $filter = []; + switch ($dataType) { + case 'all': + // 全部 + $filter = []; + break; + case 'pay': + // 待支付 + $filter = ['pay_status' => 10, 'order_status' => 10]; + break; + case 'sharing'; + // 拼团中 + $filter['active.status'] = 10; + break; + case 'sharing_succeed'; + // 拼团成功 + $filter['active.status'] = 20; + break; + case 'sharing_fail'; + // 拼团失败 + $filter['active.status'] = 30; + break; + case 'delivery': + // 待发货 + $this->where('IF ( (`order`.`order_type` = 20), (`active`.`status` = 20), TRUE)'); + $filter = [ + 'pay_status' => 20, + 'delivery_status' => 10, + 'order_status' => ['in', [10, 21]] + ]; + break; + case 'receipt': + // 待收货 + $filter = [ + 'pay_status' => 20, + 'delivery_status' => 20, + 'receipt_status' => 10 + ]; + break; + case 'complete': + // 已完成 + $filter = ['order_status' => 30]; + break; + case 'cancel': + // 已取消 + $filter = ['order_status' => 20]; + break; + } + return $filter; + } + + /** + * 确认发货(单独订单) + * @param $data + * @return array|bool|false + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + * @throws \Exception + */ + public function delivery($data) + { + // 转义为订单列表 + $orderList = [$this]; + // 验证订单是否满足发货条件 + if (!$this->verifyDelivery($orderList)) { + return false; + } + // 整理更新的数据 + $updateList = [[ + 'order_id' => $this['order_id'], + 'express_id' => $data['express_id'], + 'express_no' => $data['express_no'] + ]]; + // 更新订单发货状态 + if ($status = $this->updateToDelivery($updateList)) { + // 获取已发货的订单 + $completed = self::detail($this['order_id'], ['user', 'address', 'goods', 'express']); + // 发送消息通知 + $this->sendDeliveryMessage([$completed]); + } + return $status; + } + + /** + * 批量发货 + * @param $data + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function batchDelivery($data) + { + // 获取csv文件中的数据 + if (!$csvData = $this->getCsvData()) { + return false; + } + // 整理订单id集 + $orderNos = helper::getArrayColumn($csvData, 0); + // 获取订单列表数据 + $orderList = helper::arrayColumn2Key($this->getListByOrderNos($orderNos), 'order_no'); + // 验证订单是否存在 + $tempArr = array_values(array_diff($orderNos, array_keys($orderList))); + if (!empty($tempArr)) { + $this->error = "订单号[{$tempArr[0]}] 不存在!"; + return false; + } + // 整理物流单号 + $updateList = []; + foreach ($csvData as $item) { + $updateList[] = [ + 'order_id' => $orderList[$item[0]]['order_id'], + 'express_id' => $data['express_id'], + 'express_no' => $item[1], + ]; + } + // 验证订单是否满足发货条件 + if (!$this->verifyDelivery($orderList)) { + return false; + } + // 更新订单发货状态(批量) + if ($status = $this->updateToDelivery($updateList)) { + // 获取已发货的订单 + $completed = $this->getListByOrderNos($orderNos, ['user', 'address', 'goods', 'express']); + // 发送消息通知 + $this->sendDeliveryMessage($completed); + } + return $status; + } + + /** + * 确认发货后发送消息通知 + * @param array|\think\Collection $orderList + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + private function sendDeliveryMessage($orderList) + { + // 实例化消息通知服务类 + $Service = new MessageService; + foreach ($orderList as $item) { + // 发送消息通知 + $Service->delivery($item, OrderTypeEnum::SHARING); + } + return true; + } + + /** + * 更新订单发货状态(批量) + * @param $orderList + * @return array|false + * @throws \Exception + */ + private function updateToDelivery($orderList) + { + $data = []; + foreach ($orderList as $item) { + $data[] = [ + 'order_id' => $item['order_id'], + 'express_no' => $item['express_no'], + 'express_id' => $item['express_id'], + 'delivery_status' => 20, + 'delivery_time' => time(), + ]; + } + return $this->isUpdate()->saveAll($data); + } + + /** + * 验证订单是否满足发货条件 + * @param $orderList + * @return bool + */ + private function verifyDelivery($orderList) + { + foreach ($orderList as $order) { + if ( + $order['pay_status']['value'] != 20 + || $order['delivery_type']['value'] != DeliveryTypeEnum::EXPRESS + || $order['delivery_status']['value'] != 10 + || ( + // 拼团订单验证拼单状态 + $order['order_type']['value'] == 20 + && $order['active']['status']['value'] != 20 + ) + ) { + $this->error = "订单号[{$order['order_no']}] 不满足发货条件!"; + return false; + } + } + return true; + } + + /** + * 获取csv文件中的数据 + * @return array|bool + */ + private function getCsvData() + { + // 获取表单上传文件 例如上传了001.jpg + $file = \request()->file('iFile'); + if (empty($file)) { + $this->error = '请上传发货模板'; + return false; + } + // 设置区域信息 + setlocale(LC_ALL, 'zh_CN'); + // 打开上传的文件 + $csvFile = fopen($file->getInfo()['tmp_name'], 'r'); + // 忽略第一行(csv标题) + fgetcsv($csvFile); + // 遍历并记录订单信息 + $orderList = []; + while ($item = fgetcsv($csvFile)) { + if (!isset($item[0]) || empty($item[0]) || !isset($item[1]) || empty($item[1])) { + $this->error = '模板文件数据不合法'; + return false; + } + $orderList[] = $item; + } + if (empty($orderList)) { + $this->error = '模板文件中没有订单数据'; + return false; + } + return $orderList; + } + + /** + * 修改订单价格 + * @param $data + * @return bool + */ + public function updatePrice($data) + { + if ($this['pay_status']['value'] != 10) { + $this->error = '该订单不合法'; + return false; + } + // 实际付款金额 + $payPrice = bcadd($data['update_price'], $data['update_express_price'], 2); + if ($payPrice <= 0) { + $this->error = '订单实付款价格不能为0.00元'; + return false; + } + return $this->save([ + 'order_no' => $this->orderNo(), // 修改订单号, 否则微信支付提示重复 + 'order_price' => $data['update_price'], + 'pay_price' => $payPrice, + 'update_price' => helper::bcsub($data['update_price'], helper::bcsub($this['total_price'], $this['coupon_money'])), + 'express_price' => $data['update_express_price'] + ]) !== false; + } + + /** + * 审核:用户取消订单 + * @param $data + * @return bool + */ + public function confirmCancel($data) + { + // 判断订单是否有效 + if ($this['pay_status']['value'] != 20) { + $this->error = '该订单不合法'; + return false; + } + // 订单取消事件 + return $this->transaction(function () use ($data) { + if ($data['is_cancel'] == true) { + // 执行退款操作 + (new RefundService)->execute($this); + // 回退商品库存 + (new OrderGoods)->backGoodsStock($this['goods'], true); + // 回退用户优惠券 + $this['coupon_id'] > 0 && UserCouponModel::setIsUse($this['coupon_id'], false); + // 回退用户积分 + $User = UserModel::detail($this['user_id']); + $describe = "订单取消:{$this['order_no']}"; + $this['points_num'] > 0 && $User->setIncPoints($this['points_num'], $describe); + } + // 更新订单状态 + return $this->save(['order_status' => $data['is_cancel'] ? 20 : 10]); + }); + } + + /** + * 拼团失败手动退款 + * @return bool|false|int + * @throws \app\common\exception\BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function refund() + { + if ( + $this['order_type']['value'] != 20 + || $this['pay_status']['value'] != 20 + || $this['active']['status']['value'] != 30 + || $this['is_refund'] == 1 + ) { + $this->error = '该订单不合法'; + return false; + } + // 执行退款操作 + (new RefundService)->execute($this); + // 更新订单状态 + return $this->save(['order_status' => 20, 'is_refund' => 1]); + } + + /** + * 获取已付款订单总数 (可指定某天) + * @param null $day + * @return int|string + * @throws \think\Exception + */ + public function getPayOrderTotal($day = null) + { + $filter = ['pay_status' => 20]; + if (!is_null($day)) { + $startTime = strtotime($day); + $filter['pay_time'] = [ + ['>=', $startTime], + ['<', $startTime + 86400], + ]; + } + return $this->getOrderTotal($filter); + } + + /** + * 获取订单总数量 + * @param array $filter + * @return int|string + * @throws \think\Exception + */ + public function getOrderTotal($filter = []) + { + return $this->where($filter) + ->where('is_delete', '=', 0) + ->count(); + } + + /** + * 获取某天的总销售额 + * @param $day + * @return float|int + */ + public function getOrderTotalPrice($day) + { + $startTime = strtotime($day); + return $this->where('pay_time', '>=', $startTime) + ->where('pay_time', '<', $startTime + 86400) + ->where('pay_status', '=', 20) + ->where('is_delete', '=', 0) + ->sum('pay_price'); + } + + /** + * 获取某天的下单用户数 + * @param $day + * @return float|int + */ + public function getPayOrderUserTotal($day) + { + $startTime = strtotime($day); + $userIds = $this->distinct(true) + ->where('pay_time', '>=', $startTime) + ->where('pay_time', '<', $startTime + 86400) + ->where('pay_status', '=', 20) + ->where('is_delete', '=', 0) + ->column('user_id'); + return count($userIds); + } + +} diff --git a/source/application/store/model/sharing/OrderAddress.php b/source/application/store/model/sharing/OrderAddress.php new file mode 100644 index 0000000..0909921 --- /dev/null +++ b/source/application/store/model/sharing/OrderAddress.php @@ -0,0 +1,15 @@ +where('order.order_no', 'like', "%{$query['order_no']}%"); + } + // 查询条件:起始日期 + if (isset($query['start_time']) && !empty($query['start_time'])) { + $this->where('m.create_time', '>=', strtotime($query['start_time'])); + } + // 查询条件:截止日期 + if (isset($query['end_time']) && !empty($query['end_time'])) { + $this->where('m.create_time', '<', strtotime($query['end_time']) + 86400); + } + // 售后类型 + if (isset($query['type']) && $query['type'] > 0) { + $this->where('m.type', '=', $query['type']); + } + // 处理状态 + if (isset($query['state']) && is_numeric($query['state'])) { + $this->where('m.status', '=', $query['state']); + } + // 获取列表数据 + return $this->alias('m') + ->field('m.*, order.order_no') + ->with(['order_goods.image', 'orderMaster', 'user']) + ->join('sharing_order order', 'order.order_id = m.order_id') + ->order(['m.create_time' => 'desc']) + ->paginate(10, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 商家审核 + * @param $data + * @return bool + * @throws \think\exception\PDOException + */ + public function audit($data) + { + if ($data['is_agree'] == 20 && empty($data['refuse_desc'])) { + $this->error = '请输入拒绝原因'; + return false; + } + if ($data['is_agree'] == 10 && empty($data['address_id'])) { + $this->error = '请选择退货地址'; + return false; + } + $this->startTrans(); + try { + // 拒绝申请, 标记售后单状态为已拒绝 + $data['is_agree'] == 20 && $data['status'] = 10; + // 同意换货申请, 标记售后单状态为已完成 + $data['is_agree'] == 10 && $this['type']['value'] == 20 && $data['status'] = 20; + // 更新退款单状态 + $this->allowField(true)->save($data); + // 同意售后申请, 记录退货地址 + if ($data['is_agree'] == 10) { + $model = new OrderRefundAddress; + $model->add($this['order_refund_id'], $data['address_id']); + } + // 订单详情 + $order = Order::detail($this['order_id']); + // 发送模板消息 + (new MessageService)->refund(static::detail($this['order_refund_id']), $order['order_no'], OrderTypeEnum::SHARING); + // 事务提交 + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 确认收货并退款 + * @param $data + * @return bool + * @throws \think\exception\DbException + */ + public function receipt($data) + { + // 订单详情 + $order = Order::detail($this['order_id']); + if ($data['refund_money'] > min($order['pay_price'], $this['order_goods']['total_pay_price'])) { + $this->error = '退款金额不能大于商品实付款金额'; + return false; + } + $this->transaction(function () use ($order, $data) { + // 更新售后单状态 + $this->allowField(true)->save([ + 'refund_money' => $data['refund_money'], + 'is_receipt' => 1, + 'status' => 20 + ]); + // 消减用户的实际消费金额 + // 条件:判断订单是否已结算 + if ($order['is_settled'] == true) { + (new UserModel)->setDecUserExpend($order['user_id'], $data['refund_money']); + } + // 执行原路退款 + (new RefundService)->execute($order, $data['refund_money']); + // 发送模板消息 + (new MessageService)->refund(self::detail($this['order_refund_id']), $order['order_no'], OrderTypeEnum::SHARING); + }); + return true; + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharing/OrderRefundAddress.php b/source/application/store/model/sharing/OrderRefundAddress.php new file mode 100644 index 0000000..5ec22a4 --- /dev/null +++ b/source/application/store/model/sharing/OrderRefundAddress.php @@ -0,0 +1,34 @@ +save([ + 'order_refund_id' => $order_refund_id, + 'name' => $detail['name'], + 'phone' => $detail['phone'], + 'detail' => $detail['detail'], + 'wxapp_id' => self::$wxapp_id + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharing/OrderRefundImage.php b/source/application/store/model/sharing/OrderRefundImage.php new file mode 100644 index 0000000..9a7ac42 --- /dev/null +++ b/source/application/store/model/sharing/OrderRefundImage.php @@ -0,0 +1,15 @@ + '基础设置', + ]; + + /** + * 更新系统设置 + * @param $key + * @param $values + * @return bool + * @throws \think\exception\DbException + */ + public function edit($key, $values) + { + $model = self::detail($key) ?: $this; + // 删除系统设置缓存 + Cache::rm('sharing_setting_' . self::$wxapp_id); + return $model->save([ + 'key' => $key, + 'describe' => $this->describe[$key], + 'values' => $values, + 'wxapp_id' => self::$wxapp_id, + ]) !== false; + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharp/Active.php b/source/application/store/model/sharp/Active.php new file mode 100644 index 0000000..372e205 --- /dev/null +++ b/source/application/store/model/sharp/Active.php @@ -0,0 +1,132 @@ +with(['active_time']) + ->where('is_delete', '=', 0) + ->order(['active_date' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + return $this->getActiveTimeCount($list); + } + + private function getActiveTimeCount($list) + { + foreach ($list as &$item) { + $activeTimeArr = helper::getArrayColumn($item['active_time'], 'active_time'); + $item['active_time_count'] = count($activeTimeArr); + } + return $list; + } + + /** + * 新增记录 + * @param $data + * @return bool + * @throws \Exception + */ + public function add($data) + { + // 表单验证 + if (!$this->onValidate($data)) return false; + // 新增活动 + $data['wxapp_id'] = static::$wxapp_id; + $data['active_date'] = strtotime($data['active_date']); + !isset($data['sharp_goods']) && $data['sharp_goods'] = []; + return $this->transaction(function () use ($data) { + // 新增活动 + $this->allowField(true)->save($data); + // 新增活动场次 + (new ActiveTimeModel)->onBatchAdd( + $this['active_id'], + $data['active_times'], + $data['sharp_goods'] + ); + return true; + }); + } + + /** + * 表单验证 + * @param $data + * @return bool + */ + private function onValidate($data) + { + // 活动日期是否已存在 + if ($this->isExistByActiveDate($data['active_date'])) { + $this->error = '该活动日期已存在'; + return false; + } +// // 验证是否选择商品 +// if (!isset($data['sharp_goods']) || empty($data['sharp_goods'])) { +// $this->error = '您还没有选择秒杀商品'; +// return false; +// } + return true; + } + + /** + * 活动日期是否已存在 + * @param $date + * @return bool + */ + private function isExistByActiveDate($date) + { + return !!(new static)->where('active_date', '=', strtotime($date)) + ->where('is_delete', '=', 0) + ->value('active_id'); + } + + /** + * 修改商品状态 + * @param $state + * @return false|int + */ + public function setStatus($state) + { + return $this->allowField(true)->save(['status' => (int)$state]) !== false; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + // 同步删除场次和商品关联 + (new ActiveTimeModel)->onDeleteByActiveId($this['active_id']); + // 将该活动设置为已删除 + return $this->allowField(true)->save(['is_delete' => 1]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharp/ActiveGoods.php b/source/application/store/model/sharp/ActiveGoods.php new file mode 100644 index 0000000..55ec9f4 --- /dev/null +++ b/source/application/store/model/sharp/ActiveGoods.php @@ -0,0 +1,33 @@ +where('sharp_goods_id', '=', $sharpGoodsId)->delete(); + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharp/ActiveTime.php b/source/application/store/model/sharp/ActiveTime.php new file mode 100644 index 0000000..e24df10 --- /dev/null +++ b/source/application/store/model/sharp/ActiveTime.php @@ -0,0 +1,248 @@ +with(['active']) + ->withCount(['goods']) + ->where('active_id', '=', $activeId) + ->order(['active_time' => 'asc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + return $list; + } + + /** + * 修改商品状态 + * @param $state + * @return false|int + */ + public function setStatus($state) + { + return $this->allowField(true)->save(['status' => (int)$state]) !== false; + } + + /** + * 获取指定会场的所有场次时间 + * @param $activeId + * @return array + */ + public function getActiveTimeData($activeId) + { + return $this->where('active_id', '=', $activeId)->column('active_time'); + } + + /** + * 根据活动场次ID获取商品列表 (格式化后用于编辑页) + * @param $activeTimeId + * @return array + */ + public function getGoodsListByActiveTimeId($activeTimeId) + { + $data = []; + foreach (ActiveGoodsModel::getGoodsListByActiveTimeId($activeTimeId) as $item) { + $data[] = [ + 'goods_id' => $item['sharp_goods_id'], + 'goods_name' => $item['goods']['goods_name'], + 'goods_image' => $item['goods']['goods_image'], + ]; + } + return $data; + } + + /** + * 新增记录 + * @param $activeId + * @param $data + * @return bool|mixed + */ + public function add($activeId, $data) + { + // 表单验证 + if (!$this->onValidate($data)) return false; + // 事务处理 + return $this->transaction(function () use ($activeId, $data) { + // 新增活动场次 + $this->onBatchAdd( + $activeId, + $data['active_times'], + $data['sharp_goods'], + $data['status'] + ); + return true; + }); + } + + /** + * 更新记录 + * @param $data + * @return bool|mixed + */ + public function edit($data) + { + // 验证是否选择商品 + if (!$this->onValidateSharpGoods($data)) { + return false; + } + // 事务处理 + return $this->transaction(function () use ($data) { + // 更新活动场次 + $this->allowField(true)->save($data); + // 更新场次的商品关联记录 + $this->onUpdateActiveGoodsRec($data['sharp_goods']); + return true; + }); + } + + /** + * 更新当前场次的商品关联记录 + * @param $sharpGoodsIds + * @return array|false + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + private function onUpdateActiveGoodsRec($sharpGoodsIds) + { + $saveData = []; + foreach ($sharpGoodsIds as $goodsId) { + $saveData[] = [ + 'active_id' => $this['active_id'], + 'active_time_id' => $this['active_time_id'], + 'sharp_goods_id' => $goodsId, + 'wxapp_id' => static::$wxapp_id, + ]; + } + $this->goods()->delete(); + return (new ActiveGoodsModel)->isUpdate(false)->saveAll($saveData); + } + + /** + * 表单验证 + * @param $data + * @return bool + */ + private function onValidate($data) + { + // 验证是否选择活动场次 + if (!isset($data['active_times']) || empty($data['active_times'])) { + $this->error = '您还没有选择活动场次'; + return false; + } + // 验证是否选择商品 + if (!$this->onValidateSharpGoods($data)) { + return false; + } + return true; + } + + /** + * 验证是否选择商品 + * @param $data + * @return bool + */ + private function onValidateSharpGoods($data) + { + // 验证是否选择商品 + if (!isset($data['sharp_goods']) || empty($data['sharp_goods'])) { + $this->error = '您还没有选择秒杀商品'; + return false; + } + return true; + } + + /** + * 批量新增活动场次 + * @param $activeId + * @param array $times + * @param array $sharpGoodsIds + * @param int $status + * @return bool + * @throws \Exception + */ + public function onBatchAdd($activeId, $times, $sharpGoodsIds, $status = 1) + { + $saveData = []; + foreach ($times as $time) { + $saveData[] = [ + 'active_id' => $activeId, + 'active_time' => (int)$time, + 'status' => (int)$status, + 'wxapp_id' => static::$wxapp_id, + ]; + } + // 批量更新 + $activeTimes = $this->isUpdate(false)->saveAll($saveData); + // 新增活动场次与商品关联关系记录 + if (!empty($sharpGoodsIds)) { + $this->onBatchAddActiveGoodsRec($activeTimes, $sharpGoodsIds); + } + return true; + } + + /** + * 新增活动场次与商品关联记录 + * @param $activeTimes + * @param $sharpGoodsIds + * @return array|false + * @throws \Exception + */ + private function onBatchAddActiveGoodsRec($activeTimes, $sharpGoodsIds) + { + $saveData = []; + foreach ($activeTimes as $item) { + foreach ($sharpGoodsIds as $goodsId) { + $saveData[] = [ + 'active_id' => $item['active_id'], + 'active_time_id' => $item['active_time_id'], + 'sharp_goods_id' => $goodsId, + 'wxapp_id' => static::$wxapp_id, + ]; + } + } + return (new ActiveGoodsModel)->isUpdate(false)->saveAll($saveData); + } + + /** + * 根据活动ID删除全部场次和商品关系 + * @param $activeId + * @return bool + */ + public function onDeleteByActiveId($activeId) + { + $this->where('active_id', '=', $activeId)->delete(); + (new ActiveGoodsModel)->where('active_id', '=', $activeId)->delete(); + return true; + } + + /** + * 删除当前场次 + * @return bool + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function onDelete() + { + $this->delete(); + $this->goods()->delete(); + return true; + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharp/Goods.php b/source/application/store/model/sharp/Goods.php new file mode 100644 index 0000000..8c6ccee --- /dev/null +++ b/source/application/store/model/sharp/Goods.php @@ -0,0 +1,183 @@ +setBaseQuery($this->alias, [ + ['goods', 'goods_id'], + ]); + // 检索查询条件 + if (!empty($search)) { + $this->where('goods.goods_name', 'like', "%{$search}%"); + } + // 获取活动列表 + $list = $this->where("{$this->alias}.is_delete", '=', 0) + ->order(["{$this->alias}.sort" => 'asc', "{$this->alias}.create_time" => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + // 设置商品数据 + return $this->setGoodsListData($list, true); + } + + /** + * 根据商品id集获取商品列表 + * @param array $goodsIds + * @param array $param + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getListByIds($goodsIds, $param = []) + { + // 获取商品列表数据 + $list = parent::getListByIds($goodsIds, $param); + // 整理列表数据并返回 + return $this->setGoodsListData($list, true); + } + + /** + * 添加商品 + * @param $goods + * @param $data + * @return bool + * @throws \Exception + */ + public function add($goods, $data) + { + // 添加商品 + $this->allowField(true)->save(array_merge($data, [ + 'goods_id' => $goods['goods_id'], + 'seckill_stock' => $this->getSeckillStock($goods, $data), + 'wxapp_id' => self::$wxapp_id, + ])); + // 商品规格 + $this->addGoodsSpec($goods, $data); + return true; + } + + /** + * 编辑商品 + * @param $goods + * @param $data + * @return bool + * @throws \Exception + */ + public function edit($goods, $data) + { + // 更新商品 + $this->allowField(true)->save(array_merge($data, [ + 'seckill_stock' => $this->getSeckillStock($goods, $data), + ])); + // 商品规格 + $this->addGoodsSpec($goods, $data, true); + return true; + } + + /** + * 获取总库存数量 + * @param $goods + * @param $data + * @return int + */ + private function getSeckillStock($goods, $data) + { + if ($goods['spec_type'] == '10') { + return $data['sku']['seckill_stock']; + } + $seckillStock = 0; + foreach ($data['spec_many']['spec_list'] as $item) { + $seckillStock += $item['form']['seckill_stock']; + } + return $seckillStock; + } + + /** + * 验证商品ID能否被添加 + * @param $goodsId + * @return bool + */ + public function validateGoodsId($goodsId) + { + if ($goodsId <= 0) { + $this->error = '很抱歉,您还没有选择商品'; + return false; + } + // 验证是否存在秒杀商品 + if ($this->isExistGoodsId($goodsId)) { + $this->error = '很抱歉,该商品已存在,无需重复添加'; + return false; + } + return true; + } + + /** + * 添加商品规格 + * @param $goods + * @param $data + * @param bool $isUpdate + * @return array|false|\think\Model + * @throws \Exception + */ + private function addGoodsSpec($goods, $data, $isUpdate = false) + { + // 更新模式: 先删除所有规格 + $model = new GoodsSkuModel; + $isUpdate && $model->removeAll($this['sharp_goods_id']); + // 添加sku (单规格) + if ($goods['spec_type'] == '10') { + return $this->sku()->save(array_merge($data['sku'], [ + 'wxapp_id' => self::$wxapp_id, + ])); + } + // 添加sku (多规格) + return $model->addSkuList($this['sharp_goods_id'], $data['spec_many']['spec_list']); + } + + /** + * 商品ID是否存在 + * @param $goodsId + * @return bool + */ + public static function isExistGoodsId($goodsId) + { + return !!(new static)->where('goods_id', '=', $goodsId) + ->where('is_delete', '=', 0) + ->value('sharp_goods_id'); + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + // 同步删除活动会场与商品关联记录 + $model = new ActiveGoodsModel; + $model->onDeleteSharpGoods($this['sharp_goods_id']); + return $this->allowField(true)->save(['is_delete' => 1]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharp/GoodsSku.php b/source/application/store/model/sharp/GoodsSku.php new file mode 100644 index 0000000..e60b997 --- /dev/null +++ b/source/application/store/model/sharp/GoodsSku.php @@ -0,0 +1,44 @@ + $item['spec_sku_id'], + 'sharp_goods_id' => $sharpGoodsId, + 'wxapp_id' => self::$wxapp_id, + ]); + } + return $this->allowField(true)->saveAll($data); + } + + /** + * 移除指定商品的所有sku + * @param $sharpGoodsId + * @return int + */ + public function removeAll($sharpGoodsId) + { + return $this->where('sharp_goods_id', '=', $sharpGoodsId)->delete(); + } + +} \ No newline at end of file diff --git a/source/application/store/model/sharp/Setting.php b/source/application/store/model/sharp/Setting.php new file mode 100644 index 0000000..67b0cb6 --- /dev/null +++ b/source/application/store/model/sharp/Setting.php @@ -0,0 +1,43 @@ + '基础设置', + ]; + + /** + * 更新系统设置 + * @param $key + * @param $values + * @return bool + * @throws \think\exception\DbException + */ + public function edit($key, $values) + { + $model = self::detail($key) ?: $this; + // 删除系统设置缓存 + Cache::rm('sharp_setting_' . self::$wxapp_id); + return $model->save([ + 'key' => $key, + 'describe' => $this->describe[$key], + 'values' => $values, + 'wxapp_id' => self::$wxapp_id, + ]) !== false; + } + +} \ No newline at end of file diff --git a/source/application/store/model/store/Access.php b/source/application/store/model/store/Access.php new file mode 100644 index 0000000..c01bb68 --- /dev/null +++ b/source/application/store/model/store/Access.php @@ -0,0 +1,56 @@ +getAll() as $item) { + $jsTree[] = [ + 'id' => $item['access_id'], + 'parent' => $item['parent_id'] > 0 ? $item['parent_id'] : '#', + 'text' => $item['name'], + 'state' => [ + 'selected' => (in_array($item['access_id'], $accessIds) && !$this->hasChildren($item['access_id'])) + ] + ]; + } + return json_encode($jsTree); + } + + /** + * 是否存在子集 + * @param $access_id + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function hasChildren($access_id) + { + foreach (self::getAll() as $item) { + if ($item['parent_id'] == $access_id) + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/source/application/store/model/store/Role.php b/source/application/store/model/store/Role.php new file mode 100644 index 0000000..710f49a --- /dev/null +++ b/source/application/store/model/store/Role.php @@ -0,0 +1,187 @@ +getAll(); + return $this->formatTreeData($all); + } + + /** + * 新增记录 + * @param $data + * @return bool + * @throws \Exception + */ + public function add($data) + { + $data['wxapp_id'] = self::$wxapp_id; + if (empty($data['access'])) { + $this->error = '请选择权限'; + return false; + } + $this->startTrans(); + try { + // 新增角色记录 + $this->allowField(true)->save($data); + // 新增角色权限关系记录 + (new RoleAccess)->add($this['role_id'], $data['access']); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 更新记录 + * @param $data + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function edit($data) + { + if (empty($data['access'])) { + $this->error = '请选择权限'; + return false; + } + // 判断上级角色是否为当前子级 + if ($data['parent_id'] > 0) { + // 获取所有上级id集 + $parentIds = $this->getTopRoleIds($data['parent_id']); + if (in_array($this['role_id'], $parentIds)) { + $this->error = '上级角色不允许设置为当前子角色'; + return false; + } + } + $this->startTrans(); + try { + // 更新角色记录 + $this->allowField(true)->save($data); + // 更新角色权限关系记录 + (new RoleAccess)->edit($this['role_id'], $data['access']); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 获取所有上级id集 + * @param $role_id + * @param null $all + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getTopRoleIds($role_id, &$all = null) + { + static $ids = []; + is_null($all) && $all = $this->getAll(); + foreach ($all as $item) { + if ($item['role_id'] == $role_id && $item['parent_id'] > 0) { + $ids[] = $item['parent_id']; + $this->getTopRoleIds($item['parent_id'], $all); + } + } + return $ids; + } + + /** + * 删除记录 + * @return bool|int + * @throws \think\exception\DbException + */ + public function remove() + { + // 判断是否存在下级角色 + if (self::detail(['parent_id' => $this['role_id']])) { + $this->error = '当前角色下存在子角色,不允许删除'; + return false; + } + // 删除对应的权限关系 + RoleAccess::deleteAll(['role_id' => $this['role_id']]); + return $this->delete(); + } + + /** + * 获取所有角色 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getAll() + { + $data = $this->order(['sort' => 'asc', 'create_time' => 'asc'])->select(); + return $data ? $data->toArray() : []; + } + + /** + * 获取权限列表 + * @param $all + * @param int $parent_id + * @param int $deep + * @return array + */ + private function formatTreeData(&$all, $parent_id = 0, $deep = 1) + { + static $tempTreeArr = []; + foreach ($all as $key => $val) { + if ($val['parent_id'] == $parent_id) { + // 记录深度 + $val['deep'] = $deep; + // 根据角色深度处理名称前缀 + $val['role_name_h1'] = $this->htmlPrefix($deep) . $val['role_name']; + $tempTreeArr[] = $val; + $this->formatTreeData($all, $val['role_id'], $deep + 1); + } + } + return $tempTreeArr; + } + + /** + * 角色名称 html格式前缀 + * @param $deep + * @return string + */ + private function htmlPrefix($deep) + { + // 根据角色深度处理名称前缀 + $prefix = ''; + if ($deep > 1) { + for ($i = 1; $i <= $deep - 1; $i++) { + $prefix .= '   ├ '; + } + $prefix .= ' '; + } + return $prefix; + } + +} \ No newline at end of file diff --git a/source/application/store/model/store/RoleAccess.php b/source/application/store/model/store/RoleAccess.php new file mode 100644 index 0000000..8f734f1 --- /dev/null +++ b/source/application/store/model/store/RoleAccess.php @@ -0,0 +1,95 @@ + $role_id, + 'access_id' => $access_id, + 'wxapp_id' => self::$wxapp_id, + ]; + } + return $this->saveAll($data); + } + + /** + * 更新关系记录 + * @param $role_id + * @param array $newAccess 新的权限集 + * @return array|false + * @throws \Exception + */ + public function edit($role_id, $newAccess) + { + // 已分配的权限集 + $assignAccessIds = self::getAccessIds($role_id); + + /** + * 找出删除的权限 + * 假如已有的权限集合是A,界面传递过得权限集合是B + * 权限集合A当中的某个权限不在权限集合B当中,就应该删除 + * 使用 array_diff() 计算补集 + */ + if ($deleteAccessIds = array_diff($assignAccessIds, $newAccess)) { + self::deleteAll(['role_id' => $role_id, 'access_id' => ['in', $deleteAccessIds]]); + } + + /** + * 找出添加的权限 + * 假如已有的权限集合是A,界面传递过得权限集合是B + * 权限集合B当中的某个权限不在权限集合A当中,就应该添加 + * 使用 array_diff() 计算补集 + */ + $newAccessIds = array_diff($newAccess, $assignAccessIds); + $data = []; + foreach ($newAccessIds as $access_id) { + $data[] = [ + 'role_id' => $role_id, + 'access_id' => $access_id, + 'wxapp_id' => self::$wxapp_id, + ]; + } + return $this->saveAll($data); + } + + /** + * 获取指定角色的所有权限id + * @param int|array $role_id 角色id (支持数组) + * @return array + */ + public static function getAccessIds($role_id) + { + $roleIds = is_array($role_id) ? $role_id : [(int)$role_id]; + return (new self)->where('role_id', 'in', $roleIds)->column('access_id'); + } + + /** + * 删除记录 + * @param $where + * @return int + */ + public static function deleteAll($where) + { + return self::destroy($where); + } + +} \ No newline at end of file diff --git a/source/application/store/model/store/Shop.php b/source/application/store/model/store/Shop.php new file mode 100644 index 0000000..a513bc4 --- /dev/null +++ b/source/application/store/model/store/Shop.php @@ -0,0 +1,113 @@ +where('status', '=', (int)$status); + return $this->where('is_delete', '=', '0') + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 获取所有门店列表 + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public static function getAllList() + { + return (new self)->where('is_delete', '=', '0') + ->order(['sort' => 'asc', 'create_time' => 'desc']) + ->select(); + } + + /** + * 新增记录 + * @param $data + * @return bool + * @throws \Exception + */ + public function add($data) + { + if (!$this->validateForm($data)) { + return false; + } + return $this->allowField(true)->save($this->createData($data)); + } + + /** + * 编辑记录 + * @param $data + * @return false|int + */ + public function edit($data) + { + if (!$this->validateForm($data)) { + return false; + } + return $this->allowField(true)->save($this->createData($data)) !== false; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]); + } + + /** + * 创建数据 + * @param array $data + * @return array + */ + private function createData($data) + { + $data['wxapp_id'] = self::$wxapp_id; + // 格式化坐标信息 + $coordinate = explode(',', $data['coordinate']); + $data['latitude'] = $coordinate[0]; + $data['longitude'] = $coordinate[1]; + // 生成geohash + $Geohash = new Geohash; + $data['geohash'] = $Geohash->encode($data['longitude'], $data['latitude']); + return $data; + } + + /** + * 表单验证 + * @param $data + * @return bool + */ + private function validateForm($data) + { + if (!isset($data['logo_image_id']) || empty($data['logo_image_id'])) { + $this->error = '请选择门店logo'; + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/store/model/store/User.php b/source/application/store/model/store/User.php new file mode 100644 index 0000000..3a15d43 --- /dev/null +++ b/source/application/store/model/store/User.php @@ -0,0 +1,201 @@ +getLoginUser($data['user_name'], $data['password'])) { + $this->error = '登录失败, 用户名或密码错误'; + return false; + } + if (empty($user['wxapp'])) { + $this->error = '登录失败, 未找到小程序信息'; + return false; + } + if ($user['wxapp']['is_recycle']) { + $this->error = '登录失败, 当前小程序商城已删除'; + return false; + } + // 保存登录状态 + $this->loginState($user); + return true; + } + + /** + * 获取登录用户信息 + * @param $user_name + * @param $password + * @return array|false|\PDOStatement|string|\think\Model + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getLoginUser($user_name, $password) + { + return self::useGlobalScope(false)->with(['wxapp'])->where([ + 'user_name' => $user_name, + 'password' => yoshop_hash($password), + 'is_delete' => 0 + ])->find(); + } + + /** + * 获取用户列表 + * @return \think\Paginator + * @throws \think\exception\DbException + */ + public function getList() + { + return $this->where('is_delete', '=', '0') + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 新增记录 + * @param $data + * @return bool|false|int + * @throws \think\exception\DbException + */ + public function add($data) + { + if (self::checkExist($data['user_name'])) { + $this->error = '用户名已存在'; + return false; + } + if ($data['password'] !== $data['password_confirm']) { + $this->error = '确认密码不正确'; + return false; + } + if (empty($data['role_id'])) { + $this->error = '请选择所属角色'; + return false; + } + $this->startTrans(); + try { + // 新增管理员记录 + $data['password'] = yoshop_hash($data['password']); + $data['wxapp_id'] = self::$wxapp_id; + $data['is_super'] = 0; + $this->allowField(true)->save($data); + // 新增角色关系记录 + (new UserRole)->add($this['store_user_id'], $data['role_id']); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 更新记录 + * @param array $data + * @return bool + * @throws \think\exception\DbException + */ + public function edit($data) + { + if ($this['user_name'] !== $data['user_name'] + && self::checkExist($data['user_name'])) { + $this->error = '用户名已存在'; + return false; + } + if (!empty($data['password']) && ($data['password'] !== $data['password_confirm'])) { + $this->error = '确认密码不正确'; + return false; + } + if (empty($data['role_id'])) { + $this->error = '请选择所属角色'; + return false; + } + if (!empty($data['password'])) { + $data['password'] = yoshop_hash($data['password']); + } else { + unset($data['password']); + } + $this->startTrans(); + try { + // 更新管理员记录 + $this->allowField(true)->save($data); + // 更新角色关系记录 + (new UserRole)->edit($this['store_user_id'], $data['role_id']); + $this->commit(); + return true; + } catch (\Exception $e) { + $this->error = $e->getMessage(); + $this->rollback(); + return false; + } + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + if ($this['is_super']) { + $this->error = '超级管理员不允许删除'; + return false; + } + // 删除对应的角色关系 + UserRole::deleteAll(['store_user_id' => $this['store_user_id']]); + return $this->save(['is_delete' => 1]); + } + + /** + * 更新当前管理员信息 + * @param $data + * @return bool + */ + public function renew($data) + { + if ($data['password'] !== $data['password_confirm']) { + $this->error = '确认密码不正确'; + return false; + } + if ($this['user_name'] !== $data['user_name'] + && self::checkExist($data['user_name'])) { + $this->error = '用户名已存在'; + return false; + } + // 更新管理员信息 + if ($this->save([ + 'user_name' => $data['user_name'], + 'password' => yoshop_hash($data['password']), + ]) === false) { + return false; + } + // 更新session + Session::set('yoshop_store.user', [ + 'store_user_id' => $this['store_user_id'], + 'user_name' => $data['user_name'], + ]); + return true; + } + +} diff --git a/source/application/store/model/store/UserRole.php b/source/application/store/model/store/UserRole.php new file mode 100644 index 0000000..dec1e53 --- /dev/null +++ b/source/application/store/model/store/UserRole.php @@ -0,0 +1,94 @@ + $store_user_id, + 'role_id' => $role_id, + 'wxapp_id' => self::$wxapp_id, + ]; + } + return $this->saveAll($data); + } + + /** + * 更新关系记录 + * @param $store_user_id + * @param array $newRole 新的角色集 + * @return array|false + * @throws \Exception + */ + public function edit($store_user_id, $newRole) + { + // 已分配的角色集 + $assignRoleIds = self::getRoleIds($store_user_id); + + /** + * 找出删除的角色 + * 假如已有的角色集合是A,界面传递过得角色集合是B + * 角色集合A当中的某个角色不在角色集合B当中,就应该删除 + * 使用 array_diff() 计算补集 + */ + if ($deleteRoleIds = array_diff($assignRoleIds, $newRole)) { + self::deleteAll(['store_user_id' => $store_user_id, 'role_id' => ['in', $deleteRoleIds]]); + } + + /** + * 找出添加的角色 + * 假如已有的角色集合是A,界面传递过得角色集合是B + * 角色集合B当中的某个角色不在角色集合A当中,就应该添加 + * 使用 array_diff() 计算补集 + */ + $newRoleIds = array_diff($newRole, $assignRoleIds); + $data = []; + foreach ($newRoleIds as $role_id) { + $data[] = [ + 'store_user_id' => $store_user_id, + 'role_id' => $role_id, + 'wxapp_id' => self::$wxapp_id, + ]; + } + return $this->saveAll($data); + } + + /** + * 获取指定管理员的所有角色id + * @param $store_user_id + * @return array + */ + public static function getRoleIds($store_user_id) + { + return (new self)->where('store_user_id', '=', $store_user_id)->column('role_id'); + } + + /** + * 删除记录 + * @param $where + * @return int + */ + public static function deleteAll($where) + { + return self::destroy($where); + } + +} \ No newline at end of file diff --git a/source/application/store/model/store/shop/Clerk.php b/source/application/store/model/store/shop/Clerk.php new file mode 100644 index 0000000..011afbb --- /dev/null +++ b/source/application/store/model/store/shop/Clerk.php @@ -0,0 +1,102 @@ + -1 && $this->where('status', '=', (int)$status); + $shop_id > 0 && $this->where('shop_id', '=', (int)$shop_id); + !empty($search) && $this->where('real_name|mobile', 'like', "%{$search}%"); + // 查询列表数据 + return $this->with(['user', 'shop']) + ->where('is_delete', '=', '0') + ->order(['create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 新增记录 + * @param $data + * @return bool + * @throws \Exception + */ + public function add($data) + { + // 表单验证 + if (!$this->validateForm($data, self::FORM_SCENE_ADD)) { + return false; + } + $data['wxapp_id'] = self::$wxapp_id; + return $this->allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return bool|false|int + * @throws \think\exception\DbException + */ + public function edit($data) + { + // 表单验证 + if (!$this->validateForm($data, self::FORM_SCENE_EDIT)) { + return false; + } + return $this->allowField(true)->save($data) !== false; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + return $this->save(['is_delete' => 1]); + } + + /** + * 表单验证 + * @param $data + * @param string $scene + * @return bool + * @throws \think\exception\DbException + */ + private function validateForm($data, $scene = self::FORM_SCENE_ADD) + { + if ($scene === self::FORM_SCENE_ADD) { + if (!isset($data['user_id']) || empty($data['user_id'])) { + $this->error = '请选择用户'; + return false; + } + if (self::detail(['user_id' => $data['user_id'], 'is_delete' => 0])) { + $this->error = '该用户已经是店员,无需重复添加'; + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/source/application/store/model/store/shop/Order.php b/source/application/store/model/store/shop/Order.php new file mode 100644 index 0000000..d545c5a --- /dev/null +++ b/source/application/store/model/store/shop/Order.php @@ -0,0 +1,43 @@ + 0 && $this->where('clerk.shop_id', '=', (int)$shop_id); + !empty($search) && $this->where('clerk.real_name', 'like', "%{$search}%"); + // 查询列表数据 + $data = $this->with(['shop', 'clerk']) + ->alias('order') + ->field(['order.*']) + ->join('store_shop_clerk clerk', 'clerk.clerk_id = order.clerk_id', 'INNER') + ->order(['order.create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + if ($data->isEmpty()) { + return $data; + } + // 整理订单信息 + return OrderService::getOrderList($data); + } + +} \ No newline at end of file diff --git a/source/application/store/model/user/BalanceLog.php b/source/application/store/model/user/BalanceLog.php new file mode 100644 index 0000000..4d2b626 --- /dev/null +++ b/source/application/store/model/user/BalanceLog.php @@ -0,0 +1,61 @@ +setQueryWhere($query); + // 获取列表数据 + return $this->with(['user']) + ->alias('log') + ->field('log.*') + ->join('user', 'user.user_id = log.user_id') + ->order(['log.create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 设置查询条件 + * @param $query + */ + private function setQueryWhere($query) + { + // 设置默认的检索数据 + $params = $this->setQueryDefaultValue($query, [ + 'user_id' => 0, + 'search' => '', + 'scene' => -1, + 'start_time' => '', + 'end_time' => '', + ]); + // 用户ID + $params['user_id'] > 0 && $this->where('log.user_id', '=', $params['user_id']); + // 用户昵称 + !empty($params['search']) && $this->where('user.nickName', 'like', "%{$params['search']}%"); + // 余额变动场景 + $params['scene'] > -1 && $this->where('log.scene', '=', (int)$params['scene']); + // 起始时间 + !empty($params['start_time']) && $this->where('log.create_time', '>=', strtotime($params['start_time'])); + // 截止时间 + !empty($params['end_time']) && $this->where('log.create_time', '<', strtotime($params['end_time']) + 86400); + } + +} \ No newline at end of file diff --git a/source/application/store/model/user/Grade.php b/source/application/store/model/user/Grade.php new file mode 100644 index 0000000..216dbf7 --- /dev/null +++ b/source/application/store/model/user/Grade.php @@ -0,0 +1,97 @@ +where('is_delete', '=', 0) + ->order(['weight' => 'asc', 'create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + + /** + * 新增记录 + * @param $data + * @return bool + * @throws \Exception + */ + public function add($data) + { + if (!$this->validateForm($data)) { + return false; + } + $data['wxapp_id'] = self::$wxapp_id; + return $this->allowField(true)->save($data); + } + + /** + * 编辑记录 + * @param $data + * @return false|int + */ + public function edit($data) + { + if (!$this->validateForm($data, 'edit')) { + return false; + } + return $this->allowField(true)->save($data) !== false; + } + + /** + * 软删除 + * @return false|int + */ + public function setDelete() + { + // 判断该等级下是否存在会员 + if (UserModel::checkExistByGradeId($this['grade_id'])) { + $this->error = '该会员等级下存在用户,不允许删除'; + return false; + } + return $this->save(['is_delete' => 1]); + } + + /** + * 表单验证 + * @param $data + * @param string $scene + * @return bool + */ + private function validateForm($data, $scene = 'add') + { + if ($scene === 'add') { + // 需要判断等级权重是否已存在 + if (self::checkExistByWeight($data['weight'])) { + $this->error = '等级权重已存在'; + return false; + } + } elseif ($scene === 'edit') { + // 需要判断等级权重是否已存在 + if (self::checkExistByWeight($data['weight'], $this['grade_id'])) { + $this->error = '等级权重已存在'; + return false; + } + } + return true; + } + + +} \ No newline at end of file diff --git a/source/application/store/model/user/GradeLog.php b/source/application/store/model/user/GradeLog.php new file mode 100644 index 0000000..c603caa --- /dev/null +++ b/source/application/store/model/user/GradeLog.php @@ -0,0 +1,26 @@ +records([$data]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/user/PointsLog.php b/source/application/store/model/user/PointsLog.php new file mode 100644 index 0000000..fcb7728 --- /dev/null +++ b/source/application/store/model/user/PointsLog.php @@ -0,0 +1,58 @@ +setQueryWhere($query); + // 获取列表数据 + return $this->with(['user']) + ->alias('log') + ->field('log.*') + ->join('user', 'user.user_id = log.user_id') + ->order(['log.create_time' => 'desc']) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 设置查询条件 + * @param $query + */ + private function setQueryWhere($query) + { + // 设置默认的检索数据 + $params = $this->setQueryDefaultValue($query, [ + 'user_id' => 0, + 'search' => '', + 'start_time' => '', + 'end_time' => '', + ]); + // 用户ID + $params['user_id'] > 0 && $this->where('log.user_id', '=', $params['user_id']); + // 用户昵称 + !empty($params['search']) && $this->where('user.nickName', 'like', "%{$params['search']}%"); + // 起始时间 + !empty($params['start_time']) && $this->where('log.create_time', '>=', strtotime($params['start_time'])); + // 截止时间 + !empty($params['end_time']) && $this->where('log.create_time', '<', strtotime($params['end_time']) + 86400); + } + +} \ No newline at end of file diff --git a/source/application/store/model/wow/Order.php b/source/application/store/model/wow/Order.php new file mode 100644 index 0000000..e44c160 --- /dev/null +++ b/source/application/store/model/wow/Order.php @@ -0,0 +1,43 @@ +setBaseQuery($this->alias, [ + ['order', 'order_id'], + ['user', 'user_id'], + ]); + // 检索查询条件 + if (!empty($search)) { + $this->where(function ($query) use ($search) { + $query->whereOr('order.order_no', 'like', "%{$search}%") + ->whereOr('user.nickName', 'like', "%{$search}%"); + }); + } + // 返回列表数据 + return $this->with(['user']) + ->field(['wow_order.*', 'order.order_no', 'order.pay_price']) + ->where("{$this->alias}.is_delete", '=', 0) + ->order(["{$this->alias}.create_time" => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/wow/Setting.php b/source/application/store/model/wow/Setting.php new file mode 100644 index 0000000..e4193cd --- /dev/null +++ b/source/application/store/model/wow/Setting.php @@ -0,0 +1,43 @@ + '基础设置', + ]; + + /** + * 更新系统设置 + * @param $key + * @param $values + * @return bool + * @throws \think\exception\DbException + */ + public function edit($key, $values) + { + $model = self::detail($key) ?: $this; + // 删除系统设置缓存 + Cache::rm('wow_setting_' . self::$wxapp_id); + return $model->save([ + 'key' => $key, + 'describe' => $this->describe[$key], + 'values' => $values, + 'wxapp_id' => self::$wxapp_id, + ]) !== false; + } + +} \ No newline at end of file diff --git a/source/application/store/model/wow/Shoping.php b/source/application/store/model/wow/Shoping.php new file mode 100644 index 0000000..c9aa239 --- /dev/null +++ b/source/application/store/model/wow/Shoping.php @@ -0,0 +1,43 @@ +setBaseQuery($this->alias, [ + ['goods', 'goods_id'], + ['user', 'user_id'], + ]); + // 检索查询条件 + if (!empty($search)) { + $this->where(function ($query) use ($search) { + $query->whereOr('goods.goods_name', 'like', "%{$search}%") + ->whereOr('user.nickName', 'like', "%{$search}%"); + }); + } + // 返回列表数据 + return $this->with(['goods.image.file', 'user']) + ->where("{$this->alias}.is_delete", '=', 0) + ->order(["{$this->alias}.create_time" => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/model/wxapp/Formid.php b/source/application/store/model/wxapp/Formid.php new file mode 100644 index 0000000..bd69211 --- /dev/null +++ b/source/application/store/model/wxapp/Formid.php @@ -0,0 +1,32 @@ +with(['user']) + ->field(['user_id', 'count(id) AS total_formid']) + ->where('is_used', '=', 0) + ->where('expiry_time', '>', time()) + ->group('user_id') + ->order(['total_formid' => 'desc']) + ->paginate(15, false, [ + 'query' => request()->request() + ]); + } + +} \ No newline at end of file diff --git a/source/application/store/service/Auth.php b/source/application/store/service/Auth.php new file mode 100644 index 0000000..2a2b46e --- /dev/null +++ b/source/application/store/service/Auth.php @@ -0,0 +1,168 @@ +store = Session::get('yoshop_store'); + // 当前用户信息 + $this->user = User::detail($this->store['user']['store_user_id']); + } + + /** + * 私有化克隆方法 + */ + private function __clone() + { + } + + /** + * 验证指定url是否有访问权限 + * @param string|array $url + * @param bool $strict 严格模式(必须全部通过才返回true) + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function checkPrivilege($url, $strict = true) + { + if (!is_array($url)): + return $this->checkAccess($url); + else: + foreach ($url as $val): + if ($strict && !$this->checkAccess($val)) { + return false; + } + if (!$strict && $this->checkAccess($val)) { + return true; + } + endforeach; + endif; + return true; + } + + /** + * @param string $url + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function checkAccess($url) + { + // 超级管理员无需验证 + if ($this->user['is_super']) { + return true; + } + // 验证当前请求是否在白名单 + if (in_array($url, $this->allowAllAction)) { + return true; + } + // 通配符支持 + foreach ($this->allowAllAction as $action) { + if (strpos($action, '*') !== false + && preg_match('/^' . str_replace('/', '\/', $action) . '/', $url) + ) { + return true; + } + } + // 获取当前用户的权限url列表 + if (!in_array($url, $this->getAccessUrls())) { + return false; + } + return true; + } + + /** + * 获取当前用户的权限url列表 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getAccessUrls() + { + if (empty($this->accessUrls)) { + // 获取当前用户的角色集 + $roleIds = UserRole::getRoleIds($this->user['store_user_id']); + // 根据已分配的权限 + $accessIds = RoleAccess::getAccessIds($roleIds); + // 获取当前角色所有权限链接 + $this->accessUrls = Access::getAccessUrls($accessIds); + } + return $this->accessUrls; + } + +} \ No newline at end of file diff --git a/source/application/store/service/Goods.php b/source/application/store/service/Goods.php new file mode 100644 index 0000000..45e73af --- /dev/null +++ b/source/application/store/service/Goods.php @@ -0,0 +1,64 @@ +auth = Auth::getInstance(); + } + + /** + * 私有化克隆方法 + */ + private function __clone() + { + } + + /** + * 后台菜单配置 + * @param $routeUri + * @param $group + * @return array|mixed + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getMenus($routeUri, $group) + { + // 菜单列表数据 + $menus = Config::get('menus'); + $this->first($menus, $routeUri, $group); +// pre($menus); + return $menus; + } + + /** + * 一级菜单 + * @param $menus + * @param $routeUri + * @param $group + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function first(&$menus, $routeUri, $group) + { + foreach ($menus as $key => &$first) : + // 一级菜单索引url + $indexData = $this->getMenusIndexUrls($first, 1); + // 权限验证 + $first['index'] = $this->getAuthUrl($indexData); + if ($first['index'] === false) { + unset($menus[$key]); + continue; + } + // 菜单聚焦 + $first['active'] = $key === $group; + // 遍历:二级菜单 + if (isset($first['submenu'])) { + $this->second($first['submenu'], $routeUri); + } + endforeach; + } + + /** + * 二级菜单 + * @param array $menus + * @param $routeUri + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function second(&$menus, $routeUri) + { + foreach ($menus as $key => &$second) : + // 二级菜单索引url + $indexData = $this->getMenusIndexUrls($second, 2); + // 权限验证 + $second['index'] = $this->getAuthUrl($indexData); + if ($second['index'] === false) { + unset($menus[$key]); + continue; + } + // 二级菜单所有uri + $secondUris = []; + // 遍历:三级菜单 + if (isset($second['submenu'])) { + $this->third($second['submenu'], $routeUri, $secondUris); + } else { + if (isset($second['uris'])) + $secondUris = array_merge($secondUris, $second['uris']); + else + $secondUris[] = $second['index']; + } + // 二级菜单:active + !isset($second['active']) && $second['active'] = in_array($routeUri, $secondUris); + endforeach; + // 删除空数组 + $menus = array_filter($menus); + } + + /** + * 三级菜单 + * @param array $menus + * @param $routeUri + * @param $secondUris + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function third(&$menus, $routeUri, &$secondUris) + { + foreach ($menus as $key => &$third): + // 三级菜单索引url + $indexData = $this->getMenusIndexUrls($third, 3); + // 权限验证 + $third['index'] = $this->getAuthUrl($indexData); + if ($third['index'] === false) { + unset($menus[$key]); + continue; + } + // 三级菜单所有uri + $thirdUris = []; + if (isset($third['uris'])) { + $secondUris = array_merge($secondUris, $third['uris']); + $thirdUris = array_merge($thirdUris, $third['uris']); + } else { + $secondUris[] = $third['index']; + $thirdUris[] = $third['index']; + } + $third['active'] = in_array($routeUri, $thirdUris); + endforeach; + } + + /** + * 获取指定菜单下的所有索引url + * @param array $menus + * @param int $level + * @return array|null + */ + private function getMenusIndexUrls(&$menus, $level = 1) + { +// // 三级 +// if ($level === 3) { +// return isset($menus['index']) ? [$menus['index']] : null; +// } + // 判断是否存在url + if (!isset($menus['index']) && !isset($menus['submenu'])) { + return null; + } + $data = []; + if (isset($menus['index']) && !empty($menus['index'])) { + $data[] = $menus['index']; + } + if (isset($menus['submenu']) && !empty($menus['submenu'])) { + foreach ($menus['submenu'] as $submenu) { + $submenuIndex = $this->getMenusIndexUrls($submenu, ++$level); + !is_null($submenuIndex) && $data = array_merge($data, $submenuIndex); + } + } + return array_unique($data); + } + + /** + * 取出通过权限验证urk作为index + * @param $urls + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function getAuthUrl($urls) + { + // 取出通过权限验证urk作为index + foreach ($urls as $url) { + if ($this->auth->checkPrivilege($url)) return $url; + } + return false; + } + +} \ No newline at end of file diff --git a/source/application/store/service/goods/Apply.php b/source/application/store/service/goods/Apply.php new file mode 100644 index 0000000..7f57d14 --- /dev/null +++ b/source/application/store/service/goods/Apply.php @@ -0,0 +1,55 @@ +checkSharpGoods($goodsId); + } + + /** + * 验证商品是否允许删除 + * @param $goodsId + * @return bool + */ + public static function checkIsAllowDelete($goodsId) + { + $service = new static; + if ($service->checkSharpGoods($goodsId)) return false; + if ($service->checkBargainGoods($goodsId)) return false; + return true; + } + + /** + * 验证商品是否参与了秒杀商品 + * @param $goodsId + * @return bool + */ + private function checkSharpGoods($goodsId) + { + return SharpGoodsModel::isExistGoodsId($goodsId); + } + + /** + * 验证商品是否参与了砍价商品 + * @param $goodsId + * @return bool + */ + private function checkBargainGoods($goodsId) + { + return BargainGoodsModel::isExistGoodsId($goodsId); + } + +} \ No newline at end of file diff --git a/source/application/store/service/order/Export.php b/source/application/store/service/order/Export.php new file mode 100644 index 0000000..2d7f2fc --- /dev/null +++ b/source/application/store/service/order/Export.php @@ -0,0 +1,121 @@ + $this->filterValue($order['order_no']), + '商品信息' => $this->filterGoodsInfo($order), + '订单总额' => $this->filterValue($order['total_price']), + '优惠券抵扣' => $this->filterValue($order['coupon_money']), + '积分抵扣' => $this->filterValue($order['points_money']), + '运费金额' => $this->filterValue($order['express_price']), + '后台改价' => $this->filterValue("{$order['update_price']['symbol']}{$order['update_price']['value']}"), + '实付款金额' => $this->filterValue($order['pay_price']), + '支付方式' => $this->filterValue($order['pay_type']['text']), + '下单时间' => $this->filterValue($order['create_time']), + '买家' => $this->filterValue($order['user']['nickName']), + '买家留言' => $this->filterValue($order['buyer_remark']), + '配送方式' => $this->filterValue($order['delivery_type']['text']), + '自提门店名称' => !empty($order['extract_shop']) ? $this->filterValue($order['extract_shop']['shop_name']) : '', + '自提联系人' => !empty($order['extract']) ? $this->filterValue($order['extract']['linkman']) : '', + '自提联系电话' => !empty($order['extract']) ? $this->filterValue($order['extract']['phone']) : '', + '收货人姓名' => $this->filterValue($order['address']['name']), + '联系电话' => $this->filterValue($order['address']['phone']), + '收货人地址' => $this->filterValue($address ? $address->getFullAddress() : ''), + '物流公司' => $this->filterValue($order['express']['express_name']), + '物流单号' => $this->filterValue($order['express_no']), + '付款状态' => $this->filterValue($order['pay_status']['text']), + '付款时间' => $this->filterTime($order['pay_time']), + '发货状态' => $this->filterValue($order['delivery_status']['text']), + '发货时间' => $this->filterTime($order['delivery_time']), + '收货状态' => $this->filterValue($order['receipt_status']['text']), + '收货时间' => $this->filterTime($order['receipt_time']), + '订单状态' => $this->filterValue($order['order_status']['text']), + '微信支付交易号' => $this->filterValue($order['transaction_id']), + '是否已评价' => $this->filterValue($order['is_comment'] ? '是' : '否'), + ]; + } + // 导出csv文件 + $filename = 'order-' . date('YmdHis'); + return export_excel($filename . '.csv', $this->tileArray, $dataArray); + } + + /** + * 批量发货模板 + */ + public function deliveryTpl() + { + // 导出csv文件 + $filename = 'delivery-' . date('YmdHis'); + return export_excel($filename . '.csv', ['订单号', '物流单号']); + } + + /** + * 格式化商品信息 + * @param $order + * @return string + */ + private function filterGoodsInfo($order) + { + $content = ''; + foreach ($order['goods'] as $key => $goods) { + $content .= ($key + 1) . ".商品名称:{$goods['goods_name']}\n"; + !empty($goods['goods_attr']) && $content .= " 商品规格:{$goods['goods_attr']}\n"; + $content .= " 购买数量:{$goods['total_num']}\n"; + $content .= " 商品总价:{$goods['total_price']}元\n\n"; + } + return $content; + } + + /** + * 表格值过滤 + * @param $value + * @return string + */ + private function filterValue($value) + { + return "\t" . $value . "\t"; + } + + /** + * 日期值过滤 + * @param $value + * @return string + */ + private function filterTime($value) + { + if (!$value) return ''; + return $this->filterValue(date('Y-m-d H:i:s', $value)); + } + +} \ No newline at end of file diff --git a/source/application/store/service/statistics/Data.php b/source/application/store/service/statistics/Data.php new file mode 100644 index 0000000..93c451a --- /dev/null +++ b/source/application/store/service/statistics/Data.php @@ -0,0 +1,64 @@ +getSurveyData($startDate, $endDate); + } + + /** + * 近7日走势 + * @return array + * @throws \think\Exception + */ + public function getTransactionTrend() + { + return (new Trade7days)->getTransactionTrend(); + } + + /** + * 商品销售榜 + * @return string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getGoodsRanking() + { + return (new GoodsRanking)->getGoodsRanking(); + } + + /** + * 用户消费榜 + * @return string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function geUserExpendRanking() + { + return (new UserExpendRanking)->getUserExpendRanking(); + } + +} \ No newline at end of file diff --git a/source/application/store/service/statistics/data/GoodsRanking.php b/source/application/store/service/statistics/data/GoodsRanking.php new file mode 100644 index 0000000..11d4e5b --- /dev/null +++ b/source/application/store/service/statistics/data/GoodsRanking.php @@ -0,0 +1,42 @@ +alias('o_goods') + ->field([ + 'goods_id', + 'goods_name', + 'SUM(total_pay_price) AS sales_volume', + 'SUM(total_num) AS total_sales_num' + ]) + ->join('order', 'order.order_id = o_goods.order_id') + ->where('order.pay_status', '=', OrderPayStatusEnum::SUCCESS) + ->where('order.order_status', '<>', OrderStatusEnum::CANCELLED) + ->group('goods_id, goods_name') + // order:此处按总销售额排序,如需按销量改为total_sales_num + ->order(['sales_volume' => 'DESC']) + ->limit(10) + ->select(); + } + +} \ No newline at end of file diff --git a/source/application/store/service/statistics/data/Survey.php b/source/application/store/service/statistics/data/Survey.php new file mode 100644 index 0000000..4217982 --- /dev/null +++ b/source/application/store/service/statistics/data/Survey.php @@ -0,0 +1,147 @@ + $this->getUserTotal($startDate, $endDate), + // 消费人数 + 'consume_users' => $this->getConsumeUsers($startDate, $endDate), + // 付款订单数 + 'order_total' => $this->getOrderTotal($startDate, $endDate), + // 付款订单总额 + 'order_total_money' => $this->getOrderTotalMoney($startDate, $endDate), + // 商品总量 + 'goods_total' => $this->getGoodsTotal($startDate, $endDate), + // 用户充值总额 + 'recharge_total' => $this->getRechargeTotal($startDate, $endDate), + ]; + } + + /** + * 获取用户总量 + * @param null $startDate + * @param null $endDate + * @return string + * @throws \think\Exception + */ + private function getUserTotal($startDate = null, $endDate = null) + { + $model = new UserModel; + if (!is_null($startDate) && !is_null($endDate)) { + $model->where('create_time', '>=', strtotime($startDate)) + ->where('create_time', '<', strtotime($endDate) + 86400); + } + $value = $model->where('is_delete', '=', '0')->count(); + return number_format($value); + } + + /** + * 消费人数 + * @param null $startDate + * @param null $endDate + * @return string + * @throws \think\Exception + */ + public function getConsumeUsers($startDate = null, $endDate = null) + { + $model = new OrderModel; + if (!is_null($startDate) && !is_null($endDate)) { + $model->where('pay_time', '>=', strtotime($startDate)) + ->where('pay_time', '<', strtotime($endDate) + 86400); + } + $value = $model->field('user_id') + ->where('pay_status', '=', PayStatusEnum::SUCCESS) + ->where('order_status', '<>', OrderStatusEnum::CANCELLED) + ->where('is_delete', '=', '0') + ->group('user_id') + ->count(); + return number_format($value); + } + + /** + * 获取订单总量 + * @param null $startDate + * @param null $endDate + * @return string + * @throws \think\Exception + */ + private function getOrderTotal($startDate = null, $endDate = null) + { + return number_format((new OrderModel)->getPayOrderTotal($startDate, $endDate)); + } + + /** + * 付款订单总额 + * @param null $startDate + * @param null $endDate + * @return string + */ + private function getOrderTotalMoney($startDate = null, $endDate = null) + { + return helper::number2((new OrderModel)->getOrderTotalPrice($startDate, $endDate)); + } + + /** + * 获取商品总量 + * @param null $startDate + * @param null $endDate + * @return int|string + * @throws \think\Exception + */ + private function getGoodsTotal($startDate = null, $endDate = null) + { + $model = new GoodsModel; + if (!is_null($startDate) && !is_null($endDate)) { + $model->where('create_time', '>=', strtotime($startDate)) + ->where('create_time', '<', strtotime($endDate) + 86400); + } + $value = $model->where('is_delete', '=', 0)->count(); + return number_format($value); + } + + /** + * 用户充值总额 + * @param null $startDate + * @param null $endDate + * @return float|int + */ + private function getRechargeTotal($startDate = null, $endDate = null) + { + $model = new RechargeOrderModel; + if (!is_null($startDate) && !is_null($endDate)) { + $model->where('pay_time', '>=', strtotime($startDate)) + ->where('pay_time', '<', strtotime($endDate) + 86400); + } + $value = $model->where('pay_status', '=', RechargePayStatusEnum::SUCCESS) + ->sum('actual_money'); + return helper::number2($value); + } + +} \ No newline at end of file diff --git a/source/application/store/service/statistics/data/Trade7days.php b/source/application/store/service/statistics/data/Trade7days.php new file mode 100644 index 0000000..9bc19b7 --- /dev/null +++ b/source/application/store/service/statistics/data/Trade7days.php @@ -0,0 +1,107 @@ +OrderModel = new OrderModel; + } + + /** + * 近7日走势 + * @return array + * @throws \think\Exception + */ + public function getTransactionTrend() + { + // 最近七天日期 + $lately7days = $this->getLately7days(); + return [ + 'date' => helper::jsonEncode($lately7days), + 'order_total' => helper::jsonEncode($this->getOrderTotalByDate($lately7days)), + 'order_total_price' => helper::jsonEncode($this->getOrderTotalPriceByDate($lately7days)) + ]; + } + + /** + * 最近七天日期 + */ + private function getLately7days() + { + // 获取当前周几 + $date = []; + for ($i = 0; $i < 7; $i++) { + $date[] = date('Y-m-d', strtotime('-' . $i . ' days')); + } + return array_reverse($date); + } + + /** + * 获取订单总量 (指定日期) + * @param $days + * @return array + * @throws \think\Exception + */ + private function getOrderTotalByDate($days) + { + $data = []; + foreach ($days as $day) { + $data[] = $this->getOrderTotal($day); + } + return $data; + } + + /** + * 获取订单总量 + * @param null $day + * @return string + * @throws \think\Exception + */ + private function getOrderTotal($day = null) + { + return number_format($this->OrderModel->getPayOrderTotal($day, $day)); + } + + /** + * 获取某天的总销售额 + * @param null $day + * @return string + */ + private function getOrderTotalPrice($day = null) + { + return helper::number2($this->OrderModel->getOrderTotalPrice($day, $day)); + } + + /** + * 获取订单总量 (指定日期) + * @param $days + * @return array + */ + private function getOrderTotalPriceByDate($days) + { + $data = []; + foreach ($days as $day) { + $data[] = $this->getOrderTotalPrice($day); + } + return $data; + } + +} \ No newline at end of file diff --git a/source/application/store/service/statistics/data/UserExpendRanking.php b/source/application/store/service/statistics/data/UserExpendRanking.php new file mode 100644 index 0000000..58e7521 --- /dev/null +++ b/source/application/store/service/statistics/data/UserExpendRanking.php @@ -0,0 +1,31 @@ +field(['user_id', 'nickName', 'expend_money']) + ->where('is_delete', '=', 0) + ->order(['expend_money' => 'DESC']) + ->limit(10) + ->select(); + } + +} \ No newline at end of file diff --git a/source/application/store/service/wxapp/Message.php b/source/application/store/service/wxapp/Message.php new file mode 100644 index 0000000..10c79f7 --- /dev/null +++ b/source/application/store/service/wxapp/Message.php @@ -0,0 +1,113 @@ +WxTplMsg = new WxTplMsg($config['app_id'], $config['app_secret']); + } + + /** + * @param $data + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function send($data) + { + // 用户id集 + $userIdsArr = !strstr($data['user_id'], self::SEPARATOR) ? [$data['user_id']] + : explode(self::SEPARATOR, $data['user_id']); + // 批量发送 + foreach ($userIdsArr as $userId) { + $this->sendTemplateMessage($userId, $data); + } + return true; + } + + /** + * 发送模板消息 + * @param $userId + * @param $data + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + private function sendTemplateMessage($userId, $data) + { + // 获取formid + if (!$formId = FormIdService::getAvailableFormId($userId)) { + $this->recordState("用户[ID:$userId] 无可用formid,无法发送模板消息!"); + return false; + } + // 获取用户信息 + $user = UserModel::detail($data['user_id']); + // 构建模板消息参数 + $params = [ + 'touser' => $user['open_id'], + 'template_id' => $data['template_id'], + 'page' => $data['page'], + 'form_id' => $formId['form_id'], + 'data' => [] + ]; + // 格式化模板内容 + foreach (array_filter($data['content']) as $key => $item) { + $params['data']['keyword' . ($key + 1)] = $item; + } + // 请求微信api:发送模板消息 + if ($status = $this->WxTplMsg->sendTemplateMessage($params)) { + $this->recordState("用户[ID:$userId] 发送成功!"); + } + // 标记formid已使用 + FormIdService::setIsUsed($formId['id']); + return $status; + } + + /** + * 获取状态集 + * @return array + */ + public function getStateSet() + { + return $this->stateSet; + } + + /** + * 记录状态集 + * @param $content + */ + private function recordState($content) + { + $this->stateSet[] = $content; + } + +} \ No newline at end of file diff --git a/source/application/store/view/apps/bargain/active/add.php b/source/application/store/view/apps/bargain/active/add.php new file mode 100644 index 0000000..2acd76c --- /dev/null +++ b/source/application/store/view/apps/bargain/active/add.php @@ -0,0 +1,226 @@ +
+
+
+
+
+
+
+
+
新增砍价活动
+
+ +
+ +
+
+ +
+
+
+
+ 注:砍价活动仅支持单规格商品 或 同价的多规格商品 +
+
+
+ +
+ +
+ +
+ + + +
+
+ 砍价活动的开始时间与截止时间 +
+
+
+ +
+ +
+ + 自用户发起砍价到砍价截止的时间,单位:小时 +
+
+ +
+ +
+ + 砍价商品的最低价格,单位:元 +
+
+ +
+ +
+ + 每个砍价订单的帮砍人数,达到该人数才可砍至底价 +
+
+ +
+ +
+ + +
+ 砍价发起人自己砍一刀 +
+
+
+ +
+ +
+ + +
+ 只有砍到底价才可以购买 +
+
+
+ +
+ +
+ + 注:前台展示的销量 = 初始销量 + 实际销量 +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + +
+
+ +
+ +
+ + 数字越小越靠前 +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + + diff --git a/source/application/store/view/apps/bargain/active/edit.php b/source/application/store/view/apps/bargain/active/edit.php new file mode 100644 index 0000000..7be6670 --- /dev/null +++ b/source/application/store/view/apps/bargain/active/edit.php @@ -0,0 +1,200 @@ +
+
+
+
+
+
+
+
+
编辑砍价活动
+
+ +
+ +
+
+
+ +
+
+

+
+
+
+
+ +
+ +
+ +
+ + + +
+
+ 砍价活动的开始时间与截止时间 +
+
+
+ +
+ +
+ + 自用户发起砍价到砍价截止的时间,单位:小时 +
+
+ +
+ +
+ + 砍价商品的最低价格,单位:元 +
+
+ +
+ +
+ + 每个砍价订单的帮砍人数,达到该人数才可砍至底价 +
+
+ +
+ +
+ + +
+ 砍价发起人自己砍一刀 +
+
+
+ +
+ +
+ + +
+ 只有砍到底价才可以购买 +
+
+
+ +
+ +
+ + 注:前台展示的销量 = 初始销量 + 实际销量 +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + +
+
+ +
+ +
+ + 数字越小越靠前 +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/bargain/active/index.php b/source/application/store/view/apps/bargain/active/index.php new file mode 100644 index 0000000..834a1e1 --- /dev/null +++ b/source/application/store/view/apps/bargain/active/index.php @@ -0,0 +1,132 @@ +
+
+
+
+
+
砍价活动列表
+
+
+ +
+
+ +
+
+
+ + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + +
活动ID商品信息活动时间砍价底价帮砍人数实际销量排序活动状态创建时间操作
+
+ +
+
+

+
+
+

开始时间:

+

结束时间:

+
+ + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/bargain/setting/index.php b/source/application/store/view/apps/bargain/setting/index.php new file mode 100644 index 0000000..28865f2 --- /dev/null +++ b/source/application/store/view/apps/bargain/setting/index.php @@ -0,0 +1,60 @@ +
+
+
+
+
+
+
+
+
砍价设置
+
+
+ +
+ + +
+ 注:砍价订单是否参与分销 +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/apps/bargain/task/help.php b/source/application/store/view/apps/bargain/task/help.php new file mode 100644 index 0000000..73fa30b --- /dev/null +++ b/source/application/store/view/apps/bargain/task/help.php @@ -0,0 +1,65 @@ +
+
+
+
+
+
砍价助力榜
+
+
+
+ + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + +
ID用户头像用户昵称砍掉的金额操作时间
+ + + + + 发起人 + + -
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/bargain/task/index.php b/source/application/store/view/apps/bargain/task/index.php new file mode 100644 index 0000000..cfd9f2a --- /dev/null +++ b/source/application/store/view/apps/bargain/task/index.php @@ -0,0 +1,130 @@ +
+
+
+
+
+
砍价记录
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + +
ID商品信息用户信息砍价底价已砍金额截止时间是否购买砍价状态创建时间操作
+
+
+ +
+
+

+
+
+
+
+
+ +
+
+

+

(ID:)

+
+
+
+ + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/dealer/apply/index.php b/source/application/store/view/apps/dealer/apply/index.php new file mode 100644 index 0000000..7d246aa --- /dev/null +++ b/source/application/store/view/apps/dealer/apply/index.php @@ -0,0 +1,209 @@ +
+
+
+
+
+
申请成为分销商
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + +
用户ID微信头像微信昵称 +

姓名

+

手机号

+
推荐人审核状态审核方式申请时间操作
+ + + + +

+
+ +

+

+ +

--

+ +
+ 0): ?> +

+ + +

平台

+ +
+ + 待审核 + + 已通过 + + 已驳回 + + + + 后台审核 + + 无需审核 + + + +
暂无记录
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+
+ + + + + + diff --git a/source/application/store/view/apps/dealer/order/index.php b/source/application/store/view/apps/dealer/order/index.php new file mode 100644 index 0000000..87c5aa5 --- /dev/null +++ b/source/application/store/view/apps/dealer/order/index.php @@ -0,0 +1,187 @@ +
+
+
+
+
+
分销订单
+
+
+ +
+
+ + +
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + isEmpty()): foreach ($list->toArray()['data'] as $order): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + +
商品信息单价/数量实付款买家交易状态佣金结算
+ + 订单号: +
+
+ +
+
+

+ +
+
+

+

×

+
+

+ +
+

+ +
+

付款状态: + + +

+

发货状态: + + +

+

收货状态: + + +

+
+ + 已结算 + + 未结算 + +
+
+ 0): ?> +
+

+ 一级分销商: + + (ID: ) +

+

+ 分销佣金: + +

+
+ + 0): ?> +
+

+ 二级分销商: + + (ID: ) +

+

+ 分销佣金: + +

+
+ + 0): ?> +
+

+ 三级分销商: + + (ID: ) +

+

+ 分销佣金: + +

+
+ +
+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/apps/dealer/setting/index.php b/source/application/store/view/apps/dealer/setting/index.php new file mode 100644 index 0000000..1414dbe --- /dev/null +++ b/source/application/store/view/apps/dealer/setting/index.php @@ -0,0 +1,930 @@ +
+
+
+
+
+
+
+ +
+
+
+ +
+ + +
+
+
+ +
+ + + +
+
+
+ +
+ + +
+ 如开启,分销商自己购买商品,获得一级佣金 +
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+ 购买指定商品付款后自动成为分销商,无需后台审核 +
+
+ +
+ isEmpty()): foreach ($goodsList as $goods): ?> +
+ + + + + +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ + 佣金比例范围 0% - 100% +
+
+
+ +
+ + 佣金比例范围 0% - 100% +
+
+
+ +
+ + 佣金比例范围 0% - 100% +
+
+
+
+
+ +
+ + + +
+ 注:如使用微信支付,则需申请微信支付企业付款到零钱功能 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+ 当订单完成n天后,该订单的分销佣金才会结算到分销商余额,如果设置为0天 则订单完成时就结算 +
+
+ 注:建议佣金结算天数大于允许发起售后申请天数,如果用户申请退款退货 则不结算佣金 +
+
+
+
+
+
+
分销中心页面
+
+
+ +
+ + + 默认: +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
申请成为分销商页面
+
+
+ +
+ + + 默认: +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
分销订单页面
+
+
+ +
+ + + 默认: +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
我的团队页面
+
+
+ +
+ + + 默认: +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
提现明细页面
+
+
+ +
+ + + 默认: +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
申请提现页面
+
+
+ +
+ + + 默认: +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
推广二维码
+
+
+ +
+ + + 默认: +
+
+
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+ + + + + +
+ +
+
+
+ 尺寸:宽750像素 高度不限 +
+
+
+
+
+ +
+
+
+ +
+ +
+ + + + + +
+ +
+
+
+ 尺寸:宽750像素 高度不限 +
+
+
+
+
+ +
+
+
+ +
+ +
+ + + + + +
+ +
+
+
+ 尺寸:宽750像素 高度不限 +
+
+
+
+
+
+
+ +
+ + 模板编号AT0674,关键词 (申请时间、审核状态、审核时间、备注信息) + + 如何获取模板消息ID? + +
+
+
+ +
+ + 模板编号AT0324,关键词 (提现时间、提现方式、提现金额、提现状态、备注) + + 如何获取模板消息ID? + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ + + + + +{{include file="layouts/_template/file_library" /}} + + + + + + + diff --git a/source/application/store/view/apps/dealer/setting/qrcode.php b/source/application/store/view/apps/dealer/setting/qrcode.php new file mode 100644 index 0000000..eac5056 --- /dev/null +++ b/source/application/store/view/apps/dealer/setting/qrcode.php @@ -0,0 +1,206 @@ +
+
+
+
+
+
+
分销海报设置
+
+
+
+

注:可拖动头像、二维码、昵称调整位置,如修改

+

注:修改后如需生效请前往 设置-清理缓存,清除临时图片 +

+
+
+
+
+
+ +
+ +
+
+ +
+
+ 这里是昵称 +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+ 尺寸:宽750像素 高大于(等于)1200像素 +
+
+
+
+ +
+ +
+ +
+
+
+ +
+ + +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ + +{{include file="layouts/_template/file_library" /}} + + + diff --git a/source/application/store/view/apps/dealer/user/edit.php b/source/application/store/view/apps/dealer/user/edit.php new file mode 100644 index 0000000..67bb4e7 --- /dev/null +++ b/source/application/store/view/apps/dealer/user/edit.php @@ -0,0 +1,66 @@ +
+
+
+
+
+
+
+
+
编辑分销商
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/apps/dealer/user/fans.php b/source/application/store/view/apps/dealer/user/fans.php new file mode 100644 index 0000000..6ca2501 --- /dev/null +++ b/source/application/store/view/apps/dealer/user/fans.php @@ -0,0 +1,64 @@ +
+
+
+
+
+
下级用户列表
+
+
+ +
+ + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + +
用户ID微信头像微信昵称性别累积消费金额注册时间
+ + + + +

+
暂无记录
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/dealer/user/index.php b/source/application/store/view/apps/dealer/user/index.php new file mode 100644 index 0000000..43bd666 --- /dev/null +++ b/source/application/store/view/apps/dealer/user/index.php @@ -0,0 +1,205 @@ +
+
+
+
+
+
分销商列表
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + +
用户ID微信头像微信昵称 +

姓名

+

手机号

+
+

累计佣金

+

可提现佣金

+
推荐人下级用户成为时间操作
+ + + + +

+
+ +

+

+ +

--

+ +
+

+

+
+ 0): ?> +

+ + +

平台

+ +
+

+ 一级: +

+ = 2): ?> +

+ 二级: +

+ + +

+ 三级: +

+ +
+
+ + + 编辑 + + + +
+ + +
+ +
+
暂无记录
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/dealer/withdraw/index.php b/source/application/store/view/apps/dealer/withdraw/index.php new file mode 100644 index 0000000..d211a4b --- /dev/null +++ b/source/application/store/view/apps/dealer/withdraw/index.php @@ -0,0 +1,301 @@ +
+
+
+
+
+
分销商提现申请
+
+
+ +
+
+ + +
+
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + +
用户ID微信头像微信昵称 +

姓名

+

手机号

+
提现金额提现方式提现信息审核状态申请时间审核时间操作
+ + + + +

+
+ +

+

+ +

--

+ +
+

+
+

+
+ +

+

+ +

+

+

+ +

--

+ +
+ + 待审核 + + 审核通过 + +

已驳回

+ + + 查看原因 + + + 已打款 + +
+
+ + + + 审核 + + + + + 确认打款 + + + + + + 微信付款 + + + + + + --- + +
+
暂无记录
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+
+ + + + + + diff --git a/source/application/store/view/apps/sharing/active/index.php b/source/application/store/view/apps/sharing/active/index.php new file mode 100644 index 0000000..fdd0bb6 --- /dev/null +++ b/source/application/store/view/apps/sharing/active/index.php @@ -0,0 +1,111 @@ +
+
+
+
+
+
拼单记录
+
+
+
+
+ + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + +
拼单ID商品图片商品名称成团人数已拼人员发起人(团长)拼单结束时间拼单状态发起时间操作
+ + 商品图片 + + +

+
+

+
+

+ +
+ +

+ +

+ +

+ + 还差 人 + +

+ + + + + + +
+ +
暂无记录
+
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharing/active/users.php b/source/application/store/view/apps/sharing/active/users.php new file mode 100644 index 0000000..c24a10e --- /dev/null +++ b/source/application/store/view/apps/sharing/active/users.php @@ -0,0 +1,90 @@ +
+
+
+
+
+
拼单成员列表
+
+
+
+ + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + +
用户ID微信头像微信昵称拼团角色订单号订单金额收货人联系方式收货地址下单时间操作
+ + + + + + 团长 + + 团员 + + + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharing/category/add.php b/source/application/store/view/apps/sharing/category/add.php new file mode 100644 index 0000000..ad4954a --- /dev/null +++ b/source/application/store/view/apps/sharing/category/add.php @@ -0,0 +1,61 @@ +
+
+
+
+
+
+
+
+
添加商品分类
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + diff --git a/source/application/store/view/apps/sharing/category/edit.php b/source/application/store/view/apps/sharing/category/edit.php new file mode 100644 index 0000000..ce5137b --- /dev/null +++ b/source/application/store/view/apps/sharing/category/edit.php @@ -0,0 +1,61 @@ +
+
+
+
+
+
+
+
+
编辑商品分类
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + diff --git a/source/application/store/view/apps/sharing/category/index.php b/source/application/store/view/apps/sharing/category/index.php new file mode 100644 index 0000000..fa3be06 --- /dev/null +++ b/source/application/store/view/apps/sharing/category/index.php @@ -0,0 +1,128 @@ +
+
+
+
+
+
拼团商品分类
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
分类ID分类名称分类排序添加时间操作
+ +
 -- + +
   -- + +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharing/comment/detail.php b/source/application/store/view/apps/sharing/comment/detail.php new file mode 100644 index 0000000..debfc80 --- /dev/null +++ b/source/application/store/view/apps/sharing/comment/detail.php @@ -0,0 +1,163 @@ +
+
+
+
+
+
+
+
+
商品评价详情
+
+
+ +
+ + (用户id:) +
+
+
+ +
+ + 商品图片 + +
+
+
+ +
+ +
+
+
+ +
+ + + +
+
+
+ +
+ +
+
+
+ +
+
+ +
+ $item): ?> +
+ + + + + +
+ +
+
+
+ 最多允许6张,可拖拽调整显示顺序 +
+
+
+
+ +
+ + 数字越小越靠前 +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + + diff --git a/source/application/store/view/apps/sharing/comment/index.php b/source/application/store/view/apps/sharing/comment/index.php new file mode 100644 index 0000000..9f50f7e --- /dev/null +++ b/source/application/store/view/apps/sharing/comment/index.php @@ -0,0 +1,122 @@ +
+
+
+
+
+
评价列表
+
+
+
+ + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + +
ID用户商品图片商品名称评分评价内容是否有图片显示状态评价排序评价时间操作
+

+ +
+ + 商品图片 + + +

+
+ + 好评 + + 中评 + + 差评 + + +

+
+ + + + + + + + 显示 + + 隐藏 + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharing/goods/add.php b/source/application/store/view/apps/sharing/goods/add.php new file mode 100644 index 0000000..e8ac2be --- /dev/null +++ b/source/application/store/view/apps/sharing/goods/add.php @@ -0,0 +1,704 @@ + + +
+
+
+
+
+
+
+
+
基本信息
+
+
+ +
+ +
+
+
+ +
+ + + + 去添加 + + +
+
+
+ +
+
+
+ +
+
+
+
+ 尺寸750x750像素以上,大小2M以下 (可拖拽图片调整显示顺序 ) +
+
+
+
+
+ +
+ + 选填,商品卖点简述,例如:此款商品美观大方 性价比较高 不容错过 +
+
+ +
+
拼团设置
+
+
+ +
+ + +
+ 是否允许用户选择不拼团单独购买,如果允许单买,请务必设置好单买价 +
+
+
+
+ +
+ + 拼团成员的总人数,最低2人 +
+
+
+ +
+ + 注:开团后的有效时间,单位:小时,超过时长则拼团失败 +
+
+ +
+
规格/库存
+
+
+ +
+ + +
+
+ + +
+
+ +
+
+
+ {{ item.group_name }} + +
+
+
+ {{ val.spec_value }} + +
+
+ + +
+
+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{ item.group_name }}规格图片商家编码 + 拼团价 + + 单买价 + 划线价 + 库存 + + 重量(kg) +
+ {{ td.spec_value }} + +
+ + +
+
+ +
+
+ + + + + + + + + + + +
+
+ 注:如不允许单买,单买价设置为0即可 +
+
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + +
+
+ +
+
商品详情
+
+
+ +
+ + +
+
+
+
其他设置
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+ + +
+
积分设置
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+ 注:如需使用积分功能必须在 [营销管理 - 积分设置] 中开启 +
+
+
+ + +
+
会员折扣设置
+
+
+ +
+ + +
+ 如果不开启会员折扣,该商品则不享受会员等级折扣价 +
+
+
+
+
+ +
+ + +
+ 默认折扣:默认为用户所属会员等级的折扣率 +
+
+
+
+
+ +
+ + +
+ + : + + + +
+ +
+ 注:折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+
+
+ + +
+
分销设置
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+ +
+
+ 一级佣金: + + % +
+
+ 二级佣金: + + % +
+
+ 三级佣金: + + % +
+
+

+ 注:如需使用分销功能必须在 [分销中心 - 分销设置] 中开启 +

+

+ 注:如不开启单独分销则默认使用全局分销比例 +

+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + + + + + + diff --git a/source/application/store/view/apps/sharing/goods/copy_master.php b/source/application/store/view/apps/sharing/goods/copy_master.php new file mode 100644 index 0000000..f7d2f45 --- /dev/null +++ b/source/application/store/view/apps/sharing/goods/copy_master.php @@ -0,0 +1,744 @@ + + +
+
+
+
+
+
+
+
+
基本信息
+
+
+ +
+ +
+
+
+ +
+ + + + 去添加 + + +
+
+
+ +
+
+ +
+ $item): ?> +
+ + + + + +
+ +
+
+
+ 尺寸750x750像素以上,大小2M以下 (可拖拽图片调整显示顺序 ) +
+
+
+
+ +
+ + 选填,商品卖点简述,例如:此款商品美观大方 性价比较高 不容错过 +
+
+ +
+
拼团设置
+
+
+ +
+ + +
+ 是否允许用户选择不拼团单独购买,如果允许单买,请务必设置好单买价 +
+
+
+
+ +
+ + 拼团成员的总人数,最低2人 +
+
+
+ +
+ + 注:开团后的有效时间,单位:小时,超过时长则拼团失败 +
+
+ +
+
规格/库存
+
+
+ +
+ + +
+
+ + +
+
+ +
+
+
+ {{ item.group_name }} + +
+
+
+ {{ val.spec_value }} + +
+
+ + +
+
+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{ item.group_name }}规格图片商家编码 + 拼团价 + + 单买价 + 划线价 + 库存 + + 重量(kg) +
+ {{ td.spec_value }} + +
+ + +
+
+ +
+
+ + + + + + + + + + + +
+
+ 注:如不允许单买,单买价设置为0即可 +
+
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + +
+
+ +
+
商品详情
+
+
+ +
+ + +
+
+
+
其他设置
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+ + +
+
积分设置
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+ 注:如需使用积分功能必须在 [营销管理 - 积分设置] 中开启 +
+
+
+ + +
+
会员折扣设置
+
+
+ +
+ + +
+ 如果不开启会员折扣,该商品则不享受会员等级折扣价 +
+
+
+
+
+ +
+ + +
+ 默认折扣:默认为用户所属会员等级的折扣率 +
+
+
+
+
+ +
+ + +
+ + : + + + +
+ +
+ 注:折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+
+
+ + +
+
分销设置
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+ + '%', 20 => '元']; + ?> +
+
+ 一级佣金: + + + + +
+
+ 二级佣金: + + + + +
+
+ 三级佣金: + + + + +
+
+

+ 注:如需使用分销功能必须在 [分销中心 - 分销设置] 中开启 +

+

+ 注:如不开启单独分销则默认使用全局分销比例 +

+
+
+
+
+ + +
+
+ +
+
+ +
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + + + + + + diff --git a/source/application/store/view/apps/sharing/goods/edit.php b/source/application/store/view/apps/sharing/goods/edit.php new file mode 100644 index 0000000..a8dd3c5 --- /dev/null +++ b/source/application/store/view/apps/sharing/goods/edit.php @@ -0,0 +1,749 @@ + + +
+
+
+
+
+
+
+
+
基本信息
+
+
+ +
+ +
+
+
+ +
+ + + + 去添加 + + +
+
+
+ +
+
+ +
+ $item): ?> +
+ + + + + +
+ +
+
+
+ 尺寸750x750像素以上,大小2M以下 (可拖拽图片调整显示顺序 ) +
+
+
+
+ +
+ + 选填,商品卖点简述,例如:此款商品美观大方 性价比较高 不容错过 +
+
+ +
+
拼团设置
+
+
+ +
+ + +
+ 是否允许用户选择不拼团单独购买,如果允许单买,请务必设置好单买价 +
+
+
+
+ +
+ + 拼团成员的总人数,最低2人 +
+
+
+ +
+ + 注:开团后的有效时间,单位:小时,超过时长则拼团失败 +
+
+ +
+
规格/库存
+
+
+ +
+ + +
+
+ + +
+
+ +
+
+
+ {{ item.group_name }} + +
+
+
+ {{ val.spec_value }} + +
+
+ + +
+
+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{ item.group_name }}规格图片商家编码 + 拼团价 + + 单买价 + 划线价 + 库存 + + 重量(kg) +
+ {{ td.spec_value }} + +
+ + +
+
+ +
+
+ + + + + + + + + + + +
+
+ 注:如不允许单买,单买价设置为0即可 +
+
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + +
+
+ +
+
商品详情
+
+
+ +
+ + +
+
+
+
其他设置
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+ + +
+
积分设置
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+ 注:如需使用积分功能必须在 [营销管理 - 积分设置] 中开启 +
+
+
+ + +
+
会员折扣设置
+
+
+ +
+ + +
+ 如果不开启会员折扣,该商品则不享受会员等级折扣价 +
+
+
+
+
+ +
+ + +
+ 默认折扣:默认为用户所属会员等级的折扣率 +
+
+
+
+
+ +
+ + +
+ + : + + + +
+ +
+ 注:折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+
+
+ + +
+
分销设置
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+ + '%', 20 => '元']; + ?> +
+
+ 一级佣金: + + + + +
+
+ 二级佣金: + + + + +
+
+ 三级佣金: + + + + +
+
+

+ 注:如需使用分销功能必须在 [分销中心 - 分销设置] 中开启 +

+

+ 注:如不开启单独分销则默认使用全局分销比例 +

+
+
+
+
+ + +
+
+ +
+
+ +
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + + + + + + diff --git a/source/application/store/view/apps/sharing/goods/index.php b/source/application/store/view/apps/sharing/goods/index.php new file mode 100644 index 0000000..1d8640a --- /dev/null +++ b/source/application/store/view/apps/sharing/goods/index.php @@ -0,0 +1,253 @@ +
+
+
+
+
+
拼团商品列表
+
+
+ +
+
+ + +
+
+
+ get('category_id') ?: null; ?> + +
+
+ get('status') ?: null; ?> + +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + +
商品ID商品图片商品名称商品分类成团人数成团有效时长实际销量商品排序商品状态添加时间操作
+ + 商品图片 + + +

+
小时 + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + + + + diff --git a/source/application/store/view/apps/sharing/order/detail.php b/source/application/store/view/apps/sharing/order/detail.php new file mode 100644 index 0000000..1e29e65 --- /dev/null +++ b/source/application/store/view/apps/sharing/order/detail.php @@ -0,0 +1,700 @@ + +
+
+
+
+
+ + +
+ +
    +
  • + 下单时间 +
    +
  • +
  • + 付款 + +
    + 付款于 +
    + +
  • +
  • + 发货 + +
    + 发货于 +
    + +
  • +
  • + 收货 + +
    + 收货于 +
    + +
  • +
  • + 完成 + +
    + 完成于 +
    + +
  • +
+
+ + +
+
基本信息
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
订单号买家订单类型订单金额支付方式配送方式交易状态操作
+

+ +
+ + + + + + + + + + +
+
    +
  • 订单总额:
  • +
  • +
+ 0) : ?> +
    +
  • 优惠券抵扣:
  • +
  • - ¥
  • +
+ + 0) : ?> +
    +
  • 积分抵扣:
  • +
  • - ¥
  • +
+ +
    +
  • 运费金额:
  • +
  • + ¥
  • +
+ +
    +
  • 后台改价:
  • +
  • + ¥
  • +
+ +
    +
  • 实付款金额:
  • +
  • + ¥
  • +
+
+
+ + + + +

付款状态: + + +

+ +

拼单状态: + + + + + + + +

+ + +

退款状态: + + 待退款 + + 已退款 + +

+ + + + +

发货状态: + + +

+

收货状态: + + +

+ + +

订单状态: + +

+ +
+ +

+ 修改价格 +

+ +
+
+ + +
+
商品信息
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
商品名称商品编码重量(Kg)单价购买数量商品总价
+
+ +
+
+

+ +
+
+

+ +

+ +

+ 会员折扣价: +

+ +
×
+ 买家留言: + 总计金额:¥ +
+
+ + + +
+
收货信息
+
+
+ + + + + + + + + + + + + +
收货人收货电话收货地址
+ + + + +
+
+ + + + + +
+
自提信息
+
+
+

联系人:

+

联系电话:

+
+ +
+
自提门店信息
+
+
+ + + + + + + + + + + + + + + + + + + +
门店ID门店logo门店名称联系人联系电话门店地址
+ + + + + + + + +
+
+ + + + +
+
付款信息
+
+
+ + + + + + + + + + + + + + + + + +
应付款金额支付方式支付流水号付款状态付款时间
+ + + + +
+
+ + + + + +
+
用户取消订单
+
+
+
+

当前买家已付款并申请取消订单,请审核是否同意,如同意则自动退回付款金额(微信支付原路退款)并关闭订单。

+
+
+ +
+
+ +
+
+ + +
+ +
+
+
+
+ + +
+
+
+ + + + + +
+
发货信息
+
+ + + + +
+
+ +
+ +
+ 可在 物流公司列表 + 中设置 + +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + +
物流公司物流单号发货状态发货时间
+ + + + +
+
+ + + + + +
+
门店自提核销
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + +
自提门店名称核销员核销状态核销时间
+

+ +
+

+ +
+ + 已核销 + + +
+
+ + + + + + +
+
拼团失败手动退款
+
+
+
+

当前拼团已失败,可选择手动退款并关闭订单。

+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+ + + +
+
+ +
+
+
+ + + + + diff --git a/source/application/store/view/apps/sharing/order/index.php b/source/application/store/view/apps/sharing/order/index.php new file mode 100644 index 0000000..bcbc343 --- /dev/null +++ b/source/application/store/view/apps/sharing/order/index.php @@ -0,0 +1,359 @@ + +
+
+
+
+
+
拼团订单列表
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $order): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + +
商品信息订单类型单价/数量实付款买家支付方式配送方式交易状态操作
+ + 订单号: +
+
+ +
+
+

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

+

×

+
+

+ +
+

+ +
+ + + + +

付款状态: + + +

+ +

拼单状态: + + + + + + + +

+ + +

退款状态: + + 待退款 + + 已退款 + +

+ + + + +

发货状态: + + +

+

收货状态: + + +

+ + +

订单状态: + +

+ +
+
+ + 0 + ): ?> + + + 拼团信息 + + + + + + 订单详情 + + + + + 去发货 + + + + + + 去审核 + + + + + + 去退款 + + +
+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharing/order/operate/batchDelivery.php b/source/application/store/view/apps/sharing/order/operate/batchDelivery.php new file mode 100644 index 0000000..f8ad473 --- /dev/null +++ b/source/application/store/view/apps/sharing/order/operate/batchDelivery.php @@ -0,0 +1,80 @@ +
+
+
+
+
+
+
+
+
批量发货
+
+
+ +
+
+ + +
+
+ +
+
+
+ +
+ +
+ 可在 物流公司列表 + 中设置 + +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/apps/sharing/order/refund/detail.php b/source/application/store/view/apps/sharing/order/refund/detail.php new file mode 100644 index 0000000..f351f58 --- /dev/null +++ b/source/application/store/view/apps/sharing/order/refund/detail.php @@ -0,0 +1,358 @@ +
+
+
+
+
+
+
售后单信息
+
+
+ + + + + + + + + + + + + + + + + +
订单号买家售后类型处理状态操作
+

+ +
+ + + + +

+ 商家审核: + + + + + + + +

+ + +

+ 用户发货: + + 待发货 + + 已发货 + +

+ + + +

商家收货: 待收货

+ + + + + + +
+ + 订单详情 + +
+
+ +
+
售后商品信息
+
+
+ + + + + + + + + + + + + + + + + + + +
商品名称商品编码重量(Kg)单价购买数量付款价
+
+ +
+
+

+ +
+
× +
+
+ +
+
用户申请原因
+
+
+
+ +
+
+
+ +
+ + + +
+ +
+
+
+ + + + +
+
商家审核
+
+ +
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + 如审核状态为拒绝,则需要输入拒绝原因 +
+
+
+
+ +
+
+
+ + + + + +
+
退货地址
+
+
+ + + + + + + + + + + + + +
收货人收货电话收货地址
+
+ + + + +
+
商家拒绝原因
+
+
+
+ +
+
+ + + + +
+
用户发货信息
+
+
+ + + + + + + + + + + + + + + + + +
物流公司物流单号用户发货状态发货时间商家收货状态
+ 已发货 + + + 已收货 + + 待收货 + +
+
+ + + + + +
+
确认收货并退款
+
+
+
+

注:该操作将执行订单原路退款 并关闭当前售后单,请确认并填写退款的金额(不能大于订单实付款)

+ +

+ 注:当前订单存在后台改价记录,退款金额请参考订单实付款金额

+ +
+
+
+
+ +
+ +
+
+
+ +
+ + + 请输入退款金额,最多 + 元 + +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharing/order/refund/index.php b/source/application/store/view/apps/sharing/order/refund/index.php new file mode 100644 index 0000000..6d23bf2 --- /dev/null +++ b/source/application/store/view/apps/sharing/order/refund/index.php @@ -0,0 +1,210 @@ +
+
+
+
+
+
售后列表
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + + + +
商品信息单价/数量付款价买家售后类型处理状态操作
+ 售后申请时间: + 订单号: +
+
+ +
+
+

+ +
+
+

+

×

+
+

+
+

+ +
+ + + + +

+ 商家审核: + + + + + + + +

+ + +

+ 用户发货: + + 待发货 + + 已发货 + +

+ + + +

商家收货: 待收货

+ + + + + + +
+ $item['order_refund_id']]); ?> +
+ + 售后详情 + + + + 去审核 + + + + + 确认收货 + + +
+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharing/setting/index.php b/source/application/store/view/apps/sharing/setting/index.php new file mode 100644 index 0000000..f02c3fd --- /dev/null +++ b/source/application/store/view/apps/sharing/setting/index.php @@ -0,0 +1,129 @@ +
+
+
+
+
+
+
+
+
拼团设置
+
+
+ +
+ + +
+ 注:如果不开启自动退款,则需要在 [订单管理] 处手动退款 +
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+ 注:拼团订单是否参与分销 +
+
+
+ +
+
规则描述
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
模板消息
+
+
+ +
+ + 模板编号AT1814,关键词 (订单编号、商品名称、拼团价格、拼团人数、拼团时间、拼团结果) + + 如何获取模板消息ID? + +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/apps/sharp/active/add.php b/source/application/store/view/apps/sharp/active/add.php new file mode 100644 index 0000000..4108790 --- /dev/null +++ b/source/application/store/view/apps/sharp/active/add.php @@ -0,0 +1,173 @@ +
+
+
+
+
+
+
+
+
新增活动会场
+
+
+ +
+ +
+ 注:活动日期保存后不能更改 +
+
+
+
+ +
+ + + +
+
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + +
商品ID商品图片商品名称操作
+ + {{ item.goods_id }} + + + 商品图片 + + +

{{ item.goods_name }}

+
+ 删除 +
+
+
+
+ 注:每个活动场次中出售的秒杀商品,此处非必填,可在场次管理中单独设置 +
+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + diff --git a/source/application/store/view/apps/sharp/active/index.php b/source/application/store/view/apps/sharp/active/index.php new file mode 100644 index 0000000..6cad6c7 --- /dev/null +++ b/source/application/store/view/apps/sharp/active/index.php @@ -0,0 +1,122 @@ +
+
+
+
+
+
活动会场列表
+
+
+ +
+
+ +
+
+
+ + + +
+
+
+
+
+
+ + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + +
活动ID活动日期场次数量活动状态创建时间操作
+ + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharp/active_time/add.php b/source/application/store/view/apps/sharp/active_time/add.php new file mode 100644 index 0000000..62e7b9f --- /dev/null +++ b/source/application/store/view/apps/sharp/active_time/add.php @@ -0,0 +1,156 @@ +
+
+
+
+
+
+
+
+
新增活动场次
+
+
+ +
+
+
+
+
+ +
+ + + +
+
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + +
商品ID商品图片商品名称操作
+ + {{ item.goods_id }} + + + 商品图片 + + +

{{ item.goods_name }}

+
+ 删除 +
+
+
+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + diff --git a/source/application/store/view/apps/sharp/active_time/edit.php b/source/application/store/view/apps/sharp/active_time/edit.php new file mode 100644 index 0000000..cb94afe --- /dev/null +++ b/source/application/store/view/apps/sharp/active_time/edit.php @@ -0,0 +1,148 @@ +
+
+
+
+
+
+
+
+
编辑活动场次
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + +
商品ID商品图片商品名称操作
+ + {{ item.goods_id }} + + + 商品图片 + + +

{{ item.goods_name }}

+
+ 删除 +
+
+
+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + diff --git a/source/application/store/view/apps/sharp/active_time/index.php b/source/application/store/view/apps/sharp/active_time/index.php new file mode 100644 index 0000000..3487468 --- /dev/null +++ b/source/application/store/view/apps/sharp/active_time/index.php @@ -0,0 +1,125 @@ +
+
+
+
+
+
活动会场-场次列表
+
+
+ +
+
+ +
+
+
+ + + +
+
+
+
+
+
+ + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + +
场次ID场次时间活动日期商品数量场次状态创建时间操作
+ + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharp/goods/edit.php b/source/application/store/view/apps/sharp/goods/edit.php new file mode 100644 index 0000000..897d296 --- /dev/null +++ b/source/application/store/view/apps/sharp/goods/edit.php @@ -0,0 +1,247 @@ + +
+
+
+
+
+
+
+
+
编辑秒杀商品
+
+
+ +
+
+
+ +
+
+

+

ID:

+
+
+
+
+ + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+ 注:秒杀库存为独立库存,与主商品库存不同步 +
+
+
+
+ + + + +
+ +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
{{ item.group_name }}商家编码商品售价商品库存 + 秒杀价格 + + 秒杀库存 +
+ {{ td.spec_value }} + {{ item.form.goods_no ? item.form.goods_no : '--' }}{{ item.form.goods_price }}{{ item.form.stock_num }} + + + +
+
+ 注:秒杀库存为独立库存,与主商品库存不同步 +
+
+
+
+
+ + +
+ +
+ + + + + + + +
+ 注:秒杀商品默认为下单减库存,可在 基础设置 + 中配置未支付订单自动关闭释放库存 + +
+
+
+ + +
+ +
+ + 注:每人限制购买的数量,如果填写0则不限购 +
+
+
+ +
+ + +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + + diff --git a/source/application/store/view/apps/sharp/goods/index.php b/source/application/store/view/apps/sharp/goods/index.php new file mode 100644 index 0000000..ce75f31 --- /dev/null +++ b/source/application/store/view/apps/sharp/goods/index.php @@ -0,0 +1,127 @@ +
+
+
+
+
+
秒杀商品列表
+
+
+ +
+
+ +
+
+
+ + + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + +
秒杀商品ID商品信息限购数量累积销量库存总量排序状态创建时间操作
+
+ +
+
+

+
+
+ + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/sharp/goods/step1.php b/source/application/store/view/apps/sharp/goods/step1.php new file mode 100644 index 0000000..85219bd --- /dev/null +++ b/source/application/store/view/apps/sharp/goods/step1.php @@ -0,0 +1,73 @@ +
+
+
+
+
+ + +
+
+
+
第一步:选择商品
+
+
+ +
+
+ +
+
+
+
+ 注:添加秒杀商品后,将不允许修改主商品的规格属性 +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + diff --git a/source/application/store/view/apps/sharp/goods/step2.php b/source/application/store/view/apps/sharp/goods/step2.php new file mode 100644 index 0000000..294d54c --- /dev/null +++ b/source/application/store/view/apps/sharp/goods/step2.php @@ -0,0 +1,236 @@ + +
+
+
+
+
+
+
+
+
第二步:填写商品信息
+
+
+ +
+
+
+ +
+
+

+

ID:

+
+
+
+
+ + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+ 注:秒杀库存为独立库存,与主商品库存不同步 +
+
+
+
+ + + + +
+ +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
{{ item.group_name }}商家编码商品售价商品库存 + 秒杀价格 + + 秒杀库存 +
+ {{ td.spec_value }} + {{ item.form.goods_no ? item.form.goods_no : '--' }}{{ item.form.goods_price }}{{ item.form.stock_num }} + + + +
+
+ 注:秒杀库存为独立库存,与主商品库存不同步 +
+
+
+
+
+ + +
+ +
+ + +
+
+ +
+ +
+ + 注:每人限制购买的数量,如果填写0则不限购 +
+
+
+ +
+ + +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + + diff --git a/source/application/store/view/apps/sharp/setting/index.php b/source/application/store/view/apps/sharp/setting/index.php new file mode 100644 index 0000000..0c2bc4f --- /dev/null +++ b/source/application/store/view/apps/sharp/setting/index.php @@ -0,0 +1,67 @@ +
+
+
+
+
+
+
+
+
整点秒杀设置
+
+
+ +
+
+ +
+ +
+ 秒杀订单下单未付款,n分钟后自动关闭,设置0则不自动关闭 +
+
+
+
+ +
+ + +
+ 注:整点秒杀订单是否参与分销 +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/apps/wow/order/index.php b/source/application/store/view/apps/wow/order/index.php new file mode 100644 index 0000000..4b78b7e --- /dev/null +++ b/source/application/store/view/apps/wow/order/index.php @@ -0,0 +1,122 @@ + +
+
+
+
+
+
订单同步记录
+
+
+
+
+

注:用户下单(付款)后,自动同步到微信好物圈订单信息。

+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + +
ID用户头像用户昵称订单编号付款金额订单状态最后同步时间创建时间操作
+ + 用户头像 + + +

+ +
+ + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/apps/wow/setting/index.php b/source/application/store/view/apps/wow/setting/index.php new file mode 100644 index 0000000..c8f9db8 --- /dev/null +++ b/source/application/store/view/apps/wow/setting/index.php @@ -0,0 +1,72 @@ +
+
+
+
+
+
+
+
+
好物圈设置
+
+
+ +
+ + +
+ 注:用户将商品加入购物车时,自动同步到微信好物圈商品收藏 +
+
+
+
+ +
+ + +
+ 注:用户下单(付款)后,自动同步到微信好物圈订单信息。 +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/apps/wow/shoping/index.php b/source/application/store/view/apps/wow/shoping/index.php new file mode 100644 index 0000000..e086441 --- /dev/null +++ b/source/application/store/view/apps/wow/shoping/index.php @@ -0,0 +1,114 @@ +
+
+
+
+
+
商品收藏记录
+
+
+
+
+

注:用户将商品加入购物车时,自动同步到微信好物圈商品收藏。

+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + +
ID用户头像用户昵称商品图片商品名称收藏时间操作
+ + 用户头像 + + +

+ +
+ + 商品图片 + + +

+
+ +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/content/article/add.php b/source/application/store/view/content/article/add.php new file mode 100644 index 0000000..239c7d4 --- /dev/null +++ b/source/application/store/view/content/article/add.php @@ -0,0 +1,159 @@ + +
+
+
+
+
+
+
+
+
添加文章
+
+
+ +
+ +
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + +
+ 小图模式建议封面图尺寸:300 * 188,大图模式建议封面图尺寸:750 * 455 +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + 显示的阅读量 = 实际阅读量 + 虚拟阅读量 +
+
+
+ +
+ + +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + +{{include file="layouts/_template/file_library" /}} + + + + diff --git a/source/application/store/view/content/article/category/add.php b/source/application/store/view/content/article/category/add.php new file mode 100644 index 0000000..caf8df3 --- /dev/null +++ b/source/application/store/view/content/article/category/add.php @@ -0,0 +1,50 @@ +
+
+
+
+
+
+
+
+
添加文章分类
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/content/article/category/edit.php b/source/application/store/view/content/article/category/edit.php new file mode 100644 index 0000000..638b91b --- /dev/null +++ b/source/application/store/view/content/article/category/edit.php @@ -0,0 +1,50 @@ +
+
+
+
+
+
+
+
+
编辑文章分类
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/content/article/category/index.php b/source/application/store/view/content/article/category/index.php new file mode 100644 index 0000000..ab216ad --- /dev/null +++ b/source/application/store/view/content/article/category/index.php @@ -0,0 +1,79 @@ +
+
+
+
+
+
文章分类
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
分类ID分类名称分类排序添加时间操作
+ +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/content/article/edit.php b/source/application/store/view/content/article/edit.php new file mode 100644 index 0000000..e5db4cc --- /dev/null +++ b/source/application/store/view/content/article/edit.php @@ -0,0 +1,172 @@ + +
+
+
+
+
+
+
+
+
编辑文章
+
+
+ +
+ +
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + +
+ 小图模式建议封面图尺寸:300 * 188,大图模式建议封面图尺寸:750 * 455 +
+
+
+
+ +
+
+
+ +
+
+ + + + + +
+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+ + 显示的阅读量 = 实际阅读量 + 虚拟阅读量 +
+
+
+ +
+ + +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + +{{include file="layouts/_template/file_library" /}} + + + + diff --git a/source/application/store/view/content/article/index.php b/source/application/store/view/content/article/index.php new file mode 100644 index 0000000..e764770 --- /dev/null +++ b/source/application/store/view/content/article/index.php @@ -0,0 +1,109 @@ +
+
+
+
+
+
文章列表
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + +
文章ID文章标题封面图文章分类实际阅读量文章排序状态添加时间更新时间操作
+

+
+ + + + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/content/files/group/add.php b/source/application/store/view/content/files/group/add.php new file mode 100644 index 0000000..ec7dce0 --- /dev/null +++ b/source/application/store/view/content/files/group/add.php @@ -0,0 +1,71 @@ +
+
+
+
+
+
+
+
+
添加文件分组
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + diff --git a/source/application/store/view/content/files/group/edit.php b/source/application/store/view/content/files/group/edit.php new file mode 100644 index 0000000..ac9174a --- /dev/null +++ b/source/application/store/view/content/files/group/edit.php @@ -0,0 +1,60 @@ +
+
+
+
+
+
+
+
+
添加文件分组
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/content/files/group/index.php b/source/application/store/view/content/files/group/index.php new file mode 100644 index 0000000..2fd8296 --- /dev/null +++ b/source/application/store/view/content/files/group/index.php @@ -0,0 +1,81 @@ +
+
+
+
+
+
文件分组
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + isEmpty()): foreach ($list as $first): ?> + + + + + + + + + + + + + + +
分组ID分组名称文件类型分组排序添加时间操作
+ +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/content/files/index.php b/source/application/store/view/content/files/index.php new file mode 100644 index 0000000..b654a70 --- /dev/null +++ b/source/application/store/view/content/files/index.php @@ -0,0 +1,87 @@ +
+
+
+
+
+
文件列表
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + +
文件ID文件名称所属分组存储方式存储域名文件大小文件类型文件后缀上传时间操作
+ + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/content/files/recycle.php b/source/application/store/view/content/files/recycle.php new file mode 100644 index 0000000..8d87e26 --- /dev/null +++ b/source/application/store/view/content/files/recycle.php @@ -0,0 +1,96 @@ +
+
+
+
+
+
文件列表
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + +
文件ID文件名称所属分组存储方式存储域名文件大小文件类型文件后缀上传时间操作
+ + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/data/bargain/goods/list.php b/source/application/store/view/data/bargain/goods/list.php new file mode 100644 index 0000000..599198f --- /dev/null +++ b/source/application/store/view/data/bargain/goods/list.php @@ -0,0 +1,136 @@ + + + + + + + + + + + 砍价活动列表 + + + +
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + +
+ + 活动ID商品信息活动时间砍价底价帮砍人数创建时间
+ + +
+ +
+
+

+
+
+

开始时间:

+

结束时间:

+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+ + + + + diff --git a/source/application/store/view/data/goods/list.php b/source/application/store/view/data/goods/list.php new file mode 100644 index 0000000..ed7e662 --- /dev/null +++ b/source/application/store/view/data/goods/list.php @@ -0,0 +1,165 @@ + + + + + + + + + + + 商品列表 + + + +
+
+ +
+
+ get('category_id') ?: null; ?> + +
+
+ get('status') ?: null; ?> + +
+
+
+ +
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + +
+ + 商品ID商品图片商品名称商品分类添加时间
+ + + + 商品图片 + + +

+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+ + + + + diff --git a/source/application/store/view/data/sharing/goods/list.php b/source/application/store/view/data/sharing/goods/list.php new file mode 100644 index 0000000..d2c6e0d --- /dev/null +++ b/source/application/store/view/data/sharing/goods/list.php @@ -0,0 +1,168 @@ + + + + + + + + + + + 拼团商品列表 + + + +
+
+ +
+
+ get('category_id') ?: null; ?> + +
+
+ get('status') ?: null; ?> + +
+
+
+ +
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + +
+ + 商品ID商品图片商品名称商品分类成团人数成团有效时长添加时间
+ + + + 商品图片 + + +

+
小时
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+ + + + + diff --git a/source/application/store/view/data/sharp/goods/list.php b/source/application/store/view/data/sharp/goods/list.php new file mode 100644 index 0000000..305d319 --- /dev/null +++ b/source/application/store/view/data/sharp/goods/list.php @@ -0,0 +1,133 @@ + + + + + + + + + + + 秒杀商品列表 + + + +
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + +
+ + 商品ID商品信息限购数量累积销量商品库存总量创建时间
+ + +
+ +
+
+

+
+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+ + + + + diff --git a/source/application/store/view/data/shop/list.php b/source/application/store/view/data/shop/list.php new file mode 100644 index 0000000..ccf2c46 --- /dev/null +++ b/source/application/store/view/data/shop/list.php @@ -0,0 +1,117 @@ + + + + + + + + + + + 门店列表 + + +
+ + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + +
+ + 门店ID门店logo门店名称门店地址自提核销
+ + + + + + + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+ + + + + diff --git a/source/application/store/view/data/user/list.php b/source/application/store/view/data/user/list.php new file mode 100644 index 0000000..7d1424e --- /dev/null +++ b/source/application/store/view/data/user/list.php @@ -0,0 +1,168 @@ + + + + + + + + + + + 用户列表 + + + +
+
+ +
+
+
+ get('grade'); ?> + +
+
+ get('gender'); ?> + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + +
+ + 微信头像微信昵称用户余额会员等级累积消费金额性别注册时间
+ + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+ + + + + diff --git a/source/application/store/view/goods/_template/spec_many.php b/source/application/store/view/goods/_template/spec_many.php new file mode 100644 index 0000000..f8a4bcc --- /dev/null +++ b/source/application/store/view/goods/_template/spec_many.php @@ -0,0 +1,72 @@ + + + + + diff --git a/source/application/store/view/goods/add.php b/source/application/store/view/goods/add.php new file mode 100644 index 0000000..631be8d --- /dev/null +++ b/source/application/store/view/goods/add.php @@ -0,0 +1,643 @@ + + +
+
+
+
+
+
+
+
+
基本信息
+
+
+ +
+ +
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+
+
+ +
+
+
+
+ 尺寸750x750像素以上,大小2M以下 (可拖拽图片调整显示顺序 ) +
+
+
+
+
+ +
+ + 选填,商品卖点简述,例如:此款商品美观大方 性价比较高 不容错过 +
+
+ +
+
规格/库存
+
+
+ +
+ + +
+
+ + +
+
+ +
+
+
+ {{ item.group_name }} + +
+
+
+ {{ val.spec_value }} + +
+
+ + +
+
+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
{{ item.group_name }}规格图片商家编码销售价划线价库存重量(kg)
+ {{ td.spec_value }} + +
+ + +
+
+ +
+
+ + + + + + + + + +
+
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + +
+
+ +
+
商品详情
+
+
+ +
+ + +
+
+
+
其他设置
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+ + +
+
积分设置
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+ 注:如需使用积分功能必须在 [营销管理 - 积分设置] 中开启 +
+
+
+ + +
+
会员折扣设置
+
+
+ +
+ + +
+ 如果不开启会员折扣,该商品则不享受会员等级折扣价 +
+
+
+
+
+ +
+ + +
+ 默认折扣:默认为用户所属会员等级的折扣率 +
+
+
+
+
+ +
+ + +
+ + : + + + +
+ +
+ 注:折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+
+
+ + +
+
分销设置
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+ +
+
+ 一级佣金: + + % +
+
+ 二级佣金: + + % +
+
+ 三级佣金: + + % +
+
+

+ 注:如需使用分销功能必须在 [分销中心 - 分销设置] 中开启 +

+

+ 注:如不开启单独分销则默认使用全局分销比例 +

+
+
+
+
+ + +
+
+ +
+
+ +
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + + + + + + diff --git a/source/application/store/view/goods/category/add.php b/source/application/store/view/goods/category/add.php new file mode 100644 index 0000000..415326a --- /dev/null +++ b/source/application/store/view/goods/category/add.php @@ -0,0 +1,87 @@ +
+
+
+
+
+
+
+
+
添加商品分类
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + diff --git a/source/application/store/view/goods/category/edit.php b/source/application/store/view/goods/category/edit.php new file mode 100644 index 0000000..8ce001b --- /dev/null +++ b/source/application/store/view/goods/category/edit.php @@ -0,0 +1,96 @@ +
+
+
+
+
+
+
+
+
编辑商品分类
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+ + + +
+ +
+
+
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + diff --git a/source/application/store/view/goods/category/index.php b/source/application/store/view/goods/category/index.php new file mode 100644 index 0000000..d279e92 --- /dev/null +++ b/source/application/store/view/goods/category/index.php @@ -0,0 +1,134 @@ +
+
+
+
+
+
商品分类
+
+
+
+
+

注:商品分类最多添加2级,分类图片可参考 + 分类页模板 上传

+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
分类ID分类名称分类排序添加时间操作
+ +
 -- + +
   -- + +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/goods/comment/detail.php b/source/application/store/view/goods/comment/detail.php new file mode 100644 index 0000000..debfc80 --- /dev/null +++ b/source/application/store/view/goods/comment/detail.php @@ -0,0 +1,163 @@ +
+
+
+
+
+
+
+
+
商品评价详情
+
+
+ +
+ + (用户id:) +
+
+
+ +
+ + 商品图片 + +
+
+
+ +
+ +
+
+
+ +
+ + + +
+
+
+ +
+ +
+
+
+ +
+
+ +
+ $item): ?> +
+ + + + + +
+ +
+
+
+ 最多允许6张,可拖拽调整显示顺序 +
+
+
+
+ +
+ + 数字越小越靠前 +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + + diff --git a/source/application/store/view/goods/comment/index.php b/source/application/store/view/goods/comment/index.php new file mode 100644 index 0000000..a0a5ebb --- /dev/null +++ b/source/application/store/view/goods/comment/index.php @@ -0,0 +1,123 @@ +
+
+
+
+
+
评价列表
+
+
+
+ + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + +
ID商品图片商品名称用户评分评价内容是否有图片显示状态评价排序评价时间操作
+ + 商品图片 + + +

+
+

+ +
+ + 好评 + + 中评 + + 差评 + + +

+
+ + + + + + + + 显示 + + 隐藏 + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/goods/edit.php b/source/application/store/view/goods/edit.php new file mode 100644 index 0000000..aa2e504 --- /dev/null +++ b/source/application/store/view/goods/edit.php @@ -0,0 +1,691 @@ + + +
+
+
+
+
+
+
+
+
基本信息
+
+
+ +
+ +
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+
+ +
+ $item): ?> +
+ + + + + +
+ +
+
+
+ 尺寸750x750像素以上,大小2M以下 (可拖拽图片调整显示顺序 ) +
+
+
+
+ +
+ + 选填,商品卖点简述,例如:此款商品美观大方 性价比较高 不容错过 +
+
+ +
+
规格/库存
+
+
+ +
+ + + +
+ 注:该商品当前正在参与其他活动,商品规格不允许更改 +
+ +
+
+ + +
+
+ +
+
+
+ {{ item.group_name }} + +
+
+
+ {{ val.spec_value }} + +
+
+ + +
+
+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
{{ item.group_name }}规格图片商家编码销售价划线价库存重量(kg)
+ {{ td.spec_value }} + +
+ + +
+
+ +
+
+ + + + + + + + + +
+
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + +
+
+ +
+
商品详情
+
+
+ +
+ + +
+
+
+
其他设置
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+ + +
+
积分设置
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+ 注:如需使用积分功能必须在 [营销管理 - 积分设置] 中开启 +
+
+
+ + +
+
会员折扣设置
+
+
+ +
+ + +
+ 如果不开启会员折扣,该商品则不享受会员等级折扣价 +
+
+
+
+
+ +
+ + +
+ 默认折扣:默认为用户所属会员等级的折扣率 +
+
+
+
+
+ +
+ + +
+ + : + + + +
+ +
+ 注:折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+
+
+ + +
+
分销设置
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+ + '%', 20 => '元']; + ?> +
+
+ 一级佣金: + + + + +
+
+ 二级佣金: + + + + +
+
+ 三级佣金: + + + + +
+
+

+ 注:如需使用分销功能必须在 [分销中心 - 分销设置] 中开启 +

+

+ 注:如不开启单独分销则默认使用全局分销比例 +

+
+
+
+
+ + +
+
+ +
+
+ +
+
+
+
+
+
+
+ + +{{include file="layouts/_template/tpl_file_item" /}} + + +{{include file="layouts/_template/file_library" /}} + + + + + + + diff --git a/source/application/store/view/goods/index.php b/source/application/store/view/goods/index.php new file mode 100644 index 0000000..c2570b5 --- /dev/null +++ b/source/application/store/view/goods/index.php @@ -0,0 +1,190 @@ +
+
+
+
+
+
出售中的商品
+
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+ get('category_id') ?: null; ?> + +
+
+ get('status') ?: null; ?> + +
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + +
商品ID商品图片商品名称商品分类实际销量商品排序商品状态添加时间操作
+ + 商品图片 + + +

+
+ + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/index/index.php b/source/application/store/view/index/index.php new file mode 100644 index 0000000..1696ead --- /dev/null +++ b/source/application/store/view/index/index.php @@ -0,0 +1,179 @@ +
+ + +
+
+
+
+
商城统计
+
+
+
+
+
商品总量
+
+
+
当前商品总数量
+ +
+
+
+ +
+
+
用户总量
+
+
+
当前用户总数量
+ +
+
+
+ +
+
+
订单总量
+
+
+
已付款订单总数量
+ +
+
+
+ +
+
+
评价总量
+
+
+
订单评价总数量
+ +
+
+
+ +
+
+
+
+ + +
+
+
+
+
实时概况
+
+
+
+
+
+ +
+
+
销售额(元)
+
+
+ 昨日:
+
+
+
+
+
+
支付订单数
+
+
+ 昨日:
+
+
+
+
+
+ +
+
+
新增用户数
+
+
+ 昨日:
+
+
+
+
+
+
下单用户数
+
+
+ 昨日:
+
+
+
+
+
+
+ + +
+
+
+
+
近七日交易走势
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/source/application/store/view/layouts/_template/file_library.php b/source/application/store/view/layouts/_template/file_library.php new file mode 100644 index 0000000..ccd7066 --- /dev/null +++ b/source/application/store/view/layouts/_template/file_library.php @@ -0,0 +1,123 @@ + + + + + + + + + + + diff --git a/source/application/store/view/layouts/_template/tpl_file_item.php b/source/application/store/view/layouts/_template/tpl_file_item.php new file mode 100644 index 0000000..e8feb4d --- /dev/null +++ b/source/application/store/view/layouts/_template/tpl_file_item.php @@ -0,0 +1,13 @@ + + + diff --git a/source/application/store/view/layouts/error.php b/source/application/store/view/layouts/error.php new file mode 100644 index 0000000..7b7e4b7 --- /dev/null +++ b/source/application/store/view/layouts/error.php @@ -0,0 +1,57 @@ + +
+
+
+
+
+
+ +
+
提交失败
+
+
+ +
+
+
+
+
+
+ diff --git a/source/application/store/view/layouts/layout.php b/source/application/store/view/layouts/layout.php new file mode 100644 index 0000000..9fd2383 --- /dev/null +++ b/source/application/store/view/layouts/layout.php @@ -0,0 +1,134 @@ + + + + + + + <?= $setting['store']['values']['name'] ?> + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ + + + +
+ {__CONTENT__} +
+ + +
+ + + + + + + + + + diff --git a/source/application/store/view/market/basic/full_free.php b/source/application/store/view/market/basic/full_free.php new file mode 100644 index 0000000..cb0a357 --- /dev/null +++ b/source/application/store/view/market/basic/full_free.php @@ -0,0 +1,400 @@ +
+
+
+
+
+
+
+
+
满额包邮设置
+
+
+ +
+ + +
+
+
+ +
+
+ + +
+ 如果开启满额包邮,设置0为全场包邮 +
+
+
+ +
+
+ +
+
+ + + + + +
+
+
+
+
+
+ +
+ + + 选择地区 + +
+ + 全国 + + +
+
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+ + 清空 +
+
+
+
+ +
+ +
+

+ +

+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + + diff --git a/source/application/store/view/market/coupon/add.php b/source/application/store/view/market/coupon/add.php new file mode 100644 index 0000000..2b8a154 --- /dev/null +++ b/source/application/store/view/market/coupon/add.php @@ -0,0 +1,226 @@ +
+
+
+
+
+
+
+
+
添加优惠券
+
+
+ +
+ + 例如:满100减10 +
+
+
+ +
+ + + + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + 折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ + 限制领取的优惠券数量,-1为不限制 +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + diff --git a/source/application/store/view/market/coupon/edit.php b/source/application/store/view/market/coupon/edit.php new file mode 100644 index 0000000..2302aaa --- /dev/null +++ b/source/application/store/view/market/coupon/edit.php @@ -0,0 +1,243 @@ +
+
+
+
+
+
+
+
+
编辑优惠券
+
+
+ +
+ + 例如:满100减10 +
+
+
+ +
+ + + + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + 折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ + 限制领取的优惠券数量,-1为不限制 +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + diff --git a/source/application/store/view/market/coupon/index.php b/source/application/store/view/market/coupon/index.php new file mode 100644 index 0000000..4ffea09 --- /dev/null +++ b/source/application/store/view/market/coupon/index.php @@ -0,0 +1,120 @@ +
+
+
+
+
+
优惠券列表
+
+
+
+
+

注:优惠券只能抵扣商品金额,最多优惠到0.01元,不能抵扣运费

+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + isEmpty()): ?> + + + + + + + + + + + + + + + + + + + + + + +
优惠券ID优惠券名称优惠券类型最低消费金额优惠方式有效期发放总数量已领取数量排序添加时间操作
+ + 立减 + + + + + + 领取 天内有效 + + + ~ + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/market/coupon/receive.php b/source/application/store/view/market/coupon/receive.php new file mode 100644 index 0000000..a6c3eb4 --- /dev/null +++ b/source/application/store/view/market/coupon/receive.php @@ -0,0 +1,76 @@ +
+
+
+
+
+
优惠券领取记录
+
+
+
+ + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + +
用户优惠券ID优惠券名称优惠券类型最低消费金额优惠方式有效期领取时间
+

+ +
+ + 立减 + + + + + + 领取 天内有效 + + + ~ + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/market/points/log.php b/source/application/store/view/market/points/log.php new file mode 100644 index 0000000..764736a --- /dev/null +++ b/source/application/store/view/market/points/log.php @@ -0,0 +1,96 @@ +
+
+
+
+
+
积分明细
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + +
ID微信头像微信昵称变动数量描述/说明管理员备注创建时间
+ + + + +

+ +
+ 0 ? '+' : '' ?> +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/market/points/setting.php b/source/application/store/view/market/points/setting.php new file mode 100644 index 0000000..b2053e8 --- /dev/null +++ b/source/application/store/view/market/points/setting.php @@ -0,0 +1,153 @@ +
+
+
+
+
+
+
+
+
积分设置
+
+
+ +
+ +
+ 注:修改积分名称后,在买家端的所有页面里,看到的都是自定义的名称 +
+
+ 注:商家使用自定义的积分名称来做品牌运营。如京东把积分称为“京豆”,淘宝把积分称为“淘金币” +
+
+
+
+ +
+ +
+
+ +
+
积分赠送
+
+
+ +
+ + +
+ 注:如开启则订单完成后赠送用户积分 +
+
+ 积分赠送规则:1.订单确认收货已完成;2.已完成订单超出后台设置的申请售后期限 +
+
+
+
+ +
+
+ + % +
+
+ 注:赠送比例请填写数字0~100;订单的运费不参与积分赠送 +
+
+ 例:订单付款金额(100.00元) * 积分赠送比例(100%) = 实际赠送的积分(100积分) +
+
+
+ +
+
积分抵扣
+
+
+ +
+ + +
+ 注:如开启则用户下单时可选择使用积分抵扣订单金额 +
+
+
+
+ +
+
+ 1个积分可抵扣 + + +
+
+ 例如:1积分可抵扣0.01元,100积分则可抵扣1元,1000积分则可抵扣10元 +
+
+
+
+ +
+
+ 订单满 + + +
+
+ + 最高可抵扣金额 + + + % +
+
+ 温馨提示:例如订单金额100元,最高可抵扣10%,此时用户可抵扣10元 +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/market/push/send.php b/source/application/store/view/market/push/send.php new file mode 100644 index 0000000..e880028 --- /dev/null +++ b/source/application/store/view/market/push/send.php @@ -0,0 +1,116 @@ +
+
+
+
+
+
+
+
+
发送推送消息
+
+
+
+

+ 注:模板消息只能发送给活跃用户,查看活跃用户列表,建议每次发送不超过10人。 +

+

注:根据腾讯官方规定,滥用模板消息接口有被封号的风险,请谨慎使用!

+
+
+
+ +
+ + 如需发送多个用户,请使用英文逗号 , 隔开;例如:10001,10002 +
+
+
+ + +
+
+ +
+ +
+
+ + param('more') ? 10 : 5; ?> + +
+ +
+ +
+
+ + +
+ +
+ + + 用户点击消息进入的小程序页面,例如:pages/index/index + +
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+ + diff --git a/source/application/store/view/market/push/user.php b/source/application/store/view/market/push/user.php new file mode 100644 index 0000000..9271282 --- /dev/null +++ b/source/application/store/view/market/push/user.php @@ -0,0 +1,64 @@ +
+
+
+
+
+
活跃用户列表
+
+
+
+
+

注:仅展示7天内在小程序中活跃(浏览点击)的用户。

+

注:formId有效期是7天,可用于向用户发送模板消息。

+
+
+
+ + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + +
用户ID用户头像用户昵称formid数量注册时间
+ + + +
暂无活跃用户
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/market/recharge/plan/add.php b/source/application/store/view/market/recharge/plan/add.php new file mode 100644 index 0000000..1b500d7 --- /dev/null +++ b/source/application/store/view/market/recharge/plan/add.php @@ -0,0 +1,63 @@ +
+
+
+
+
+
+
+
+
添加充值套餐
+
+
+ +
+ + 例如:套餐A 套餐B +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/market/recharge/plan/edit.php b/source/application/store/view/market/recharge/plan/edit.php new file mode 100644 index 0000000..50ccc90 --- /dev/null +++ b/source/application/store/view/market/recharge/plan/edit.php @@ -0,0 +1,63 @@ +
+
+
+
+
+
+
+
+
编辑充值套餐
+
+
+ +
+ + 例如:套餐A 套餐B +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/market/recharge/plan/index.php b/source/application/store/view/market/recharge/plan/index.php new file mode 100644 index 0000000..90ac168 --- /dev/null +++ b/source/application/store/view/market/recharge/plan/index.php @@ -0,0 +1,93 @@ +
+
+
+
+
+
充值套餐列表
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + +
套餐ID套餐名称充值金额赠送金额排序添加时间更新时间操作
+ +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/market/recharge/setting.php b/source/application/store/view/market/recharge/setting.php new file mode 100644 index 0000000..35f1544 --- /dev/null +++ b/source/application/store/view/market/recharge/setting.php @@ -0,0 +1,91 @@ +
+
+
+
+
+
+
+
+
充值设置
+
+
+ +
+ + +
+ 如设置不允许则用户端不显示充值按钮 +
+
+
+
+ +
+ + +
+ 是否允许用户填写自定义的充值金额 +
+
+
+
+ +
+ + +
+ 用户充值自定义金额是否自动匹配套餐,如不开启则不参与套餐金额赠送 +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ + diff --git a/source/application/store/view/order/detail.php b/source/application/store/view/order/detail.php new file mode 100644 index 0000000..a08bd98 --- /dev/null +++ b/source/application/store/view/order/detail.php @@ -0,0 +1,612 @@ + +
+
+
+
+
+ + +
+ +
    +
  • + 下单时间 +
    +
  • +
  • + 付款 + +
    + 付款于 +
    + +
  • +
  • + 发货 + +
    + 发货于 +
    + +
  • +
  • + 收货 + +
    + 收货于 +
    + +
  • +
  • + 完成 + +
    + 完成于 +
    + +
  • +
+
+ + +
+
基本信息
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
订单号买家订单金额支付方式配送方式交易状态操作
+

+ +
+
+
    +
  • 订单总额:
  • +
  • +
+ 0) : ?> +
    +
  • 优惠券抵扣:
  • +
  • - ¥
  • +
+ + 0) : ?> +
    +
  • 积分抵扣:
  • +
  • - ¥
  • +
+ +
    +
  • 运费金额:
  • +
  • +¥
  • +
+ +
    +
  • 后台改价:
  • +
  • + ¥
  • +
+ +
    +
  • 实付款金额:
  • +
  • + ¥
  • +
+
+
+ + + + +

付款状态: + + +

+

发货状态: + + +

+

收货状态: + + +

+ +

订单状态: + +

+ +
+ +

+ 修改价格 +

+ +
+
+ + +
+
商品信息
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
商品名称商品编码重量(Kg)单价购买数量商品总价
+
+ +
+
+

+ +
+
+

+ +

+ +

+ 会员折扣价: +

+ +
×
+ 买家留言: + 总计金额:¥ +
+
+ + + +
+
收货信息
+
+
+ + + + + + + + + + + + + +
收货人收货电话收货地址
+ + + + +
+
+ + + + + +
+
自提信息
+
+
+

联系人:

+

联系电话:

+
+ +
+
自提门店信息
+
+
+ + + + + + + + + + + + + + + + + + + +
门店ID门店logo门店名称联系人联系电话门店地址
+ + + + + + + + +
+
+ + + + +
+
付款信息
+
+
+ + + + + + + + + + + + + + + + + +
应付款金额支付方式支付流水号付款状态付款时间
+ + + + +
+
+ + + + + +
+
用户取消订单
+
+
+
+

当前买家已付款并申请取消订单,请审核是否同意,如同意则自动退回付款金额(微信支付原路退款)并关闭订单。

+
+
+ +
+
+ +
+
+ + +
+ +
+
+
+
+ + +
+
+
+ + + + + +
+
发货信息
+
+ + + +
+
+ +
+ +
+ 可在 物流公司列表 + 中设置 + +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + +
物流公司物流单号发货状态发货时间
+ + + + +
+
+ + + + + +
+
门店自提核销
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + +
自提门店名称核销员核销状态核销时间
+

+ +
+

+ +
+ + 已核销 + + +
+
+ + + +
+
+ +
+
+
+ + + + + diff --git a/source/application/store/view/order/index.php b/source/application/store/view/order/index.php new file mode 100644 index 0000000..62b8945 --- /dev/null +++ b/source/application/store/view/order/index.php @@ -0,0 +1,251 @@ + +
+
+
+
+
+
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $order): ?> + + + + + + + + + + + + + + + + + + + + + + + + + + +
商品信息单价/数量实付款买家支付方式配送方式交易状态操作
+ + 订单号: +
+
+ +
+
+

+ +
+
+

+

×

+
+

+ +
+

+ +
+ + + + + + + + +

付款状态: + + +

+

发货状态: + + +

+

收货状态: + + +

+ +

订单状态: + +

+ +
+
+ + + 订单详情 + + + + 去发货 + + + + + 去审核 + + +
+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/order/operate/batchDelivery.php b/source/application/store/view/order/operate/batchDelivery.php new file mode 100644 index 0000000..f8ad473 --- /dev/null +++ b/source/application/store/view/order/operate/batchDelivery.php @@ -0,0 +1,80 @@ +
+
+
+
+
+
+
+
+
批量发货
+
+
+ +
+
+ + +
+
+ +
+
+
+ +
+ +
+ 可在 物流公司列表 + 中设置 + +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/order/refund/detail.php b/source/application/store/view/order/refund/detail.php new file mode 100644 index 0000000..a0c9039 --- /dev/null +++ b/source/application/store/view/order/refund/detail.php @@ -0,0 +1,358 @@ +
+
+
+
+
+
+
售后单信息
+
+
+ + + + + + + + + + + + + + + + + +
订单号买家售后类型处理状态操作
+

+ +
+ + + + +

+ 商家审核: + + + + + + + +

+ + +

+ 用户发货: + + 待发货 + + 已发货 + +

+ + + +

商家收货: 待收货

+ + + + + + +
+ + 订单详情 + +
+
+ +
+
售后商品信息
+
+
+ + + + + + + + + + + + + + + + + + + +
商品名称商品编码重量(Kg)单价购买数量付款价
+
+ +
+
+

+ +
+
× +
+
+ +
+
用户申请原因
+
+
+
+ +
+
+
+ +
+ + + +
+ +
+
+
+ + + + +
+
商家审核
+
+ +
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+ + + 去添加 + +
+
+
+ +
+ + 如审核状态为拒绝,则需要输入拒绝原因 +
+
+
+
+ +
+
+
+ + + + + +
+
退货地址
+
+
+ + + + + + + + + + + + + +
收货人收货电话收货地址
+
+ + + + +
+
商家拒绝原因
+
+
+
+ +
+
+ + + + +
+
用户发货信息
+
+
+ + + + + + + + + + + + + + + + + +
物流公司物流单号用户发货状态发货时间商家收货状态
+ 已发货 + + + 已收货 + + 待收货 + +
+
+ + + + + +
+
确认收货并退款
+
+
+
+

注:该操作将执行订单原路退款 并关闭当前售后单,请确认并填写退款的金额(不能大于订单实付款)

+ +

+ 注:当前订单存在后台改价记录,退款金额请参考订单实付款金额

+ +
+
+
+
+ +
+ +
+
+
+ +
+ + + 请输入退款金额,最多 + 元 + +
+
+
+
+ +
+
+
+ + + +
+
+
+
+
+ + diff --git a/source/application/store/view/order/refund/index.php b/source/application/store/view/order/refund/index.php new file mode 100644 index 0000000..40d15f0 --- /dev/null +++ b/source/application/store/view/order/refund/index.php @@ -0,0 +1,210 @@ +
+
+
+
+
+
售后列表
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + + + +
商品信息单价/数量付款价买家售后类型处理状态操作
+ 售后申请时间: + 订单号: +
+
+ +
+
+

+ +
+
+

+

×

+
+

+
+

+ +
+ + + + +

+ 商家审核: + + + + + + + +

+ + +

+ 用户发货: + + 待发货 + + 已发货 + +

+ + + +

商家收货: 待收货

+ + + + + + +
+ $item['order_refund_id']]); ?> +
+ + 售后详情 + + + + 去审核 + + + + + 确认收货 + + +
+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/passport/login.php b/source/application/store/view/passport/login.php new file mode 100644 index 0000000..9e1da4a --- /dev/null +++ b/source/application/store/view/passport/login.php @@ -0,0 +1,67 @@ + + + + + + 商城系统登录 + + + + + + + +
+ +
+ + + + + + diff --git a/source/application/store/view/setting/address/add.php b/source/application/store/view/setting/address/add.php new file mode 100644 index 0000000..e0a2ea9 --- /dev/null +++ b/source/application/store/view/setting/address/add.php @@ -0,0 +1,60 @@ +
+
+
+
+
+
+
+
+
新增退货地址
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/address/edit.php b/source/application/store/view/setting/address/edit.php new file mode 100644 index 0000000..10ff6b6 --- /dev/null +++ b/source/application/store/view/setting/address/edit.php @@ -0,0 +1,60 @@ +
+
+
+
+
+
+
+
+
新增退货地址
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/address/index.php b/source/application/store/view/setting/address/index.php new file mode 100644 index 0000000..67718c8 --- /dev/null +++ b/source/application/store/view/setting/address/index.php @@ -0,0 +1,92 @@ +
+
+
+
+
+
退货地址列表
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + +
地址ID收货人姓名联系电话详细地址排序添加时间操作
+ +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/setting/cache/clear.php b/source/application/store/view/setting/cache/clear.php new file mode 100644 index 0000000..125ece4 --- /dev/null +++ b/source/application/store/view/setting/cache/clear.php @@ -0,0 +1,48 @@ +
+
+
+
+
+
+
+
+
清理缓存
+
+
+ +
+ $item): ?> + + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/delivery/add.php b/source/application/store/view/setting/delivery/add.php new file mode 100644 index 0000000..82ce8b8 --- /dev/null +++ b/source/application/store/view/setting/delivery/add.php @@ -0,0 +1,197 @@ +
+
+
+
+
+
+
+
+
运费模版
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
可配送区域 + {{ method == 10 ? '首件 (个)' : '首重 (Kg)' }} + 运费 (元) + {{ method == 10 ? '续件 (个)' : '续重 (Kg)' }} + 续费 (元)
+

+ 全国 + +

+

+ 编辑 + 删除 +

+ +
+ + + + + + + +
+ + + 点击添加可配送区域和运费 + +
+
+
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+ + 清空 +
+
+
+
+ +
+ +
+

+ +

+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + + diff --git a/source/application/store/view/setting/delivery/index.php b/source/application/store/view/setting/delivery/index.php new file mode 100644 index 0000000..716b69e --- /dev/null +++ b/source/application/store/view/setting/delivery/index.php @@ -0,0 +1,91 @@ +
+
+
+
+
+
运费模板
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + isEmpty()): ?> + + + + + + + + + + + + + + + + +
模板ID模板名称计费方式排序添加时间操作
+ +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/setting/express/add.php b/source/application/store/view/setting/express/add.php new file mode 100644 index 0000000..d73c30f --- /dev/null +++ b/source/application/store/view/setting/express/add.php @@ -0,0 +1,56 @@ +
+
+
+
+
+
+
+
+
新增物流公司
+
+
+ +
+ + 请对照 物流公司编码表 填写 +
+
+
+ +
+ + 用于快递100API查询物流信息,务必填写正确 +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/express/company.php b/source/application/store/view/setting/express/company.php new file mode 100644 index 0000000..eb7444d --- /dev/null +++ b/source/application/store/view/setting/express/company.php @@ -0,0 +1,5433 @@ +
+
+
+
+
+
+
物流公司编码表
+
+
+
+

友情提示:可使用Ctrl+F键 快速检索

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
公司名称公司编码
金岸物流jinan +
海带宝haidaibao +
澳通华人物流cllexpress +
斑马物流banma +
信丰物流xinfengwuliu +
德国(Deutsche Post)deutschepost +
苏宁订单suningorder +
宜送物流yiex +
AOL澳通速递aolau +
TRAKPAKtrakpak +
GTS快递gts +
通达兴物流tongdaxing +
中国香港(HongKong Post)英文hkposten +
骏丰国际速递junfengguoji +
俄罗斯邮政(Russian Post)pochta +
云达通ydglobe +
EU-EXPRESSeuexpress +
广州海关gzcustoms +
杭州海关hzcustoms +
南京海关njcustoms +
北京海关bjcustoms +
美西快递meixi +
一站通快递zgyzt +
易联通达el56 +
驿扬国际速运iyoungspeed +
途鲜物流ibenben +
豌豆物流wandougongzhu +
哥士传奇速递gscq365 +
心怡物流alog +
ME物流macroexpressco +
疯狂快递crazyexpress +
韩国邮政韩文koreapostkr +
全速物流quansu +
新杰物流sunjex +
鲁通快运lutong +
安的快递gda +
八达通bdatong +
美国申通stoexpress +
法国小包(colissimo)colissimo +
泛捷国际速递epanex +
中远e环球cosco +
顺达快递sundarexpress +
捷记方舟ajexpress +
方舟速递arkexpress +
明大快递adaexpress +
长江国际速递changjiang +
PCA Expresspcaexpress +
洋包裹yangbaoguo +
优联吉运uluckex +
德豪驿dehaoyi +
堡昕德速递bosind +
阿根廷(Correo Argentina)correoargentino +
秘鲁(SERPOST)peru +
哈萨克斯坦(Kazpost)kazpost +
广通速递gtongsudi +
东瀚物流donghanwl +
rpxrpx +
黑猫雅玛多yamato +
华通快运htongexpress +
吉尔吉斯斯坦(Kyrgyz Post)kyrgyzpost +
拉脱维亚(Latvijas Pasts)latvia +
黎巴嫩(Liban Post)libanpost +
立陶宛(Lietuvos pa?tas)lithuania +
马尔代夫(Maldives Post)maldives +
马耳他(Malta Post)malta +
马其顿(Macedonian Post)macedonia +
新西兰(New Zealand Post)newzealand +
摩尔多瓦(Posta Moldovei)moldova +
塞尔维亚(PE Post of Serbia)serbia +
塞浦路斯(Cyprus Post)cypruspost +
突尼斯EMS(Rapid-Poste)tunisia +
乌兹别克斯坦(Post of Uzbekistan)uzbekistan +
新喀里多尼亚[法国](New Caledonia)caledonia +
叙利亚(Syrian Post)republic +
亚美尼亚(Haypost-Armenian Postal)haypost +
也门(Yemen Post)yemen +
印度(India Post)india +
英国(大包,EMS)england +
约旦(Jordan Post)jordan +
越南小包(Vietnam Posts)vietnam +
黑山(Po?ta Crne Gore)montenegro +
哥斯达黎加(Correos de Costa Rica)correos +
EFS Post(平安快递)efs +
TNT Posttntpostcn +
立白宝凯物流lbbk +
匈牙利(Magyar Posta)hungary +
中国澳门(Macau Post)macao +
西安喜来快递xilaikd +
韩润hanrun +
格陵兰[丹麦](TELE Greenland A/S)greenland +
菲律宾(Philippine Postal)phlpost +
厄瓜多尔(Correos del Ecuador)ecuador +
冰岛(Iceland Post)iceland +
波兰小包(Poczta Polska)emonitoring +
阿尔巴尼亚(Posta shqipatre)albania +
埃及(Egypt Post)egypt +
爱沙尼亚(Eesti Post)omniva +
云豹国际货运leopard +
中外运空运sinoairinex +
上海昊宏国际货物hyk +
城晓国际快递ckeex +
中铁快运ztky +
出口易chukou1 +
跨畅(直邮易)kuachangwuliu +
WTD海外通wtdex +
CHS中环国际快递chszhonghuanguoji +
汉邦国际速递handboy +
银河物流milkyway +
荷兰速递(Nederland Post)nederlandpost +
澳州顺风快递emms +
环东物流huandonglg +
中邮速递wondersyd +
布谷鸟速递cuckooexpess +
万庚国际速递vangenexpress +
FedRoad 联邦转运fedroad +
Landmark Globallandmarkglobal +
佳成快递jiacheng +
诺尔国际物流nuoer +
加运美速递jym56 +
新时速物流csxss +
中宇天地zytdscm +
翔腾物流xiangteng +
恒瑞物流hengrui56 +
中国翼cnws +
邦工快运bgky100 +
上海无疆for买卖宝shanghaiwujiangmmb +
新加坡小包(Singapore Post)singpost +
中俄速通(淼信)mxe56 +
海派通hipito +
源安达yuananda +
赛澳递for买卖宝saiaodimmb +
ECMS Expressecmsglobal +
英脉物流gml +
佳家通货运jiajiatong56 +
吉日优派jrypex +
西安胜峰xaetc +
logen路坚ilogen +
amazon-国际订单amusorder +
CJ物流doortodoor +
转运四方zhuanyunsifang +
成都东骏物流dongjun +
日本郵便japanpost +
猴急送hjs +
全信通快递quanxintong +
信天捷快递xintianjie +
泰国138国际物流sd138 +
荷兰包裹(PostNL International Parcels)postnlpacle +
乐天速递ltexp +
智通物流ztong +
全速通quansutong +
中技物流zhongjiwuliu +
九曳供应链jiuyescm +
当当dangdang +
美龙快递mjexp +
唯品会(vip)vipshop +
1号店yhdshop +
皇家物流pfcexpress +
百千诚物流bqcwl +
法国(La Poste)csuivi +
DHL-全球件dhlen +
运通中港yuntongkuaidi +
苏宁物流suning +
荷兰Sky Postskypost +
瑞达国际速递ruidaex +
丰程物流sccod +
德中快递decnlh +
全时速运runhengfeng +
云邮跨境快递hkems +
亚风速递yafengsudi +
快淘快递kuaitao +
鑫通宝物流xtb +
USPSusps +
加拿大邮政canpostfr +
汇通天下物流httx56 +
台湾(中华邮政)postserv +
好又快物流haoyoukuai +
永旺达快递yongwangda +
木春货运mchy +
程光快递flyway +
百事亨通bsht +
万家通快递timedg +
全之鑫物流qzx56 +
美快国际物流meiquick +
ILYANGilyang +
先锋快递xianfeng +
亿顺航yishunhang +
尚橙物流shangcheng +
OnTracontrac +
TNT-全球件tnten +
顺丰-美国件shunfengen +
共速达gongsuda +
源伟丰yuanweifeng +
祥龙运通物流xianglongyuntong +
偌亚奥国际快递nuoyaao +
陪行物流peixingwuliu +
天天快递tiantian +
CCES/国通快递cces +
彪记快递biaojikuaidi +
安信达anxindakuaixi +
配思货运peisihuoyunkuaidi +
大田物流datianwuliu +
邮政快递包裹youzhengguonei +
文捷航空wenjiesudi +
BHTbht +
北青小红帽xiaohongmao +
GSMgsm +
汇强快递huiqiangkuaidi +
昊盛物流haoshengwuliu +
联邦快递-英文lianbangkuaidien +
伍圆速递wuyuansudi +
南京100nanjing +
全通快运quantwl +
宅急便zhaijibian +
加拿大(Canada Post)canpost +
COEcoe +
百通物流buytong +
友家速递youjia +
新元快递xingyuankuaidi +
中澳速递cnausu +
联合快递gslhkd +
河南次晨达ccd +
奔腾物流benteng +
今枫国际快运mapleexpress +
中运全速topspeedex +
中欧快运otobv +
宜家行yjxlm +
金马甲jmjss +
一号仓onehcang +
论道国际物流lundao +
顺通快递stkd +
globaltracktraceglobaltracktrace +
德方物流ahdf +
速递中国sendtochina +
NLEnle +
亚欧专线nlebv +
信联通sinatone +
澳德物流auod +
微转运wzhaunyun +
iExpressiexpress +
远成快运ycgky +
高考通知书emsluqu +
安鲜达exfresh +
BCWELTbcwelt +
欧亚专线euasia +
乐递供应链ledii +
万通快递gswtkd +
特急送lntjs +
金大物流jindawuliu +
民航快递minghangkuaidi +
红马甲物流sxhongmajia +
amazon-国内订单amcnorder +
ABFabf +
小米xiaomi +
新元国际xynyc +
小C海淘xiaocex +
航空快递airgtc +
叮咚澳洲转运dindon +
环球通达hqtd +
新西兰中通nzzto +
良藤国际速递lmfex +
速品快递supinexpress +
海龟国际快递turtle +
韩国邮政koreapostcn +
韵丰物流yunfeng56 +
易达通快递qexpress +
一运全成物流yyqc56 +
泛远国际物流farlogistis +
达速物流dasu +
恒通快递lqht +
壹品速递ypsd +
鹰运国际速递vipexpress +
南方传媒物流ndwl +
速呈宅配sucheng +
云南滇驿物流dianyi +
四川星程快递scxingcheng +
运通中港快递ytkd +
Gati-英文gatien +
jcexjcex +
凯信达kxda +
安达信advancing +
亿翔yxexpress +
加运美jiayunmeiwuliu +
赛澳递saiaodi +
康力物流kangliwuliu +
鑫飞鸿xinhongyukuaidi +
全一快递quanyikuaidi +
华企快运huaqikuaiyun +
青岛安捷快递anjiekuaidi +
递四方disifang +
三态速递santaisudi +
成都立即送lijisong +
河北建华hebeijianhua +
风行天下fengxingtianxia +
一统飞鸿yitongfeihong +
海外环球haiwaihuanqiu +
DHL-中国件dhl +
西安城联速递xianchengliansudi +
一柒国际物流yiqiguojiwuliu +
广东通路guangdongtonglu +
中国香港骏辉物流chunfai +
三三国际物流zenzen +
比利时国际(Bpost international)bpostinter +
海红for买卖宝haihongmmb +
FedEx-英国件(FedEx UK)fedexuk +
FedEx-英国件fedexukcn +
叮咚快递dingdong +
MRWmrw +
Chronopost Portugalchronopostport +
西班牙(Correos de Espa?a)correosdees +
丹麦(Post Denmark)postdanmarken +
Purolatorpurolator +
法国大包、EMS-法文(Chronopost France)chronopostfra +
Selektvrachtselektvracht +
蓝弧快递lanhukuaidi +
比利时(Belgium Post)belgiumpost +
晟邦物流nanjingshengbang +
UPS Mail Innovationsupsmailinno +
挪威(Posten Norge)postennorge +
瑞士(Swiss Post)swisspost +
英国邮政小包royalmailcn +
英国小包(Royal Mail)royalmail +
DHL Beneluxdhlbenelux +
DHL-荷兰(DHL Netherlands)dhlnetherlands +
OPEKopek +
Italy SDAitalysad +
Fastway Irelandfastway +
DHL-波兰(DHL Poland)dhlpoland +
DPDdpd +
速通物流sutongwuliu +
荷兰邮政-中文(PostNL international registered mail)postnlcn +
荷兰邮政(PostNL international registered mail)postnl +
乌克兰EMS(EMS Ukraine)emsukraine +
乌克兰邮政包裹ukrpostcn +
英国大包、EMS(Parcel Force)parcelforce +
YODELyodel +
UBI Australiagotoubi +
红马速递nedahm +
云南诚中物流czwlyn +
万博快递wanboex +
腾达速递nntengda +
郑州速捷sujievip +
中睿速递zhongruisudi +
中天万运zhongtianwanyun +
新蛋奥硕neweggozzo +
七天连锁sevendays +
UPS-全球件upsen +
跨越速运kuayue +
全际通quanjitong +
UPSups +
一邦速递yibangwuliu +
上海快通shanghaikuaitong +
品速心达快递pinsuxinda +
PostNord(Posten AB)postenab +
城际速递chengjisudi +
户通物流hutongwuliu +
飞康达feikangda +
星晨急便xingchengjibian +
全日通quanritongkuaidi +
凤凰快递fenghuangkuaidi +
广东邮政guangdongyouzhengwuliu +
长宇物流changyuwuliu +
万家物流wanjiawuliu +
EMS-国际件-英文emsinten +
飞远配送feiyuanvipshop +
国美gome +
能达速递ganzhongnengda +
急先达jixianda +
凡宇快递fanyukuaidi +
希优特xiyoutekuaidi +
中通(带电话)zhongtongphone +
蓝镖快递lanbiaokuaidi +
佳吉快运jiajiwuliu +
宏品物流hongpinwuliu +
GLSgls +
原飞航yuanfeihangwuliu +
海红网送haihongwangsong +
TNTtnt +
元智捷诚yuanzhijiecheng +
国际包裹youzhengguoji +
城市100city100 +
DPEXdpex +
芝麻开门zhimakaimen +
EMS-国际件emsguoji +
晋越快递jinyuekuaidi +
乐捷递lejiedi +
飞力士物流flysman +
百腾物流baitengwuliu +
品骏快递pjbest +
瓦努阿图(Vanuatu Post)vanuatu +
巴巴多斯(Barbados Post)barbados +
萨摩亚(Samoa Post)samoa +
斐济(Fiji Post)fiji +
英超物流yingchao +
TNY物流tny +
美通valueway +
新速航sunspeedy +
速方(Sufast)bphchina +
华航快递hzpl +
Gati-KWEgatikwe +
Red Expressredexpress +
Toll Priority(Toll Online)tollpriority +
Estafetaestafeta +
港快速递gdkd +
墨西哥(Correos de Mexico)mexico +
罗马尼亚(Posta Romanian)romanian +
DPD Polanddpdpoland +
阿联酋(Emirates Post)emirates +
新顺丰(NSF)nsf +
巴基斯坦(Pakistan Post)pakistan +
Asendia USAasendiausa +
法国大包、EMS-英文(Chronopost France)chronopostfren +
意大利(Poste Italiane)italiane +
世运快递shiyunkuaidi +
新干线快递anlexpress +
飞洋快递shipgce +
贝海国际速递xlobo +
黄马甲huangmajia +
Tolldpexen +
如风达rufengda +
EC-Firstclassecfirstclass +
DTDC Indiadtdcindia +
Safexpresssafexpress +
泰国(Thailand Thai Post)thailand +
SkyNet Malaysiaskynetmalaysia +
TNT Australiatntau +
马来西亚小包(Malaysia Post(Registered))malaysiapost +
马来西亚大包、EMS(Malaysia Post(parcel,EMS))malaysiaems +
沙特阿拉伯(Saudi Post)saudipost +
南非(South African Post Office)southafrican +
Mexico Senda Expressmexicodenda +
MyHermesmyhermes +
DPD Germanydpdgermany +
Nova Poshtanovaposhta +
Estesestes +
TNT UKtntuk +
Deltec Courierdeltec +
UPS Freightupsfreight +
TNT Italytntitaly +
Mexico Multipackmultipack +
葡萄牙(Portugal CTT)portugalctt +
Interlink Expressinterlink +
DPD UKdpduk +
乌克兰EMS-中文(EMS Ukraine)emsukrainecn +
乌克兰小包、大包(UkrPost)ukrpost +
TCI XPStcixps +
高铁速递hre +
新加坡EMS、大包(Singapore Speedpost)speedpost +
LaserShiplasership +
英国邮政大包EMSparcelforcecn +
同舟行物流chinatzx +
秦邦快运qbexpress +
skynetskynet +
忠信达zhongxinda +
门对门menduimen +
微特派weitepai +
海盟速递haimengsudi +
圣安物流shenganwuliu +
联邦快递lianbangkuaidi +
飞快达feikuaida +
EMSems +
天地华宇tiandihuayu +
煜嘉物流yujiawuliu +
郑州建华zhengzhoujianhua +
大洋物流dayangwuliu +
递达速运didasuyun +
易通达yitongda +
邮必佳youbijia +
EMS-英文emsen +
闽盛快递minshengkuaidi +
佳惠尔syjiahuier +
KCSkcs +
ADP国际快递adp +
颿达国际快递fardarww +
颿达国际快递-英文fandaguoji +
林道国际快递shlindao +
中外运速递-中文sinoex +
中外运速递zhongwaiyun +
深圳德创物流dechuangwuliu +
林道国际快递-英文ldxpres +
中国香港(HongKong Post)hkpost +
邦送物流bangsongwuliu +
华赫物流nmhuahe +
顺捷丰达shunjiefengda +
天马迅达tianma +
恒宇运通hyytes +
考拉国际速递kaolaexpress +
BlueDartbluedart +
日日顺快线rrskx +
运东西yundx +
黑狗物流higo +
鹏远国际速递pengyuanexpress +
安捷物流anjie88 +
骏达快递jdexpressusa +
C&C国际速递cncexp +
北京EMSbjemstckj +
airpak expresssairpak +
荷兰邮政-中国件postnlchina +
大达物流idada +
益递物流edlogistics +
中外运esinotrans +
速派快递(FastGo)fastgo +
易客满ecmscn +
美国云达yundaexus +
Tolltoll +
深圳DPEXszdpex +
俄顺达eshunda +
广东速腾物流suteng +
新鹏快递gdxp +
平安达腾飞pingandatengfei +
穗佳物流suijiawuliu +
传喜物流chuanxiwuliu +
捷特快递jietekuaidi +
隆浪快递longlangkuaidi +
佳吉快递jiajikuaidi +
快达物流kuaidawuliu +
飞狐快递feihukuaidi +
潇湘晨报xiaoxiangchenbao +
巴伦支balunzhi +
安能物流annengwuliu +
申通快递shentong +
亿领速运yilingsuyun +
店通快递diantongkuaidi +
OCA Argentinaocaargen +
尼日利亚(Nigerian Postal)nigerianpost +
智利(Correos Chile)chile +
以色列(Israel Post)israelpost +
京东物流jd +
奥地利(Austrian Post)austria +
乌克兰小包、大包(UkrPoshta)ukraine +
乌干达(Posta Uganda)uganda +
阿塞拜疆EMS(EMS AzerExpressPost)azerbaijan +
芬兰(Itella Posti Oy)finland +
斯洛伐克(Slovenská Posta)slovak +
阿鲁巴[荷兰](Post Aruba)aruba +
爱尔兰(An Post)ireland +
印度尼西亚EMS(Pos Indonesia-EMS)indonesia +
易优包裹eupackage +
威时沛运货运wtdchina +
行必达speeda +
中通国际zhongtongguoji +
千顺快递qskdyxgs +
西邮寄xipost +
顺捷达shunjieda +
CE易欧通国际速递cloudexpress +
和丰同城hfwuxi +
天联快运tlky +
优速物流youshuwuliu +
埃塞俄比亚(Ethiopian postal)ethiopia +
卢森堡(Luxembourg Post)luxembourg +
毛里求斯(Mauritius Post)mauritius +
文莱(Brunei Postal)brunei +
Quantiumquantium +
中铁物流zhongtiewuliu +
宇鑫物流yuxinwuliu +
巴林(Bahrain Post)bahrain +
纳米比亚(NamPost)namibia +
卢旺达(Rwanda i-posita)rwanda +
莱索托(Lesotho Post)lesotho +
肯尼亚(POSTA KENYA)kenya +
喀麦隆(CAMPOST)cameroon +
伯利兹(Belize Postal)belize +
巴拉圭(Correo Paraguayo)paraguay +
波黑(JP BH Posta)bohei +
玻利维亚bolivia +
柬埔寨(Cambodia Post)cambodia +
兰州伙伴物流huoban +
天纵物流tianzong +
坦桑尼亚(Tanzania Posts)tanzania +
阿曼(Oman Post)oman +
直布罗陀[英国]( Royal Gibraltar Post)gibraltar +
展勤快递byht +
越南EMS(VNPost Express)vnpost +
安迅物流anxl +
达方物流dfpost +
十方通物流sfift +
飞鹰物流hnfy +
UPS i-parceliparcel +
鑫锐达bjxsrd +
孟加拉国(EMS)bangladesh +
快捷速递kuaijiesudi +
日本(Japan Post)japanposten +
众辉达物流zhdwl +
秦远物流qinyuan +
澳邮中国快运auexpress +
日益通速递rytsd +
航宇快递hangyu +
急顺通pzhjst +
优速通达yousutongda +
飞邦快递fbkd +
华达快运huada +
FOX国际快递fox +
佳怡物流jiayiwuliu +
鹏程快递pengcheng +
冠庭国际物流guanting +
美国快递meiguokuaidi +
通和天下tonghetianxia +
音素快运yinsu +
创一快递chuangyi +
重庆星程快递cqxingcheng +
贵州星程快递gzxingcheng +
河南全速通hnqst +
快速递ksudi +
北极星快运polarisexpress +
6LS EXPRESSlsexpress +
ANTS EXPRESSqdants +
S2Cs2c +
Hi淘易快递hitaoe +
CNAIRcnair +
易欧洲国际物流yiouzhou +
阳光快递shiningexpress +
北京丰越供应链beijingfengyue +
华中快递cpsair +
青旅物流zqlwl +
易航物流yihangmall +
城铁速递cex +
千里速递qianli +
急递jdpplus +
佳捷翔物流jjx888 +
洋口岸ykouan +
考拉速递koalaexp +
天越物流surpassgo +
邮政标准快递youzhengbk +
运通快运ytky168 +
卢森堡航空cargolux +
优优速递youyou +
全川物流quanchuan56 +
SYNSHIP快递synship +
仓鼠快递cangspeed +
递五方云仓di5pll +
卓志速运chinaicip +
闪电兔shandiantu +
新宁物流xinning +
春风物流spring56 +
首达速运sdsy888 +
丽狮物流lishi +
雅澳物流yourscm +
直德邮zdepost +
日昱物流riyuwuliu +
Gati-中文gaticn +
派尔快递peex +
汇文huiwen +
东红物流donghong +
增益速递zengyisudi +
好运来hlyex +
顺丰速运shunfeng +
城际快递chengji +
程光快递chengguangkuaidi +
天翼快递tykd +
京东订单jdorder +
蓝天快递lantiankuaidi +
永昌物流yongchangwuliu +
笨鸟海淘birdex +
一正达速运yizhengdasuyun +
德意思dabei +
佐川急便sagawa +
优配速运sdyoupei +
速必达subida +
景光物流jgwl +
御风速运yufeng +
至诚通达快递zhichengtongda +
特急便物流sucmj +
亚马逊中国yamaxunwuliu +
货运皇kingfreight +
锦程物流jinchengwuliu +
澳货通auex +
澳速物流aosu +
澳世速递aus +
环球速运huanqiu +
麦力快递mailikuaidi +
瑞丰速递rfsd +
美联快递letseml +
CNPEX中邮快递cnpex +
鑫世锐达xsrd +
顺丰优选sfbest +
全峰快递quanfengkuaidi +
克罗地亚(Hrvatska Posta)hrvatska +
保加利亚(Bulgarian Posts)bulgarian +
Portugal Seurportugalseur +
International Seurseur +
久易快递jiuyicn +
Direct Linkdirectlink +
希腊EMS(ELTA Courier)eltahell +
捷克(?eská po?ta)ceskaposta +
Siodemkasiodemka +
爱尔兰(An Post)anposten +
渥途国际速运wotu +
一号线lineone +
四海快递sihaiet +
德坤物流dekuncn +
准实快运zsky123 +
宏捷国际物流hongjie +
鸿讯物流hongxun +
卡邦配送ahkbps +
凡客配送(作废)vancl +
瑞士邮政swisspostcn +
辉联物流huilian +
A2U速递a2u +
UEQ快递ueq +
中加国际快递scic +
易达通yidatong +
宜送yisong +
全球快运abcglobal +
芒果速递mangguo +
金海淘goldhaitao +
极光转运jiguang +
富腾达国际货运ftd +
DCSdcs +
捷网俄全通ruexp +
华通务达物流htwd +
申必达speedoex +
联运快递lianyun +
捷安达jieanda +
SHL畅灵国际物流shlexp +
EWE全球快递ewe +
顺邦国际物流shunbang +
成达国际速递chengda +
启辰国际速递qichen +
合众速递(UCS)ucs +
阿富汗(Afghan Post)afghan +
白俄罗斯(Belpochta)belpost +
冠捷物流gjwl +
钏博物流cbo56 +
西翼物流westwing +
优邦速运ubonex +
首通快运staky +
马珂博逻cnmcpl +
小熊物流littlebearbear +
玥玛速运yue777 +
上海航瑞货运hangrui +
星云速递nebuex +
环创物流ghl +
林安物流lasy56 +
笨鸟国际benniao +
全速快递fsexp +
法翔速运ftlexpress +
易转运ezhuanyuan +
Superb Gracesuperb +
蓝天国际快递ltx +
圣飞捷快递sfjhd +
淘韩国际快递krtao +
容智快运gdrz58 +
锦程快递hrex +
顺时达物流hnssd56 +
骏绅物流jsexpress +
德国雄鹰速递adlerlogi +
远为快递ywexpress +
嗖一下同城快递sofast56 +
开心快递happylink +
五六快运wuliuky +
卓烨快递hrbzykd +
ZTE中兴物流zteexpress +
尼尔快递nell +
高铁快运gaotieex +
万家康物流wjkwl +
国晶物流xdshipping +
德国云快递yunexpress +
宏递快运hd +
一起送yiqisong +
迈隆递运mailongdy +
新亚物流nalexpress +
艾瑞斯远ariesfar +
澳多多国际速递adodoxm +
CNUP 中联邮cnup +
UEX国际物流uex +
Hermeshermes +
PostElbepostelbe +
维普恩物流vps +
明辉物流zsmhwl +
联运通物流szuem +
龙象国际物流edragon +
永邦国际物流yongbangwuliu +
51跨境通wykjt +
速配欧翼superoz +
嘉里大荣物流kerrytj +
中国香港环球快运huanqiuabc +
CL日中速运clsp +
SQK国际速递chinasqk +
家家通快递newsway +
邮客全球速递yyox +
华瀚快递hhair56 +
顺士达速运shunshid +
天翔东捷运djy56 +
卓实快运zhuoshikuaiyun +
吉祥邮(澳洲)jixiangyouau +
蓝天快递blueskyexpress +
天天快物流guoeryue +
纵通速运ynztsy +
中通快运zhongtongkuaiyun +
CNEcnexps +
希腊包裹(ELTA Hellenic Post)elta +
星速递starex +
土耳其ptt +
哥伦比亚(4-72 La Red Postal de Colombia)colombia +
加州猫速递jiazhoumao +
捷邦物流jieborne +
邮政国内yzguonei +
Canparcanpar +
海硕高铁速递hsgtsd +
日日通国际rrthk +
天翼物流tywl99 +
啪啪供应链papascm +
万达美wdm +
安得物流annto +
广东诚通物流gdct56 +
安达速递adapost +
易达国际速递eta100 +
西游寄xiyoug +
光线速递gxwl +
易邮国际euguoji +
深圳邮政szyouzheng +
粤中国际货运代理(上海)有限公司yuezhongsh +
城通物流chengtong +
GE2D跨境物流ge2d +
败欧洲europe8 +
飛斯特bester +
蒙古国(Mongol Post)mongolpost +
乌拉圭(Correo Uruguayo)correo +
牙买加(Jamaica Post)jamaicapost +
格鲁吉亚(Georgian Pos)georgianpost +
美达快递meidaexpress +
驭丰速运yfsuyun +
无忧物流aliexpress +
邮鸽速运ugoexpress +
澳洲新干线快递expressplus +
标杆物流bmlchina +
长风物流longvast +
邮来速递youlai +
魔速达mosuda +
商桥物流shangqiao56 +
AUV国际快递auvexpress +
Newgisticsnewgistics +
FQ狂派速递freakyquick +
泽西岛jerseypost +
威盛快递wherexpess +
运通速运yuntong +
老挝(Lao Express)lao +
巴布亚新几内亚(PNG Post)postpng +
EASY EXPRESSeasyexpress +
壹米滴答yimidida +
飞云快递系统fyex +
跨跃国际kyue +
EMS包裹emsbg +
珠峰速运zf365 +
甘肃安的快递gansuandi +
一辉物流yatfai +
e直运edtexpress +
wish邮shpostwish +
顶世国际物流topshey +
龙枫国际快递lfexpress +
安能快递ane66 +
圆通快运yuantongkuaiyun +
宝通快递baotongkd +
美国汉邦快递aplus100 +
易普递sixroad +
速呈sczpds +
海淘物流ht22 +
海米派物流haimibuy +
天翔快递tianxiang +
易境达国际物流uscbexpress +
大韩通运cjkoreaexpress +
澳世速递ausexpress +
未来明天快递weilaimingtian +
科捷物流kejie +
大道物流dadaoex +
全联速运guexp +
可可树美中速运excocotree +
邮邦国际youban +
西安运逸快递yyexp +
Aplus物流aplusex +
锋鸟物流beebird +
青云物流bjqywl +
万邑通winit +
中翼国际物流chnexp +
亚洲顺物流yzswuliu +
E跨通ecallturn +
递四方美国disifangus +
星空国际wlwex +
极地快递polarexpress +
到了港camekong +
斯里兰卡(Sri Lanka Post)slpost +
斯洛文尼亚(Slovenia Post)slovenia +
多米尼加(INPOSDOM – Instituto Postal Dominicano)inposdom +
星运快递staryvr +
狮爱高铁物流sycawl +
爱拜物流ibuy8 +
商海德物流shd56 +
九宫物流jiugong +
缔惠盛合twkd56 +
快服务kfwnet +
dhl小包dhlecommerce +
宇佳物流yujiawl +
湘达物流xiangdawuliu +
远盾物流yuandun +
黑猫宅急便tcat +
韵达快运yundakuaiyun +
速派快递fastgoexpress +
中集冷云cccc58 +
久久物流jiujiuwl +
德国八易转运deguo8elog +
UTAO优到utaoscm +
乾坤物流yatexpress +
摩洛哥 ( Morocco Post )morocco +
尼泊尔(Nepal Postal Services)nepalpost +
伊朗(Iran Post)iran +
坦桑尼亚(Tanzania Posts Corporation)posta +
莫桑比克(Correios de Moçambique)correios +
聚中大juzhongda +
中邮电商chinapostcb +
鸿泰物流hnht56 +
南非EMSemssouthafrica +
申通国际stosolution +
皮牙子快递bazirim +
联众国际epspost +
丰通快运ftky365 +
BorderGuruborderguru +
艾姆勒imlb2c +
中欧国际物流eucnrail +
递四方澳洲disifangau +
艺凡快递yifankd +
宏观国际快递gvpexpress +
博茨瓦纳botspost +
塞内加尔laposte +
卡塔尔(Qatar Post)qpost +
苏丹(Sudapost)sudapost +
Sureline冠泰sureline +
海沧无忧hivewms +
安世通快递astexpress +
集先锋快递jxfex +
丰客物流fecobv +
同城快寄shpost +
海联快递hltop +
中联速递auvanda +
三象速递sxexpress +
神马快递shenma +
互联快运hlkytj +
温通物流wto56kj +
四海捷运sihiexpress +
苏通快运zjstky +
邦通国际comexpress +
劲通快递jintongkd +
凡仕特物流wlfast +
红背心hongbeixin +
居家通homexpress +
上大物流shangda +
中邮物流zhongyouwuliu +
Fedex-国际件-中文fedexcn +
韩国(Korea Post)koreapost +
中通快递zhongtong +
京广速递jinguangsudikuaijian +
FedEx-国际件fedex +
日日顺物流rrs +
微店weidianorder +
当当dangdangorder +
国送快运guosong +
考拉订单kaolaorder +
AAE-中国件aae +
四川快优达速递kuaiyouda +
百福东方baifudongfang +
TST速运通tstexp +
YUN TRACKyuntrack +
招金精炼zhaojin +
全程快递agopost +
CDEKcdek +
签收快递signedexpress +
佰麒快递beckygo +
增速跨境zyzoom +
Aramexaramex +
越中国际物流vctrans +
德国优拜物流ubuy +
德尚国际速递gslexpress +
德国 EUC POSTeucpost +
泰国中通ZTOthaizto +
泰国中通CTOctoexp +
顺丰-繁体shunfenghk +
嘉诚速达jcsuda +
AFLafl +
众派速递zhpex +
海星桥快递haixingqiao +
蘑菇街mogujieorder +
嘉里大通jialidatong +
万象物流wanxiangwuliu +
澳大利亚(Australia Post)auspost +
国通快递guotongkuaidi +
全晨快递quanchenkuaidi +
飞豹快递feibaokuaidi +
中速快递zhongsukuaidi +
优能物流mantoo +
国美gomeorder +
亚马逊中国订单amazoncnorder +
蜜芽订单miaorder +
顺丰订单sfexpressorder +
申通快运stoe56 +
City-Linkcitylink +
德邦物流debangwuliu +
银捷速递yinjiesudi +
D速快递dsukuaidi +
民邦速递minbangsudi +
百世快运baishiwuliu +
DHL-德国件(DHL Deutschland)dhlde +
能装能送canhold +
聚美优品jumeiyoupinorder +
诚一物流parcelchina +
网易严选wangyiyxorder +
龙邦速递longbanwuliu +
明亮物流mingliangwuliu +
速尔快递suer +
盛辉物流shenghuiwuliu +
越丰物流yuefengwuliu +
比利时(Bpost)bpost +
韵达快递yunda +
唯品会vipshoporder +
美丽说meilishuoorder +
顺丰优选sfbestorder +
驼峰国际humpline +
小米订单xiaomiorder +
一智通1ziton +
TransRushtransrush +
百世快递huitongkuaidi +
联昊通lianhaowuliu +
远成物流yuanchengwuliu +
FedEx-美国件fedexus +
OCSocs +
巴西(Brazil Post/Correios)brazilposten +
孔夫子kongfzorder +
一号店yhdshoporder +
卷皮juanpiorder +
淘宝订单taobaoorder +
盛丰物流shengfengwuliu +
瑞典(Sweden Post)ruidianyouzheng +
圆通速递yuantong +
宅急送zhaijisong +
新邦物流xinbangwuliu +
恒路物流hengluwuliu +
华夏龙huaxialongwuliu +
龙飞祥快递longfx +
城市映急city56 +
五六快运56kuaiyun +
速通物流sut56 +
迅达速递xdexpress +
JDIEXjdiex +
泰捷达国际物流ztjieda +
捎客物流shaoke +
全球速递pdstow +
安达易国际速递adiexpress +
番薯国际货运koali +
贝贝beibeiorder +
德邦快递debangkuaidi +
联合速运unitedex +
龙邦物流lbex +
GHT物流ghtexpress +
香港伟豪国际物流whgjkd +
澳洲迈速快递maxeedexpress +
TCXB国际物流tcxbthai +
波音速递overseaex +
鑫远东速运xyd666 +
中环快递zhonghuan +
沃埃家wowvip +
OBOR Expressoborexpress +
盛丰物流sfwl +
速达通sdto +
苏豪快递shipsoho +
三盛快递sanshengco +
迅速快递xunsuexpress +
众川国际zhongchuan +
陆本速递 LUBEN EXPRESSluben +
西濃運輸seino +
加拿大联通快运fastontime +
花瓣转运flowerkd +
合心速递hexinexpress +
Highsincehighsince +
蜜蜂速递bee001 +
天使物流云tswlcloud +
王牌快递shipbyace +
华美快递hmus +
折800zhe800order +
小红书xiaohongshuorder +
铁中快运tzky +
景顺物流jingshun +
中环转运zhonghuanus +
YCG物流ycgglobal +
驿递汇速递yidihui
+
+
+
+
+
+
\ No newline at end of file diff --git a/source/application/store/view/setting/express/edit.php b/source/application/store/view/setting/express/edit.php new file mode 100644 index 0000000..1c9a7fa --- /dev/null +++ b/source/application/store/view/setting/express/edit.php @@ -0,0 +1,58 @@ +
+
+
+
+
+
+
+
+
编辑物流公司
+
+
+ +
+ + 请对照 物流公司编码表 填写 +
+
+
+ +
+ + 用于快递100API查询物流信息,务必填写正确 +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/express/index.php b/source/application/store/view/setting/express/index.php new file mode 100644 index 0000000..f4130db --- /dev/null +++ b/source/application/store/view/setting/express/index.php @@ -0,0 +1,91 @@ +
+
+
+
+
+
物流公司列表
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + isEmpty()): ?> + + + + + + + + + + + + + + + + +
物流公司ID物流公司名称物流公司代码排序添加时间操作
+ +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/setting/help/tplMsg.php b/source/application/store/view/setting/help/tplMsg.php new file mode 100644 index 0000000..47407b5 --- /dev/null +++ b/source/application/store/view/setting/help/tplMsg.php @@ -0,0 +1,29 @@ +
+
+
+
+
+
+
如何获取模板消息ID
+
+
+

1. 进入微信小程序官方后台,找到模板库

+

+
+
+

2. 根据模板编号,选用指定的模板

+

+
+
+

3. 选择指定的关键词,并调整好顺序,点击提交

+

+
+
+

4. 复制模板ID

+

+
+
+
+
+
+
\ No newline at end of file diff --git a/source/application/store/view/setting/printer.php b/source/application/store/view/setting/printer.php new file mode 100644 index 0000000..039a052 --- /dev/null +++ b/source/application/store/view/setting/printer.php @@ -0,0 +1,71 @@ +
+
+
+
+
+
+
+
+
小票打印设置
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/printer/add.php b/source/application/store/view/setting/printer/add.php new file mode 100644 index 0000000..ad5e047 --- /dev/null +++ b/source/application/store/view/setting/printer/add.php @@ -0,0 +1,130 @@ + +
+
+
+
+
+
+
+
+
新增小票打印机
+
+
+ +
+ +
+
+
+ +
+ +
+ 目前支持 飞鹅打印机、365云打印 +
+
+
+ + +
+
+ +
+ + 飞鹅云后台注册用户名 +
+
+
+ +
+ + 飞鹅云后台登录生成的UKEY +
+
+
+ +
+ + 打印机编号为9位数字,查看飞鹅打印机底部贴纸上面的编号 +
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + 同一订单,打印的次数 +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/printer/edit.php b/source/application/store/view/setting/printer/edit.php new file mode 100644 index 0000000..2c1ce35 --- /dev/null +++ b/source/application/store/view/setting/printer/edit.php @@ -0,0 +1,141 @@ + +
+
+
+
+
+
+
+
+
编辑小票打印机
+
+
+ +
+ +
+
+
+ +
+ +
+ 目前支持 飞鹅打印机、365云打印 +
+
+
+ + +
+
+ +
+ + 飞鹅云后台注册用户名 +
+
+
+ +
+ + 飞鹅云后台登录生成的UKEY +
+
+
+ +
+ + 打印机编号为9位数字,查看飞鹅打印机底部贴纸上面的编号 +
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + 同一订单,打印的次数 +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/printer/index.php b/source/application/store/view/setting/printer/index.php new file mode 100644 index 0000000..87ceab7 --- /dev/null +++ b/source/application/store/view/setting/printer/index.php @@ -0,0 +1,91 @@ +
+
+
+
+
+
小票打印机列表
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + isEmpty()): ?> + + + + + + + + + + + + + + + + +
打印机ID打印机名称打印机类型排序添加时间操作
+ +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/setting/sms.php b/source/application/store/view/setting/sms.php new file mode 100644 index 0000000..d9803f5 --- /dev/null +++ b/source/application/store/view/setting/sms.php @@ -0,0 +1,156 @@ +
+
+
+
+
+
+
+
+
短信通知(阿里云短信)
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
新付款订单提醒
+
+
+ +
+ + +
+
+
+ +
+ + 例如:SMS_139800030 +
+
+
+
+ 模板内容:您有一条新订单,订单号为:${order_no},请注意查看。 +
+
+
+ +
+ +
+ 注:如需填写多个手机号,可用英文逗号 , 隔开 +
+
+ 接收测试: 点击发送 + +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/storage.php b/source/application/store/view/setting/storage.php new file mode 100644 index 0000000..be79f44 --- /dev/null +++ b/source/application/store/view/setting/storage.php @@ -0,0 +1,197 @@ +
+
+
+
+
+
+
+
+
文件上传设置
+
+
+ +
+ + + + +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 请补全http:// 或 https://,例如:http://static.cloud.com +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 请补全http:// 或 https://,例如:http://static.cloud.com +
+
+
+
+
+ +
+ +
+
+
+ +
+ + 请填写地域简称,例如:ap-beijing、ap-hongkong、eu-frankfurt +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 请补全http:// 或 https://,例如:http://static.cloud.com +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/store.php b/source/application/store/view/setting/store.php new file mode 100644 index 0000000..a32ceff --- /dev/null +++ b/source/application/store/view/setting/store.php @@ -0,0 +1,81 @@ + +
+
+
+
+
+
+
+
+
商城设置
+
+
+ +
+ +
+
+
+ +
+ + + +
+ 注:配送方式至少选择一个 +
+
+
+
+
物流查询API
+
+
+ +
+ + 用于查询物流信息,快递100申请 +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/tplMsg.php b/source/application/store/view/setting/tplMsg.php new file mode 100644 index 0000000..f9b6b5d --- /dev/null +++ b/source/application/store/view/setting/tplMsg.php @@ -0,0 +1,145 @@ +
+
+
+
+
+
+
+
+
+

+ 模板消息仅用于微信小程序向用户发送服务通知,因微信限制,每笔支付订单可允许向用户在7天内推送最多3条模板消息。 + 如何获取模板消息ID? +

+
+
+
+
支付成功通知
+
+
+ +
+ + +
+
+
+ +
+ +
+ 模板编号AT0009,关键词 (订单编号、支付时间、订单金额、商品名称) +
+
+
+ +
+
订单发货通知
+
+
+ +
+ + +
+
+
+ +
+ + 模板编号AT0007,关键词 (订单编号、商品信息、收货人、收货地址、物流公司、物流单号) +
+
+ +
+
售后状态通知
+
+
+ +
+ + +
+
+
+ +
+ + 模板编号AT0553,关键词 (售后类型、状态、订单号、商品名称、申请时间、申请原因) +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/setting/trade.php b/source/application/store/view/setting/trade.php new file mode 100644 index 0000000..d86281d --- /dev/null +++ b/source/application/store/view/setting/trade.php @@ -0,0 +1,112 @@ +
+
+
+
+
+
+
+
+
订单流程设置
+
+
+ +
+
+ +
+ +
+ 订单下单未付款,n天后自动关闭,设置0天不自动关闭 +
+
+
+
+ +
+
+ +
+ +
+ 如果在期间未确认收货,系统自动完成收货,设置0天不自动收货 +
+
+
+
+ +
+
+ +
+ +
+ 订单完成后 ,用户在n天内可以发起售后申请,设置0天不允许申请售后 +
+
+
+ +
+
运费设置
+
+
+ +
+
+ +
+ 订单中的商品有多个运费模板时,将每个商品的运费之和订为订单总运费 +
+
+
+ +
+ 订单中的商品有多个运费模板时,取订单中运费最少的商品的运费计为订单总运费 +
+
+
+ +
+ 订单中的商品有多个运费模板时,取订单中运费最多的商品的运费计为订单总运费 +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/shop/add.php b/source/application/store/view/shop/add.php new file mode 100644 index 0000000..24a7112 --- /dev/null +++ b/source/application/store/view/shop/add.php @@ -0,0 +1,195 @@ +
+
+
+
+
+
+
+
+
添加门店
+
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 例如:8:30-17:30 +
+
+
+ +
+
+ + + +
+
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + +{{include file="layouts/_template/file_library" /}} + + + + diff --git a/source/application/store/view/shop/clerk/add.php b/source/application/store/view/shop/clerk/add.php new file mode 100644 index 0000000..6a14314 --- /dev/null +++ b/source/application/store/view/shop/clerk/add.php @@ -0,0 +1,123 @@ +
+
+
+
+
+
+
+
+
添加店员
+
+
+ +
+
+ +
+
+
+ 选择后不可更改 +
+
+
+
+
+ +
+ +
+ 请选择店员所属的门店,用于核销订单 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + diff --git a/source/application/store/view/shop/clerk/edit.php b/source/application/store/view/shop/clerk/edit.php new file mode 100644 index 0000000..2a0f1d3 --- /dev/null +++ b/source/application/store/view/shop/clerk/edit.php @@ -0,0 +1,110 @@ +
+
+
+
+
+
+
+
+
编辑店员
+
+
+ +
+ +
+ 请选择店员所属的门店,用于核销订单 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + + diff --git a/source/application/store/view/shop/clerk/index.php b/source/application/store/view/shop/clerk/index.php new file mode 100644 index 0000000..3a2def1 --- /dev/null +++ b/source/application/store/view/shop/clerk/index.php @@ -0,0 +1,134 @@ +
+
+
+
+
+
店员列表
+
+
+ +
+
+ +
+
+ + + +
+
+
+
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + +
店员ID微信头像微信昵称所属门店店员姓名店员手机号状态添加时间操作
+ + + + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/shop/edit.php b/source/application/store/view/shop/edit.php new file mode 100644 index 0000000..2d68bfd --- /dev/null +++ b/source/application/store/view/shop/edit.php @@ -0,0 +1,215 @@ +
+
+
+
+
+
+
+
+
编辑门店
+
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+ + + + + +
+
+
+
+
+
+
+ +
+ +
+
+
+ +
+ + 例如:8:30-17:30 +
+
+
+ +
+ +
+
+
+ +
+
+ + + +
+
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+ + 数字越小越靠前 +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + + + + +{{include file="layouts/_template/file_library" /}} + + + + diff --git a/source/application/store/view/shop/getpoint.php b/source/application/store/view/shop/getpoint.php new file mode 100644 index 0000000..3d531ab --- /dev/null +++ b/source/application/store/view/shop/getpoint.php @@ -0,0 +1,1179 @@ + + + + + 腾讯地图开放API - 轻快小巧,简单易用! + + + + + + + + + + + +
+
+ +
+
当前坐标:
+ +
当前地址:
+ +
+
+
+
+
+ 北京市[更换城市]当前缩放等级:10 +
+

热门城市X

+
+ 北京 + 深圳 + 上海 + 香港 + 澳门 + 广州 + 天津 + 重庆 + 杭州 + 成都 + 武汉 + 青岛 +
+

全国城市

+
+
直辖市
+
+ 北京 + 上海 + 天津 + 重庆 +
+
+
+
+
内蒙古
+
+ 呼和浩特 + 包头 + 乌海 + 赤峰 + 通辽 + 鄂尔多斯 + 呼伦贝尔 + 巴彦淖尔 + 乌兰察布 + 兴安盟 + 锡林郭勒盟 + 阿拉善盟 +
+
+
+
+
山西
+
+ 太原 + 大同 + 阳泉 + 长治 + 晋城 + 朔州 + 晋中 + 运城 + 忻州 + 临汾 + 吕梁 + +
+
+
+
+
陕西
+
+ 西安 + 铜川 + 宝鸡 + 咸阳 + 渭南 + 延安 + 汉中 + 榆林 + 安康 + 商洛 +
+
+
+
+
河北
+
+ 石家庄 + 唐山 + 秦皇岛 + 邯郸 + 邢台 + 保定 + 张家口 + 承德 + 沧州 + 廊坊 + 衡水 +
+
+
+
+
辽宁
+
+ 沈阳 + 大连 + 鞍山 + 抚顺 + 本溪 + 丹东 + 锦州 + 营口 + 阜新 + 辽阳 + 盘锦 + 铁岭 + 朝阳 + 葫芦岛 +
+
+
+
+
吉林
+
+ 长春 + 吉林市 + 四平 + 辽源 + 通化 + 白山 + 松原 + 白城 + 延边 +
+
+
+
+
黑龙江
+
+ 哈尔滨 + 齐齐哈尔 + 鸡西 + 鹤岗 + 双鸭山 + 大庆 + 伊春 + 牡丹江 + 佳木斯 + 七台河 + 黑河 + 绥化 + 大兴安岭 +
+
+
+
+
江苏
+
+ 南京 + 无锡 + 徐州 + 常州 + 苏州 + 南通 + 连云港 + 淮安 + 盐城 + 扬州 + 镇江 + 泰州 + 宿迁 +
+
+
+
+
安徽
+
+ 合肥 + 蚌埠 + 芜湖 + 淮南 + 马鞍山 + 淮北 + 铜陵 + 安庆 + 黄山 + 阜阳 + 宿州 + 滁州 + 六安 + 宣城 + 池州 + 亳州 +
+
+
+
+
山东
+
+ 济南 + 青岛 + 淄博 + 枣庄 + 东营 + 潍坊 + 烟台 + 威海 + 济宁 + 泰安 + 日照 + 临沂 + 德州 + 聊城 + 滨州 + 菏泽 +
+
+
+
+
浙江
+
+ 杭州 + 宁波 + 温州 + 嘉兴 + 绍兴 + 金华 + 衢州 + 舟山 + 台州 + 丽水 + 湖州 +
+
+
+
+
江西
+
+ 南昌 + 景德镇 + 萍乡 + 九江 + 新余 + 鹰潭 + 赣州 + 吉安 + 宜春 + 抚州 + 上饶 +
+
+
+
+
福建
+
+ 福州 + 厦门 + 莆田 + 三明 + 泉州 + 漳州 + 南平 + 龙岩 + 宁德 +
+
+
+
+
湖南
+
+ 长沙 + 株洲 + 湘潭 + 衡阳 + 邵阳 + 岳阳 + 常德 + 张家界 + 益阳 + 郴州 + 永州 + 怀化 + 娄底 + 湘西 +
+
+
+
+
湖北
+
+ 武汉 + 黄石 + 襄阳 + 十堰 + 宜昌 + 荆门 + 鄂州 + 孝感 + 荆州 + 黄冈 + 咸宁 + 随州 + 恩施 + 潜江 + 仙桃 + 天门 + 神农架 +
+
+
+
+
河南
+
+ 郑州 + 开封 + 洛阳 + 平顶山 + 焦作 + 鹤壁 + 新乡 + 安阳 + 濮阳 + 许昌 + 漯河 + 三门峡 + 南阳 + 商丘 + 信阳 + 周口 + 驻马店 + 济源 +
+
+
+
+
海南
+
+ 海口 + 三亚 + 三沙 + 儋州 + 五指山 + 文昌 + 琼海 + 万宁 + 东方 + 定安 + 屯昌 + 澄迈 + 临高 + 白沙 + 昌江 + 乐东 + 陵水 + 保亭 + 琼中 +
+
+
+
+
广东
+
+ 广州 + 深圳 + 珠海 + 汕头 + 韶关 + 佛山 + 江门 + 湛江 + 茂名 + 肇庆 + 惠州 + 梅州 + 汕尾 + 河源 + 阳江 + 清远 + 东莞 + 中山 + 潮州 + 揭阳 + 云浮 +
+
+
+
+
广西
+
+ 南宁 + 柳州 + 桂林 + 梧州 + 北海 + 防城港 + 钦州 + 贵港 + 玉林 + 百色 + 贺州 + 河池 + 来宾 + 崇左 +
+
+
+
+
贵州
+
+ 贵阳 + 遵义 + 安顺 + 铜仁 + 毕节 + 六盘水 + 黔西南 + 黔东南 + 黔南 +
+
+
+
+
四川
+
+ 成都 + 自贡 + 攀枝花 + 泸州 + 德阳 + 绵阳 + 广元 + 遂宁 + 内江 + 乐山 + 南充 + 宜宾 + 广安 + 达州 + 眉山 + 雅安 + 巴中 + 资阳 + 阿坝 + 甘孜 + 凉山 +
+
+
+
+
云南
+
+ 昆明 + 保山 + 昭通 + 丽江 + 普洱 + 临沧 + 曲靖 + 玉溪 + 文山 + 西双版纳 + 楚雄 + 红河 + 德宏 + 大理 + 怒江 + 迪庆 +
+
+
+
+
甘肃
+
+ 兰州 + 嘉峪关 + 金昌 + 白银 + 天水 + 酒泉 + 张掖 + 武威 + 定西 + 陇南 + 平凉 + 庆阳 + 临夏 + 甘南 +
+
+
+
+
宁夏
+
+ 银川 + 石嘴山 + 吴忠 + 固原 + 中卫 +
+
+
+
+
青海
+
+ 西宁 + 玉树 + 果洛 + 海东 + 海西 + 黄南 + 海北 + 海南 +
+
+
+
+
西藏
+
+ 拉萨 + 那曲 + 昌都 + 山南 + 日喀则 + 阿里 + 林芝 +
+
+
+
+
新疆
+
+ 乌鲁木齐 + 克拉玛依 + 吐鲁番 + 哈密 + 博尔塔拉 + 巴音郭楞 + 克孜勒苏 + 和田 + 阿克苏 + 喀什 + 塔城 + 伊犁 + 昌吉 + 阿勒泰 + 石河子 + 阿拉尔 + 图木舒克 + 五家渠 + 北屯 + 铁门关 + 双河 + 可克达拉 + 昆玉 +
+
+
+
+
+ +
+
+
+

功能简介:

+ +

1、支持地址 精确/模糊 查询;

+ +

2、支持POI点坐标显示;

+ +

3、坐标鼠标跟随显示;

+ +

使用说明:

+ +

在搜索框搜索关键词后,地图上会显示相应poi点,同时左侧显示对应该点的信息,点击某点或某信息,右上角会显示相应该点的坐标和地址。

+
+ +
+
+
+
+
+
+ + + + diff --git a/source/application/store/view/shop/index.php b/source/application/store/view/shop/index.php new file mode 100644 index 0000000..027cb68 --- /dev/null +++ b/source/application/store/view/shop/index.php @@ -0,0 +1,113 @@ +
+
+
+
+
+
门店列表
+
+
+ +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + +
门店ID门店名称门店logo营业时间联系人联系电话门店地址自提核销门店状态创建时间操作
+ + + + + + + + + + + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/shop/order/index.php b/source/application/store/view/shop/order/index.php new file mode 100644 index 0000000..2e9c0fc --- /dev/null +++ b/source/application/store/view/shop/order/index.php @@ -0,0 +1,102 @@ + +
+
+
+
+
+
核销记录列表
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + +
ID核销门店核销员订单号订单类型核销时间
+ + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/statistics/data/index.php b/source/application/store/view/statistics/data/index.php new file mode 100644 index 0000000..0b2a69b --- /dev/null +++ b/source/application/store/view/statistics/data/index.php @@ -0,0 +1,314 @@ + +
+ +
+
+
+
+
数据概况
+
+ +
+ + +
+ +
+
+
+ 7天 +
+
+ 30天 +
+
+ 清空 +
+
+
+
+
+
+
+
+
+
+ +
+
+
用户数量
+
{{ survey.values.user_total }}
+
+
+
+
+
+
+ +
+
+
付款订单数
+
{{ survey.values.order_total }}
+
+
+
+
+
+
+ +
+
+
商品数量
+
{{ survey.values.goods_total }}
+
+
+
+
+
+
+ +
+
+
消费人数
+
{{ survey.values.consume_users }}
+
+
+
+
+
+
+ +
+
+
付款订单总额
+
{{ survey.values.order_total_money }}
+
+
+
+
+
+
+ +
+
+
用户充值总额
+
{{ survey.values.recharge_total }}
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
近七日交易走势
+
+
+
+
+
+
+
+ +
+
+
+
+
商品销售榜
+
+
+ + + + + + + + + + + + + + + + + +
排名商品销量销售额
+
+ +
+ {{ index + 1 }} +
+

{{ item.goods_name }}

+
{{ item.total_sales_num }}{{ item.sales_volume }}
+
+
+
+
+
+
+
用户消费榜
+
+
+ + + + + + + + + + + + + + + +
排名用户昵称实际消费金额
+
+ +
+ {{ index + 1 }} +
+

{{ item.nickName }}

+
{{ item.expend_money }}
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/source/application/store/view/store/role/add.php b/source/application/store/view/store/role/add.php new file mode 100644 index 0000000..bfed640 --- /dev/null +++ b/source/application/store/view/store/role/add.php @@ -0,0 +1,105 @@ + +
+
+
+
+
+
+
+
+
添加角色
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/store/role/edit.php b/source/application/store/view/store/role/edit.php new file mode 100644 index 0000000..d400ae9 --- /dev/null +++ b/source/application/store/view/store/role/edit.php @@ -0,0 +1,108 @@ + +
+
+
+
+
+
+
+
+
编辑角色
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/store/role/index.php b/source/application/store/view/store/role/index.php new file mode 100644 index 0000000..319f147 --- /dev/null +++ b/source/application/store/view/store/role/index.php @@ -0,0 +1,80 @@ +
+
+
+
+
+
角色列表
+
+
+ +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
角色ID角色名称排序添加时间操作
+ +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/store/user/add.php b/source/application/store/view/store/user/add.php new file mode 100644 index 0000000..afeb859 --- /dev/null +++ b/source/application/store/view/store/user/add.php @@ -0,0 +1,75 @@ +
+
+
+
+
+
+
+
+
添加管理员
+
+
+ +
+ +
+
+
+ +
+ +
+ 注:支持多选 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/store/user/edit.php b/source/application/store/view/store/user/edit.php new file mode 100644 index 0000000..84fed05 --- /dev/null +++ b/source/application/store/view/store/user/edit.php @@ -0,0 +1,77 @@ +
+
+
+
+
+
+
+
+
编辑管理员
+
+
+ +
+ +
+
+
+ +
+ +
+ 注:支持多选 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/store/user/index.php b/source/application/store/view/store/user/index.php new file mode 100644 index 0000000..296ac10 --- /dev/null +++ b/source/application/store/view/store/user/index.php @@ -0,0 +1,89 @@ +
+
+
+
+
+
管理员列表
+
+
+ +
+
+ + + +
+
+
+ + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + +
管理员ID用户名姓名添加时间操作
+
+ + + + 编辑 + + + + + 删除 + + + +
+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/store/user/renew.php b/source/application/store/view/store/user/renew.php new file mode 100644 index 0000000..30f51cf --- /dev/null +++ b/source/application/store/view/store/user/renew.php @@ -0,0 +1,55 @@ +
+
+
+
+
+
+
+
+
管理员设置
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/user/balance/log.php b/source/application/store/view/user/balance/log.php new file mode 100644 index 0000000..8b9b486 --- /dev/null +++ b/source/application/store/view/user/balance/log.php @@ -0,0 +1,116 @@ +
+
+
+
+
+
余额明细
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + +
ID微信头像微信昵称余额变动场景变动金额描述/说明管理员备注创建时间
+ + + + +

+ +
+ + + 0 ? '+' : '' ?> +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/user/grade/add.php b/source/application/store/view/user/grade/add.php new file mode 100644 index 0000000..7688c50 --- /dev/null +++ b/source/application/store/view/user/grade/add.php @@ -0,0 +1,100 @@ +
+
+
+
+
+
+
+
+
添加会员等级
+
+
+ +
+ + 例如:大众会员、黄金会员、铂金会员、钻石会员 +
+
+
+ +
+
+ +
+
+ 会员等级的权重,数字越大 等级越高 +
+
+
+
+ +
+
+ 实际消费金额满 + + +
+
+ 用户的实际消费金额满n元后,自动升级 +
+
+
+
+ +
+
+ 折扣率 + + +
+
+ 折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/user/grade/edit.php b/source/application/store/view/user/grade/edit.php new file mode 100644 index 0000000..9887665 --- /dev/null +++ b/source/application/store/view/user/grade/edit.php @@ -0,0 +1,104 @@ +
+
+
+
+
+
+
+
+
编辑会员等级
+
+
+ +
+ + 例如:大众会员、黄金会员、铂金会员、钻石会员 +
+
+
+ +
+
+ +
+
+ 会员等级的权重,数字越大 等级越高 +
+
+
+
+ +
+
+ 实际消费金额满 + + +
+
+ 用户的实际消费金额满n元后,自动升级 +
+
+
+
+ +
+
+ 折扣率 + + +
+
+ 折扣率范围0-10,9.5代表9.5折,0代表不折扣 +
+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/user/grade/index.php b/source/application/store/view/user/grade/index.php new file mode 100644 index 0000000..44b7b10 --- /dev/null +++ b/source/application/store/view/user/grade/index.php @@ -0,0 +1,100 @@ +
+
+
+
+
+
会员等级列表
+
+
+ +
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + +
等级ID等级名称等级权重升级条件等级权益状态创建时间操作
+ 消费满 + + + + + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/user/index.php b/source/application/store/view/user/index.php new file mode 100644 index 0000000..7319937 --- /dev/null +++ b/source/application/store/view/user/index.php @@ -0,0 +1,412 @@ +
+
+
+
+
+
用户列表
+
+
+ +
+
+ +
+
+
+ get('grade'); ?> + +
+
+ get('gender'); ?> + +
+
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + + + +
用户ID微信头像微信昵称用户余额可用积分会员等级实际消费金额性别国家省份城市注册时间操作
+ + + + + + +
+ + + + 充值 + + + + + + 会员等级 + + + + + 删除 + + +
+ + +
+
+
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + + + + + + + + diff --git a/source/application/store/view/user/recharge/order.php b/source/application/store/view/user/recharge/order.php new file mode 100644 index 0000000..08cad4c --- /dev/null +++ b/source/application/store/view/user/recharge/order.php @@ -0,0 +1,146 @@ +
+
+
+
+
+
余额充值记录
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + + + + + + + +
订单ID微信头像微信昵称订单号充值方式套餐名称支付金额赠送金额支付状态付款时间创建时间
+ + + + +

+ +
+ + + + + +
暂无记录
+
+
+
render() ?>
+
+
总记录:total() ?>
+
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/wxapp/custom/index.php b/source/application/store/view/wxapp/custom/index.php new file mode 100644 index 0000000..5817c5f --- /dev/null +++ b/source/application/store/view/wxapp/custom/index.php @@ -0,0 +1,78 @@ +
+
+
+
+
+
帮助中心
+
+
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + +
标题内容排序添加时间操作
+

+
+

+
+ +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/wxapp/help/add.php b/source/application/store/view/wxapp/help/add.php new file mode 100644 index 0000000..e01f967 --- /dev/null +++ b/source/application/store/view/wxapp/help/add.php @@ -0,0 +1,55 @@ +
+
+
+
+
+
+
+
+
添加帮助
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/wxapp/help/edit.php b/source/application/store/view/wxapp/help/edit.php new file mode 100644 index 0000000..eb5c4cf --- /dev/null +++ b/source/application/store/view/wxapp/help/edit.php @@ -0,0 +1,56 @@ +
+
+
+
+
+
+
+
+
编辑帮助
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/wxapp/help/index.php b/source/application/store/view/wxapp/help/index.php new file mode 100644 index 0000000..1b3ea75 --- /dev/null +++ b/source/application/store/view/wxapp/help/index.php @@ -0,0 +1,83 @@ +
+
+
+
+
+
帮助中心
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + isEmpty()): foreach ($list as $item): ?> + + + + + + + + + + + + + +
标题内容排序添加时间操作
+

+
+ +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/wxapp/page/category.php b/source/application/store/view/wxapp/page/category.php new file mode 100644 index 0000000..bcb302a --- /dev/null +++ b/source/application/store/view/wxapp/page/category.php @@ -0,0 +1,102 @@ + +
+
+
+
+
+
+
+
+
分类页模板
+
+
+
+ +
+
+
+ +
+ + + +
+ 分类图尺寸:宽750像素 高度不限 + + 分类图尺寸:宽188像素 高度不限 + + 分类图尺寸:宽150像素 高150像素 + +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ diff --git a/source/application/store/view/wxapp/page/edit.php b/source/application/store/view/wxapp/page/edit.php new file mode 100644 index 0000000..b10d7ed --- /dev/null +++ b/source/application/store/view/wxapp/page/edit.php @@ -0,0 +1,2247 @@ + + +
+
+
+ +
+ +
+ + +
+ +
+
+ + +
+ +
+

{{ diyData.page.params.title }}

+
+ +
+ + + +
+
+ + +
+ + +
+
{{ diyData.page.name }}
+
+
+ +
+ +
+ 页面名称仅用于后台查找 +
+
+
+
+ +
+ +
+ 小程序端顶部显示的标题 +
+
+
+
+ +
+ +
+ 小程序端转发时显示的标题 +
+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ + +
+
+ +
+
+

1. 设计完成后点击"保存页面",在小程序端页面下拉刷新即可看到效果。

+

2. 如需填写链接地址请参考页面链接

+
+
+
+
+
+ + +{{include file="layouts/_template/file_library" /}} + + + + + + + + + \ No newline at end of file diff --git a/source/application/store/view/wxapp/page/index.php b/source/application/store/view/wxapp/page/index.php new file mode 100644 index 0000000..ad4b48e --- /dev/null +++ b/source/application/store/view/wxapp/page/index.php @@ -0,0 +1,113 @@ +
+
+
+
+
+
页面设计
+
+
+
+
+
+ + + +
+
+
+
+ + + + + + + + + + + + + isEmpty()): foreach ($list + + as $item): ?> + + + + + + + + + + + + + + +
页面ID页面名称页面类型添加时间更新时间操作
+ + + + 商城首页 + + 自定义页 + + + +
暂无记录
+
+
+
+
+
+
+ + diff --git a/source/application/store/view/wxapp/page/links.php b/source/application/store/view/wxapp/page/links.php new file mode 100644 index 0000000..260c2e5 --- /dev/null +++ b/source/application/store/view/wxapp/page/links.php @@ -0,0 +1,439 @@ +
+
+
+
+
+
+
页面链接
+
+ +
+
+
+
+
+ diff --git a/source/application/store/view/wxapp/setting.php b/source/application/store/view/wxapp/setting.php new file mode 100644 index 0000000..7426e4a --- /dev/null +++ b/source/application/store/view/wxapp/setting.php @@ -0,0 +1,93 @@ +
+
+
+
+
+
+
+
+
小程序设置
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
微信支付设置
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + 使用文本编辑器打开apiclient_cert.pem文件,将文件的全部内容复制进来 +
+
+
+ +
+ + 使用文本编辑器打开apiclient_key.pem文件,将文件的全部内容复制进来 +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ diff --git a/source/application/tags.php b/source/application/tags.php new file mode 100644 index 0000000..8594ab7 --- /dev/null +++ b/source/application/tags.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- + +// 应用行为扩展定义文件 +return [ + // 应用初始化 + 'app_init' => [], + // 应用开始 + 'app_begin' => [ + 'app\\common\\behavior\\App' + ], + // 模块初始化 + 'module_init' => [], + // 操作开始执行 + 'action_begin' => [], + // 视图内容过滤 + 'view_filter' => [], + // 日志写入 + 'log_write' => [], + // 应用结束 + 'app_end' => [], + + // 订单行为管理 + 'order' => [ + // 普通订单 + 'app\\task\\behavior\\Order', + // 秒杀订单 + 'app\\task\\behavior\\sharp\\Order', + ], + + // 优惠券行为管理 + 'UserCoupon' => [ + 'app\\task\\behavior\\UserCoupon' + ], + + // 分销商订单行为管理 + 'DealerOrder' => [ + 'app\\task\\behavior\\DealerOrder' + ], + + // 拼团订单行为管理 + 'sharing_order' => [ + 'app\\task\\behavior\\sharing\\Order' + ], + + // 拼团拼单行为管理 + 'sharing_active' => [ + 'app\\task\\behavior\\sharing\\Active' + ], + + // 会员等级行为管理 + 'user_grade' => [ + 'app\\task\\behavior\\user\\Grade' + ], + + // 砍价任务行为管理 + 'bargain_task' => [ + 'app\\task\\behavior\\bargain\\Task' + ], + +]; diff --git a/source/application/task/behavior/DealerOrder.php b/source/application/task/behavior/DealerOrder.php new file mode 100644 index 0000000..c56db78 --- /dev/null +++ b/source/application/task/behavior/DealerOrder.php @@ -0,0 +1,96 @@ +model = $model; + if (!Cache::has('__task_space__DealerOrder')) { + $this->model->startTrans(); + try { + // 发放分销订单佣金 + $this->grantMoney(); + $this->model->commit(); + } catch (\Exception $e) { + $this->model->rollback(); + } + Cache::set('__task_space__DealerOrder', time(), 3600); + } + return true; + } + + /** + * 发放分销订单佣金 + * @return bool + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function grantMoney() + { + // 获取未结算佣金的订单列表 + $list = $this->model->getUnSettledList(); + if ($list->isEmpty()) return false; + // 整理id集 + $invalidIds = []; + $grantIds = []; + // 发放分销订单佣金 + foreach ($list->toArray() as $item) { + // 已失效的订单 + if ($item['order_master']['order_status']['value'] == 20) { + $invalidIds[] = $item['id']; + } + // 已完成的订单 + if ($item['order_master']['order_status']['value'] == 30) { + $grantIds[] = $item['id']; + DealerOrderModel::grantMoney($item['order_master'], $item['order_type']['value']); + } + } + // 标记已失效的订单 + $this->model->setInvalid($invalidIds); + // 记录日志 + $this->dologs('invalidIds', ['Ids' => $invalidIds]); + $this->dologs('grantMoney', ['Ids' => $grantIds]); + return true; + } + + /** + * 记录日志 + * @param $method + * @param array $params + * @return bool|int + */ + private function dologs($method, $params = []) + { + $value = 'behavior DealerOrder --' . $method; + foreach ($params as $key => $val) { + $value .= ' --' . $key . ' ' . (is_array($val) ? json_encode($val) : $val); + } + return log_write($value); + } + +} \ No newline at end of file diff --git a/source/application/task/behavior/Order.php b/source/application/task/behavior/Order.php new file mode 100644 index 0000000..46aacac --- /dev/null +++ b/source/application/task/behavior/Order.php @@ -0,0 +1,212 @@ +model = $model; + $this->wxappId = $model::$wxapp_id; + // 普通订单行为管理 + $this->master(); + return true; + } + + /** + * 普通订单行为管理 + * @return bool + */ + private function master() + { + $key = "__task_space__order__{$this->wxappId}"; + if (Cache::has($key)) return true; + // 获取商城交易设置 + $this->service = new OrderService; + $config = Setting::getItem('trade'); + $this->model->transaction(function () use ($config) { + // 未支付订单自动关闭 + $this->close($config['order']['close_days']); + // 已发货订单自动确认收货 + $this->receive($config['order']['receive_days']); + // 已完成订单结算 + $this->settled($config['order']['refund_days']); + }); + Cache::set($key, time(), 3600); + return true; + } + + /** + * 未支付订单自动关闭 + * @param $closeDays + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + private function close($closeDays) + { + // 取消n天以前的的未付款订单 + if ($closeDays < 1) return false; + // 截止时间 + $deadlineTime = time() - ((int)$closeDays * 86400); + // 执行自动关闭 + $this->service->close($deadlineTime); + // 记录日志 + $this->dologs('close', [ + 'close_days' => (int)$closeDays, + 'deadline_time' => $deadlineTime, + 'orderIds' => json_encode($this->service->getCloseOrderIds()), + ]); + return true; + } + + /** + * 已发货订单自动确认收货 + * @param $receiveDays + * @return bool|false|int + * @throws \think\Exception + * @throws \think\exception\DbException + */ + private function receive($receiveDays) + { + // 截止时间 + if ($receiveDays < 1) return false; + $deadlineTime = time() - ((int)$receiveDays * 86400); + // 条件 + $filter = [ + 'pay_status' => 20, + 'delivery_status' => 20, + 'receipt_status' => 10, + 'delivery_time' => ['<=', $deadlineTime] + ]; + // 订单id集 + $orderIds = $this->model->where($filter)->column('order_id'); + if (!empty($orderIds)) { + // 更新订单收货状态 + $this->model->onBatchUpdate($orderIds, [ + 'receipt_status' => 20, + 'receipt_time' => time(), + 'order_status' => 30 + ]); + // 批量处理已完成的订单 + $this->onReceiveCompleted($orderIds); + } + // 记录日志 + $this->dologs('receive', [ + 'receive_days' => (int)$receiveDays, + 'deadline_time' => $deadlineTime, + 'orderIds' => json_encode($orderIds), + ]); + return true; + } + + /** + * 已完成订单结算 + * @param $refundDays + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + private function settled($refundDays) + { + // 获取已完成的订单(未累积用户实际消费金额) + // 条件1:订单状态:已完成 + // 条件2:超出售后期限 + // 条件3:is_settled 为 0 + // 截止时间 + $deadlineTime = time() - ((int)$refundDays * 86400); + // 查询条件 + $filter = [ + 'order_status' => 30, + 'receipt_time' => ['<=', $deadlineTime], // 此处使用<=,用于兼容自动确认收货后 + 'is_settled' => 0 + ]; + // 查询订单列表 + $orderList = $this->model->getList($filter, [ + 'goods' => ['refund'], // 用于计算售后退款金额 + ]); + // 订单id集 + $orderIds = helper::getArrayColumn($orderList, 'order_id'); + // 订单结算服务 + $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::MASTER); + !empty($orderIds) && $OrderCompleteService->settled($orderList); + // 记录日志 + $this->dologs('settled', [ + 'refund_days' => (int)$refundDays, + 'deadline_time' => $deadlineTime, + 'orderIds' => json_encode($orderIds), + ]); + } + + /** + * 批量处理已完成的订单 + * @param $orderIds + * @return bool + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function onReceiveCompleted($orderIds) + { + // 获取已完成的订单列表 + $list = $this->model->getList(['order_id' => ['in', $orderIds]], [ + 'goods' => ['refund'], // 用于发放分销佣金 + 'user', 'address', 'goods', 'express', // 用于同步微信好物圈 + ]); + if ($list->isEmpty()) return false; + // 执行订单完成后的操作 + $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::MASTER); + $OrderCompleteService->complete($list, $this->wxappId); + return true; + } + + /** + * 记录日志 + * @param $method + * @param array $params + * @return bool|int + */ + private function dologs($method, $params = []) + { + $value = 'behavior Order --' . $method; + foreach ($params as $key => $val) + $value .= ' --' . $key . ' ' . $val; + return log_write($value); + } + +} diff --git a/source/application/task/behavior/UserCoupon.php b/source/application/task/behavior/UserCoupon.php new file mode 100644 index 0000000..9037bc5 --- /dev/null +++ b/source/application/task/behavior/UserCoupon.php @@ -0,0 +1,67 @@ +model = $model; + if (!Cache::has('__task_space__UserCoupon')) { + // 设置优惠券过期状态 + $this->setExpired(); + Cache::set('__task_space__UserCoupon', time(), 3600); + } + return true; + } + + /** + * 设置优惠券过期状态 + * @return false|int + */ + private function setExpired() + { + // 获取已过期的优惠券ID集 + $couponIds = $this->model->getExpiredCouponIds(); + // 记录日志 + $this->dologs('setExpired', [ + 'couponIds' => json_encode($couponIds), + ]); + // 更新已过期状态 + return $this->model->setIsExpire($couponIds); + } + + /** + * 记录日志 + * @param $method + * @param array $params + * @return bool|int + */ + private function dologs($method, $params = []) + { + $value = 'UserCoupon --' . $method; + foreach ($params as $key => $val) + $value .= ' --' . $key . ' ' . $val; + return log_write($value); + } + +} diff --git a/source/application/task/behavior/bargain/Task.php b/source/application/task/behavior/bargain/Task.php new file mode 100644 index 0000000..914f0e3 --- /dev/null +++ b/source/application/task/behavior/bargain/Task.php @@ -0,0 +1,79 @@ +model = $model; + if (!$model::$wxapp_id) { + return false; + } + if (!Cache::has('__task_space__bargain_task__')) { + // 将已过期的砍价任务标记为已结束 + $this->onSetIsEnd(); + Cache::set('__task_space__bargain_task__', time(), 10); + } + return true; + } + + /** + * 将已过期的砍价任务标记为已结束 + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function onSetIsEnd() + { + // 获取已过期但未结束的砍价任务 + $list = $this->model->getEndList(); + $taskIds = helper::getArrayColumn($list, 'task_id'); + // 将砍价任务标记为已结束(批量) + !empty($taskIds) && $this->model->setIsEnd($taskIds); + // 记录日志 + $this->dologs('close', [ + 'orderIds' => json_encode($taskIds), + ]); + return true; + } + + /** + * 记录日志 + * @param $method + * @param array $params + * @return bool|int + */ + private function dologs($method, $params = []) + { + $value = 'behavior bargain Task --' . $method; + foreach ($params as $key => $val) + $value .= ' --' . $key . ' ' . $val; + return log_write($value); + } + +} \ No newline at end of file diff --git a/source/application/task/behavior/sharing/Active.php b/source/application/task/behavior/sharing/Active.php new file mode 100644 index 0000000..d1bca21 --- /dev/null +++ b/source/application/task/behavior/sharing/Active.php @@ -0,0 +1,136 @@ +model = $model; + if (!$model::$wxapp_id) { + return false; + } + if (!Cache::has('__task_space__sharing_active__' . $model::$wxapp_id)) { + try { + // 拼团设置 + $config = Setting::getItem('basic'); + // 已过期的拼单更新状态(拼单失败) + $this->onUpdateActiveEnd(); + // 更新拼团失败的订单并退款 + if ($config['auto_refund'] == true) { + $this->onOrderRefund(); + } + } catch (\Exception $e) { + } + Cache::set('__task_space__sharing_active__' . $model::$wxapp_id, time(), 10); + } + return true; + } + + /** + * 已过期的拼单更新状态 + * @return false|int + * @throws \app\common\exception\BaseException + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function onUpdateActiveEnd() + { + // 获取已过期的拼单列表 + $list = $this->model->getEndedList(); + // 拼单ID集 + $activeIds = []; + foreach ($list as $item) { + $activeIds[] = $item['active_id']; + } + // 记录日志 + $this->dologs('onSetActiveEnd', [ + 'activeIds' => json_encode($activeIds), + ]); + // 发送拼团失败模板消息 + $Message = new Message; + foreach ($list as $item) { + $Message->sharingActive($item, '拼团失败'); + } + // 更新已过期状态 + return $this->model->updateEndedStatus($activeIds); + } + + /** + * 更新拼团失败的订单并退款 + * @return bool + * @throws \app\common\exception\BaseException + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function onOrderRefund() + { + // 实例化拼单订单模型 + $model = new OrderModel; + // 每次最多处理的个数,防止运行太久 + // 及微信申请退款API请求频率限制:150qps + $maxLimit = 100; + // 获取拼团失败的订单集 + $orderList = $model->getFailedOrderList($maxLimit); + // 整理拼团订单id + $orderIds = []; + foreach ($orderList as $order) { + $orderIds[] = $order['order_id']; + } + // 记录日志 + $this->dologs('onOrderRefund', [ + 'orderIds' => json_encode($orderIds), + ]); + if (empty($orderIds)) { + return false; + } + // 更新拼团失败的订单并退款 + if ($model->updateFailedStatus($orderList)) { + return true; + } + // 存在退款出错的订单记录日志 + $this->dologs('onOrderRefund', [ + 'error: ' => $model->getError() + ]); + return false; + } + + /** + * 记录日志 + * @param $method + * @param array $params + * @return bool|int + */ + private function dologs($method, $params = []) + { + $value = 'behavior sharing Active --' . $method; + foreach ($params as $key => $val) + $value .= ' --' . $key . ' ' . $val; + return log_write($value); + } + +} diff --git a/source/application/task/behavior/sharing/Order.php b/source/application/task/behavior/sharing/Order.php new file mode 100644 index 0000000..585996a --- /dev/null +++ b/source/application/task/behavior/sharing/Order.php @@ -0,0 +1,223 @@ +model = $model; + $this->wxappId = $model::$wxapp_id; + if (!Cache::has("__task_space__sharing_order__{$this->wxappId}")) { + // 获取商城交易设置 + $config = SettingModel::getItem('trade'); + $this->model->transaction(function () use ($config) { + // 未支付订单自动关闭 + $this->close($config['order']['close_days']); + // 已发货订单自动确认收货 + $this->receive($config['order']['receive_days']); + // 已完成订单结算 + $this->settled($config['order']['refund_days']); + }); + Cache::set("__task_space__sharing_order__{$this->wxappId}", time(), 3600); + } + return true; + } + + /** + * 未支付订单自动关闭 + * @param $closeDays + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + private function close($closeDays) + { + // 取消n天以前的的未付款订单 + if ($closeDays < 1) { + return false; + } + // 截止时间 + $deadlineTime = time() - ((int)$closeDays * 86400); + // 条件 + $filter = [ + 'pay_status' => 10, + 'order_status' => 10, + 'create_time' => ['<=', $deadlineTime] + ]; + // 查询截止时间未支付的订单 + $list = $this->model->getList($filter, ['goods', 'user']); + $orderIds = helper::getArrayColumn($list, 'order_id'); + // 取消订单事件 + if (!empty($orderIds)) { + $OrderGoodsModel = new OrderGoodsModel; + foreach ($list as &$order) { + // 回退商品库存 + $OrderGoodsModel->backGoodsStock($order['goods'], false); + // 回退用户优惠券 + $order['coupon_id'] > 0 && UserCouponModel::setIsUse($order['coupon_id'], false); + // 回退用户积分 + $describe = "订单取消:{$order['order_no']}"; + $order['points_num'] > 0 && $order->user->setIncPoints($order['points_num'], $describe); + } + // 批量更新订单状态为已取消 + $this->model->onBatchUpdate($orderIds, ['order_status' => 20]); + } + // 记录日志 + $this->dologs('close', [ + 'close_days' => (int)$closeDays, + 'deadline_time' => $deadlineTime, + 'orderIds' => json_encode($orderIds), + ]); + return true; + } + + /** + * 已发货订单自动确认收货 + * @param $receiveDays + * @return bool|false|int + * @throws \think\Exception + * @throws \think\exception\DbException + */ + private function receive($receiveDays) + { + if ($receiveDays < 1) { + return false; + } + // 截止时间 + $deadlineTime = time() - ((int)$receiveDays * 86400); + // 条件 + $filter = [ + 'pay_status' => 20, + 'delivery_status' => 20, + 'receipt_status' => 10, + 'delivery_time' => ['<=', $deadlineTime] + ]; + // 订单id集 + $orderIds = $this->model->where($filter)->column('order_id'); + if (!empty($orderIds)) { + // 更新订单收货状态 + $this->model->onBatchUpdate($orderIds, [ + 'receipt_status' => 20, + 'receipt_time' => time(), + 'order_status' => 30 + ]); + // 批量处理已完成的订单 + $this->onReceiveCompleted($orderIds); + } + // 记录日志 + $this->dologs('receive', [ + 'receive_days' => (int)$receiveDays, + 'deadline_time' => $deadlineTime, + 'orderIds' => json_encode($orderIds), + ]); + return true; + } + + /** + * 批量处理已完成的订单 + * @param $orderIds + * @return bool + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function onReceiveCompleted($orderIds) + { + // 获取已完成的订单列表 + $list = $this->model->getList(['order_id' => ['in', $orderIds]], [ + 'goods' => ['refund'], // 用于发放分销佣金 + ]); + if ($list->isEmpty()) return false; + // 执行订单完成后的操作 + $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::SHARING); + $OrderCompleteService->complete($list, $this->wxappId); + return true; + } + + /** + * 已完成订单结算 + * @param $refundDays + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + private function settled($refundDays) + { + // 获取已完成的订单(未累积用户实际消费金额) + // 条件1:订单状态:已完成 + // 条件2:超出售后期限 + // 条件3:is_settled 为 0 + // 截止时间 + $deadlineTime = time() - ((int)$refundDays * 86400); + // 查询条件 + $filter = [ + 'order_status' => 30, + 'receipt_time' => ['<=', $deadlineTime], // 此处使用<=,用于兼容自动确认收货后 + 'is_settled' => 0 + ]; + // 查询订单列表 + $orderList = $this->model->getList($filter, [ + 'goods' => ['refund'], // 用于计算售后退款金额 + ]); + // 订单id集 + $orderIds = helper::getArrayColumn($orderList, 'order_id'); + // 订单结算服务 + $OrderCompleteService = new OrderCompleteService(OrderTypeEnum::SHARING); + !empty($orderIds) && $OrderCompleteService->settled($orderList); + // 记录日志 + $this->dologs('settled', [ + 'refund_days' => (int)$refundDays, + 'deadline_time' => $deadlineTime, + 'orderIds' => json_encode($orderIds), + ]); + } + + /** + * 记录日志 + * @param $method + * @param array $params + * @return bool|int + */ + private function dologs($method, $params = []) + { + $value = 'behavior sharing Order --' . $method; + foreach ($params as $key => $val) + $value .= ' --' . $key . ' ' . $val; + return log_write($value); + } + +} diff --git a/source/application/task/behavior/sharp/Order.php b/source/application/task/behavior/sharp/Order.php new file mode 100644 index 0000000..05add38 --- /dev/null +++ b/source/application/task/behavior/sharp/Order.php @@ -0,0 +1,100 @@ +model = $model; + $this->wxappId = $model::$wxapp_id; + // 秒杀订单行为管理 + $this->sharp(); + return true; + } + + /** + * 秒杀订单行为管理 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function sharp() + { + // 未支付订单自动关闭 + $this->close(); + } + + /** + * 未支付订单自动关闭 + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + private function close() + { + $key = "__task_space__sharp_order__{$this->wxappId}"; + if (Cache::has($key)) return true; + // 取消n分钟以前的的未付款订单 + $minute = SettingModel::getItem('basic')['order']['order_close']; + if ($minute < 1) return false; + // 截止时间 + $deadlineTime = time() - ((int)$minute * 60); + // 执行自动关闭 + $service = new OrderService; + $service->close($deadlineTime, ['order_source' => OrderSourceEnum::SHARP]); + // 记录日志 + $this->dologs('close', [ + 'close_minute' => (int)$minute, + 'deadline_time' => $deadlineTime, + 'orderIds' => json_encode($service->getCloseOrderIds()), + ]); + return true; + } + + /** + * 记录日志 + * @param $method + * @param array $params + * @return bool|int + */ + private function dologs($method, $params = []) + { + $value = 'behavior sharp Order --' . $method; + foreach ($params as $key => $val) + $value .= ' --' . $key . ' ' . $val; + return log_write($value); + } + +} diff --git a/source/application/task/behavior/user/Grade.php b/source/application/task/behavior/user/Grade.php new file mode 100644 index 0000000..0cd124b --- /dev/null +++ b/source/application/task/behavior/user/Grade.php @@ -0,0 +1,70 @@ +model = $model; + if (!$model::$wxapp_id) { + return false; + } + $cacheKey = "__task_space__[user/Grade]__{$model::$wxapp_id}"; + if (!Cache::has($cacheKey)) { + // 设置用户的会员等级 + $this->setUserGrade(); + Cache::set($cacheKey, time(), 60 * 10); + } + return true; + } + + /** + * 设置用户的会员等级 + * @return array|bool|false + * @throws \Exception + */ + private function setUserGrade() + { + // 用户模型 + $UserModel = new UserModel; + // 获取所有等级 + $list = GradeModel::getUsableList(null, ['weight' => 'desc']); + if ($list->isEmpty()) { + return false; + } + // 遍历等级,根据升级条件 查询满足消费金额的用户列表,并且他的等级小于该等级 + $data = []; + foreach ($list as $grade) { + $userList = $UserModel->getUpgradeUserList($grade, array_keys($data)); + foreach ($userList as $user) { + if (!isset($data[$user['user_id']])) { + $data[$user['user_id']] = [ + 'user_id' => $user['user_id'], + 'old_grade_id' => $user['grade_id'], + 'new_grade_id' => $grade['grade_id'], + ]; + } + } + } + // 批量修改会员的等级 + return $UserModel->setBatchGrade($data); + } + +} \ No newline at end of file diff --git a/source/application/task/common.php b/source/application/task/common.php new file mode 100644 index 0000000..d5e10c0 --- /dev/null +++ b/source/application/task/common.php @@ -0,0 +1,20 @@ + $val) { + $value .= " --{$key} {$val}"; + } + log_write($value); + return true; +} diff --git a/source/application/task/model/Express.php b/source/application/task/model/Express.php new file mode 100644 index 0000000..c9e68cc --- /dev/null +++ b/source/application/task/model/Express.php @@ -0,0 +1,14 @@ +with($with) + ->where($filter) + ->where('is_delete', '=', 0) + ->select(); + } + +} diff --git a/source/application/task/model/OrderAddress.php b/source/application/task/model/OrderAddress.php new file mode 100644 index 0000000..2d50743 --- /dev/null +++ b/source/application/task/model/OrderAddress.php @@ -0,0 +1,15 @@ +where('user.user_id', 'not in', $excludedUserIds); + } + return $this->alias('user') + ->field(['user.user_id', 'user.grade_id']) + ->join('user_grade grade', 'grade.grade_id = user.grade_id', 'LEFT') + ->where(function ($query) use ($upgradeGrade) { + $query->where('user.grade_id', '=', 0); + $query->whereOr('grade.weight', '<', $upgradeGrade['weight']); + }) + ->where('user.expend_money', '>=', $upgradeGrade['upgrade']['expend_money']) + ->where('user.is_delete', '=', 0) + ->select(); + } + + /** + * 批量设置会员等级 + * @param $data + * @return array|false + * @throws \Exception + */ + public function setBatchGrade($data) + { + // 批量更新会员等级的数据 + $userData = []; + // 批量更新会员等级变更记录的数据 + $logData = []; + foreach ($data as $item) { + $userData[] = [ + 'user_id' => $item['user_id'], + 'grade_id' => $item['new_grade_id'], + ]; + $logData[] = [ + 'user_id' => $item['user_id'], + 'old_grade_id' => $item['old_grade_id'], + 'new_grade_id' => $item['new_grade_id'], + 'change_type' => ChangeTypeEnum::AUTO_UPGRADE, + ]; + } + // 批量更新会员等级 + $status = $this->isUpdate()->saveAll($userData); + // 批量更新会员等级变更记录 + (new GradeLogModel)->records($logData); + return $status; + } + +} diff --git a/source/application/task/model/UserCoupon.php b/source/application/task/model/UserCoupon.php new file mode 100644 index 0000000..8d49f42 --- /dev/null +++ b/source/application/task/model/UserCoupon.php @@ -0,0 +1,43 @@ +where('is_expire', '=', 0) + ->where('is_use', '=', 0) + ->where( + "IF ( `expire_type` = 20, + (`end_time` + 86400) < {$time}, + ( `create_time` + (`expire_day` * 86400)) < {$time} )" + )->column('user_coupon_id'); + } + + /** + * 设置优惠券过期状态 + * @param $couponIds + * @return false|int + */ + public function setIsExpire($couponIds) + { + if (empty($couponIds)) { + return false; + } + return $this->save(['is_expire' => 1], ['user_coupon_id' => ['in', $couponIds]]); + } + +} diff --git a/source/application/task/model/Wxapp.php b/source/application/task/model/Wxapp.php new file mode 100644 index 0000000..ab51ada --- /dev/null +++ b/source/application/task/model/Wxapp.php @@ -0,0 +1,15 @@ +where('end_time', '<=', time()) + ->where('status', '=', 1) + ->where('is_delete', '=', 0) + ->select(); + } + + /** + * 将砍价任务标记为已结束(批量) + * @param $taskIds + * @return false|int + */ + public function setIsEnd($taskIds) + { + return $this->isUpdate(true)->save([ + 'status' => 0 + ], ['task_id' => ['in', $taskIds]]); + } + +} \ No newline at end of file diff --git a/source/application/task/model/dealer/Apply.php b/source/application/task/model/dealer/Apply.php new file mode 100644 index 0000000..2499dda --- /dev/null +++ b/source/application/task/model/dealer/Apply.php @@ -0,0 +1,14 @@ +where('is_invalid', '=', 0) + ->where('is_settled', '=', 0) + ->select(); + if ($list->isEmpty()) { + return $list; + } + // 整理订单信息 + $with = ['goods' => ['refund']]; + return OrderService::getOrderList($list, 'order_master', $with); + } + + /** + * 标记订单已失效(批量) + * @param $ids + * @return false|int + */ + public function setInvalid($ids) + { + return $this->isUpdate(true) + ->save(['is_invalid' => 1], ['id' => ['in', $ids]]); + } + +} \ No newline at end of file diff --git a/source/application/task/model/dealer/Referee.php b/source/application/task/model/dealer/Referee.php new file mode 100644 index 0000000..41ab5f9 --- /dev/null +++ b/source/application/task/model/dealer/Referee.php @@ -0,0 +1,15 @@ +with(['goods', 'users' => ['user', 'sharingOrder']]) + ->where('end_time', '<=', time()) + ->where('status', '=', 10) + ->select(); + } + + /** + * 设置拼单失败状态 + * @param $activeIds + * @return false|int + */ + public function updateEndedStatus($activeIds) + { + if (empty($activeIds)) { + return false; + } + return $this->save(['status' => 30], ['active_id' => ['in', $activeIds]]); + } + +} diff --git a/source/application/task/model/sharing/ActiveUsers.php b/source/application/task/model/sharing/ActiveUsers.php new file mode 100644 index 0000000..6cd2ecd --- /dev/null +++ b/source/application/task/model/sharing/ActiveUsers.php @@ -0,0 +1,15 @@ +with($with) + ->where($filter) + ->where('is_delete', '=', 0) + ->select(); + } + + /** + * 获取拼团失败的订单 + * @param int $limit + * @return false|\PDOStatement|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function getFailedOrderList($limit = 100) + { + return $this->alias('order') + ->join('sharing_active active', 'order.active_id = active.active_id', 'INNER') + ->where('order_type', '=', 20) + ->where('pay_status', '=', 20) + ->where('order_status', '=', 10) + ->where('active.status', '=', 30) + ->where('is_refund', '=', 0) + ->where('order.is_delete', '=', 0) + ->limit($limit) + ->select(); + } + + /** + * 更新拼团失败的订单并退款 + * @param $orderList + * @return bool + */ + public function updateFailedStatus($orderList) + { + // 批量更新订单状态 + foreach ($orderList as $order) { + /* @var static $order */ + try { + // 执行退款操作 + (new RefundService)->execute($order); + // 更新订单状态 + $order->save([ + 'is_refund' => 1, + 'order_status' => '20' + ]); + } catch (\Exception $e) { + $this->error = '订单ID:' . $order['order_id'] . ' 退款失败,错误信息:' . $e->getMessage(); + return false; + } + } + return true; + } + +} diff --git a/source/application/task/model/sharing/OrderAddress.php b/source/application/task/model/sharing/OrderAddress.php new file mode 100644 index 0000000..4e7fe53 --- /dev/null +++ b/source/application/task/model/sharing/OrderAddress.php @@ -0,0 +1,15 @@ +model = new OrderModel; + } + + /** + * 未支付订单自动关闭 + * @param int $deadlineTime + * @param array $where + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function close($deadlineTime, $where = []) + { + // 条件 + $filter = array_merge($where, [ + 'pay_status' => 10, + 'order_status' => 10, + 'create_time' => ['<=', $deadlineTime] + ]); + // 查询截止时间未支付的订单 + $list = $this->model->getList($filter, ['goods', 'user']); + $this->closeOrderIds = helper::getArrayColumn($list, 'order_id'); + // 取消订单事件 + if (!empty($this->closeOrderIds)) { + foreach ($list as &$order) { + // 回退商品库存 + FactoryStock::getFactory($order['order_source'])->backGoodsStock($order['goods'], false); + // 回退用户优惠券 + $order['coupon_id'] > 0 && UserCouponModel::setIsUse($order['coupon_id'], false); + // 回退用户积分 + $describe = "订单取消:{$order['order_no']}"; + $order['points_num'] > 0 && $order->user->setIncPoints($order['points_num'], $describe); + } + // 批量更新订单状态为已取消 + return $this->model->onBatchUpdate($this->closeOrderIds, ['order_status' => 20]); + } + return true; + } + + /** + * 获取自动关闭的订单id集 + * @return array + */ + public function getCloseOrderIds() + { + return $this->closeOrderIds; + } + +} \ No newline at end of file diff --git a/source/composer.json b/source/composer.json new file mode 100644 index 0000000..e9a0c3a --- /dev/null +++ b/source/composer.json @@ -0,0 +1,47 @@ +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=5.4.0", + "ext-pdo": "*", + "ext-curl": "*", + "ext-json": "*", + "ext-bcmath": "*", + "ext-gd": "*", + "ext-openssl": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "ext-zlib": "*", + "topthink/framework": "5.0.*", + "qiniu/php-sdk": "^7.2", + "aliyuncs/oss-sdk-php": "^2.3", + "qcloud/cos-sdk-v5": "^1.2", + "kosinix/grafika": "dev-master", + "myclabs/php-enum": "^1.6", + "lvht/geohash": "^1.1" + }, + "autoload": { + "psr-4": { + "app\\": "application" + } + }, + "extra": { + "think-path": "thinkphp" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/source/runtime/.gitignore b/source/runtime/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/source/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/source/thinkphp/.gitignore b/source/thinkphp/.gitignore new file mode 100644 index 0000000..7e31ef5 --- /dev/null +++ b/source/thinkphp/.gitignore @@ -0,0 +1,4 @@ +/composer.lock +/vendor +.idea +.DS_Store diff --git a/source/thinkphp/.htaccess b/source/thinkphp/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/source/thinkphp/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/source/thinkphp/.travis.yml b/source/thinkphp/.travis.yml new file mode 100644 index 0000000..f74ffca --- /dev/null +++ b/source/thinkphp/.travis.yml @@ -0,0 +1,47 @@ +sudo: false + +language: php + +services: + - memcached + - mongodb + - mysql + - postgresql + - redis-server + +matrix: + fast_finish: true + include: + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: hhvm + allow_failures: + - php: hhvm + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + - mysql -e "create database IF NOT EXISTS test;" -uroot + - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres + - psql -c 'create database test;' -U postgres + +install: + - ./tests/script/install.sh + +script: + ## LINT + - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \; + ## PHP Copy/Paste Detector + - vendor/bin/phpcpd --verbose --exclude vendor ./ || true + ## PHPLOC + - vendor/bin/phploc --exclude vendor ./ + ## PHPUNIT + - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/source/thinkphp/CONTRIBUTING.md b/source/thinkphp/CONTRIBUTING.md new file mode 100644 index 0000000..dc8e91c --- /dev/null +++ b/source/thinkphp/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 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范; +* 管理员不会合并造成 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,不需要重新提交 `pull request`,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 5.4 ~ 5.6 +- 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/source/thinkphp/LICENSE.txt b/source/thinkphp/LICENSE.txt new file mode 100644 index 0000000..2cb9a8a --- /dev/null +++ b/source/thinkphp/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2017 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/source/thinkphp/README.md b/source/thinkphp/README.md new file mode 100644 index 0000000..f01fd2b --- /dev/null +++ b/source/thinkphp/README.md @@ -0,0 +1,114 @@ +ThinkPHP 5.0 +=============== + +[![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411) +[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) +[![codecov.io](http://codecov.io/github/top-think/framework/coverage.svg?branch=master)](http://codecov.io/github/github/top-think/framework?branch=master) +[![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) +[![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + +ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括: + + + 基于命名空间和众多PHP新特性 + + 核心功能组件化 + + 强化路由功能 + + 更灵活的控制器 + + 重构的模型和数据库类 + + 配置文件可分离 + + 重写的自动验证和完成 + + 简化扩展机制 + + API支持完善 + + 改进的Log类 + + 命令行访问支持 + + REST支持 + + 引导文件支持 + + 方便的自动生成定义 + + 真正惰性加载 + + 分布式环境支持 + + 支持Composer + + 支持MongoDb + +> ThinkPHP5的运行环境要求PHP5.4以上。 + +详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart) + +## 目录结构 + +初始的目录结构如下: + +~~~ +www WEB部署目录(或者子目录) +├─application 应用目录 +│ ├─common 公共模块目录(可以更改) +│ ├─module_name 模块目录 +│ │ ├─config.php 模块配置文件 +│ │ ├─common.php 模块函数文件 +│ │ ├─controller 控制器目录 +│ │ ├─model 模型目录 +│ │ ├─view 视图目录 +│ │ └─ ... 更多类库目录 +│ │ +│ ├─command.php 命令行工具配置文件 +│ ├─common.php 公共函数文件 +│ ├─config.php 公共配置文件 +│ ├─route.php 路由配置文件 +│ ├─tags.php 应用行为扩展定义文件 +│ └─database.php 数据库配置文件 +│ +├─public WEB目录(对外访问目录) +│ ├─index.php 入口文件 +│ ├─router.php 快速测试文件 +│ └─.htaccess 用于apache的重写 +│ +├─thinkphp 框架系统目录 +│ ├─lang 语言文件目录 +│ ├─library 框架类库目录 +│ │ ├─think Think类库包目录 +│ │ └─traits 系统Trait目录 +│ │ +│ ├─tpl 系统模板目录 +│ ├─base.php 基础定义文件 +│ ├─console.php 控制台入口文件 +│ ├─convention.php 框架惯例配置文件 +│ ├─helper.php 助手函数文件 +│ ├─phpunit.xml phpunit配置文件 +│ └─start.php 框架入口文件 +│ +├─extend 扩展类库目录 +├─runtime 应用的运行时目录(可写,可定制) +├─vendor 第三方类库目录(Composer依赖库) +├─build.php 自动生成定义文件(参考) +├─composer.json composer 定义文件 +├─LICENSE.txt 授权说明文件 +├─README.md README 文件 +├─think 命令行入口文件 +~~~ + +> router.php用于php自带webserver支持,可用于快速测试 +> 切换到public目录后,启动命令:php -S localhost:8888 router.php +> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 + +## 命名规范 + +ThinkPHP5的命名规范遵循`PSR-2`规范以及`PSR-4`自动加载规范。 + +## 参与开发 +注册并登录 Github 帐号, fork 本项目并进行改动。 + +更多细节参阅 [CONTRIBUTING.md](CONTRIBUTING.md) + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) + +All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/source/thinkphp/base.php b/source/thinkphp/base.php new file mode 100644 index 0000000..92c4fa5 --- /dev/null +++ b/source/thinkphp/base.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- + +define('THINK_VERSION', '5.0.24'); +define('THINK_START_TIME', microtime(true)); +define('THINK_START_MEM', memory_get_usage()); +define('EXT', '.php'); +define('DS', DIRECTORY_SEPARATOR); +defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS); +define('LIB_PATH', THINK_PATH . 'library' . DS); +define('CORE_PATH', LIB_PATH . 'think' . DS); +define('TRAIT_PATH', LIB_PATH . 'traits' . DS); +defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); +defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS); +defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS); +defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS); +defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); +defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); +defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); +defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); +defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录 +defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀 +defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀 + +// 环境常量 +define('IS_CLI', PHP_SAPI == 'cli' ? true : false); +define('IS_WIN', strpos(PHP_OS, 'WIN') !== false); + +// 载入Loader类 +require CORE_PATH . 'Loader.php'; + +// 加载环境变量配置文件 +if (is_file(ROOT_PATH . '.env')) { + $env = parse_ini_file(ROOT_PATH . '.env', true); + + foreach ($env as $key => $val) { + $name = ENV_PREFIX . strtoupper($key); + + if (is_array($val)) { + foreach ($val as $k => $v) { + $item = $name . '_' . strtoupper($k); + putenv("$item=$v"); + } + } else { + putenv("$name=$val"); + } + } +} + +// 注册自动加载 +\think\Loader::register(); + +// 注册错误和异常处理机制 +\think\Error::register(); + +// 加载惯例配置文件 +\think\Config::set(include THINK_PATH . 'convention' . EXT); diff --git a/source/thinkphp/codecov.yml b/source/thinkphp/codecov.yml new file mode 100644 index 0000000..bef9d64 --- /dev/null +++ b/source/thinkphp/codecov.yml @@ -0,0 +1,12 @@ +comment: + layout: header, changes, diff +coverage: + ignore: + - base.php + - helper.php + - convention.php + - lang/zh-cn.php + - start.php + - console.php + status: + patch: false diff --git a/source/thinkphp/composer.json b/source/thinkphp/composer.json new file mode 100644 index 0000000..c546e11 --- /dev/null +++ b/source/thinkphp/composer.json @@ -0,0 +1,35 @@ +{ + "name": "topthink/framework", + "description": "the new thinkphp framework", + "type": "think-framework", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=5.4.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsStream": "~1.6", + "phploc/phploc": "2.*", + "sebastian/phpcpd": "2.*", + "phpdocumentor/reflection-docblock": "^2.0" + }, + "autoload": { + "psr-4": { + "think\\": "library/think" + } + } +} diff --git a/source/thinkphp/console.php b/source/thinkphp/console.php new file mode 100644 index 0000000..578e4a7 --- /dev/null +++ b/source/thinkphp/console.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 加载基础文件 +require __DIR__ . '/base.php'; + +// 执行应用 +App::initCommon(); +Console::init(); diff --git a/source/thinkphp/convention.php b/source/thinkphp/convention.php new file mode 100644 index 0000000..31a0a0c --- /dev/null +++ b/source/thinkphp/convention.php @@ -0,0 +1,298 @@ + '', + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 扩展函数文件 + 'extra_file_list' => [THINK_PATH . 'helper' . EXT], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'PRC', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 默认模块名 + 'default_module' => 'index', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, + + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- + + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // HTTPS代理标识 + 'https_agent_name' => '', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由 + 'url_route_on' => true, + // 路由配置文件(支持配置多个) + 'route_config_file' => ['route'], + // 路由使用完整匹配 + 'route_complete_match' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 域名部署 + 'url_domain_deploy' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 模板设置 + // +---------------------------------------------------------------------- + + 'template' => [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 视图基础目录,配置目录为所有模块的视图起始目录 + 'view_base' => '', + // 当前模板的视图目录 留空为自动获取 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + ], + + // 视图输出字符串内容替换 + 'view_replace_str' => [], + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 异常页面的模板文件 + 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + // 是否记录trace信息到日志 + 'record_trace' => false, + + // +---------------------------------------------------------------------- + // | 日志设置 + // +---------------------------------------------------------------------- + + 'log' => [ + // 日志记录方式,内置 file socket 支持扩展 + 'type' => 'File', + // 日志保存目录 + 'path' => LOG_PATH, + // 日志记录级别 + 'level' => [], + ], + + // +---------------------------------------------------------------------- + // | Trace设置 开启 app_trace 后 有效 + // +---------------------------------------------------------------------- + 'trace' => [ + // 内置Html Console 支持扩展 + 'type' => 'Html', + ], + + // +---------------------------------------------------------------------- + // | 缓存设置 + // +---------------------------------------------------------------------- + + 'cache' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => CACHE_PATH, + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + ], + + // +---------------------------------------------------------------------- + // | 会话设置 + // +---------------------------------------------------------------------- + + 'session' => [ + 'id' => '', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // SESSION 前缀 + 'prefix' => 'think', + // 驱动方式 支持redis memcache memcached + 'type' => '', + // 是否自动开启 SESSION + 'auto_start' => true, + 'httponly' => true, + 'secure' => false, + ], + + // +---------------------------------------------------------------------- + // | Cookie设置 + // +---------------------------------------------------------------------- + 'cookie' => [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ], + + // +---------------------------------------------------------------------- + // | 数据库设置 + // +---------------------------------------------------------------------- + + 'database' => [ + // 数据库类型 + 'type' => 'mysql', + // 数据库连接DSN配置 + 'dsn' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '', + // 数据库连接端口 + 'hostport' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ], + + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page', + 'list_rows' => 15, + ], + + //控制台配置 + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + ], + +]; diff --git a/source/thinkphp/helper.php b/source/thinkphp/helper.php new file mode 100644 index 0000000..12683cf --- /dev/null +++ b/source/thinkphp/helper.php @@ -0,0 +1,589 @@ + +// +---------------------------------------------------------------------- + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\Cache; +use think\Config; +use think\Cookie; +use think\Db; +use think\Debug; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\Lang; +use think\Loader; +use think\Log; +use think\Model; +use think\Request; +use think\Response; +use think\Session; +use think\Url; +use think\View; + +if (!function_exists('load_trait')) { + /** + * 快速导入Traits PHP5.5以上无需调用 + * @param string $class trait库 + * @param string $ext 类库后缀 + * @return boolean + */ + function load_trait($class, $ext = EXT) + { + return Loader::import($class, TRAIT_PATH, $ext); + } +} + +if (!function_exists('exception')) { + /** + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception + */ + function exception($msg, $code = 0, $exception = '') + { + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); + } +} + +if (!function_exists('debug')) { + /** + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 + * @return mixed + */ + function debug($start, $end = '', $dec = 6) + { + if ('' == $end) { + Debug::remark($start); + } else { + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang($name, $vars = [], $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @param string $range 作用域 + * @return mixed + */ + function config($name = '', $value = null, $range = '') + { + if (is_null($value) && is_string($name)) { + return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range); + } else { + return Config::set($name, $value, $range); + } + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input($key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + if ($pos = strpos($key, '.')) { + // 指定参数来源 + list($method, $key) = explode('.', $key, 2); + if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = $method . '.' . $key; + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + if (isset($has)) { + return request()->has($key, $method, $default); + } else { + return request()->$method($key, $default, $filter); + } + } +} + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + */ + function widget($name, $data = []) + { + return Loader::action($name, $data, 'widget'); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return Loader::model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return Loader::validate($name, $layer, $appendSuffix); + } +} + +if (!function_exists('db')) { + /** + * 实例化数据库类 + * @param string $name 操作的数据表名称(不含前缀) + * @param array|string $config 数据库配置参数 + * @param bool $force 是否强制重新连接 + * @return \think\db\Query + */ + function db($name = '', $config = [], $force = false) + { + return Db::connect($config, $force)->name($name); + } +} + +if (!function_exists('controller')) { + /** + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Controller + */ + function controller($name, $layer = 'controller', $appendSuffix = false) + { + return Loader::controller($name, $layer, $appendSuffix); + } +} + +if (!function_exists('action')) { + /** + * 调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + return Loader::action($url, $vars, $layer, $appendSuffix); + } +} + +if (!function_exists('import')) { + /** + * 导入所需的类库 同java的Import 本函数有缓存功能 + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return boolean + */ + function import($class, $baseUrl = '', $ext = EXT) + { + return Loader::import($class, $baseUrl, $ext); + } +} + +if (!function_exists('vendor')) { + /** + * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面 + * @param string $class 类库 + * @param string $ext 类库后缀 + * @return boolean + */ + function vendor($class, $ext = EXT) + { + return Loader::import($class, VENDOR_PATH, $ext); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + function dump($var, $echo = true, $label = null) + { + return Debug::dump($var, $echo, $label); + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string + */ + function url($url = '', $vars = '', $suffix = true, $domain = false) + { + return Url::build($url, $vars, $suffix, $domain); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear('' === $value ? null : $value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string|array $name cookie名称,如果为数组表示进行cookie设置 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie($name, $value = '', $option = null) + { + if (is_array($name)) { + // 初始化 + Cookie::init($name); + } elseif (is_null($name)) { + // 清除 + Cookie::clear($value); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); + } elseif (is_null($value)) { + // 删除 + return Cookie::delete($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache($name, $value = '', $options = null, $tag = null) + { + if (is_array($options)) { + // 缓存操作的同时初始化 + $cache = Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); + } else { + $cache = Cache::init(); + } + + if (is_null($name)) { + return $cache->clear($value); + } elseif ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name); + } elseif (is_null($value)) { + // 删除缓存 + return $cache->rm($name); + } elseif (0 === strpos($name, '?') && '' !== $value) { + $expire = is_numeric($options) ? $options : null; + return $cache->remember(substr($name, 1), $value, $expire); + } else { + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + if (is_null($tag)) { + return $cache->set($name, $value, $expire); + } else { + return $cache->tag($tag)->set($name, $value, $expire); + } + } + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return void|array + */ + function trace($log = '[think]', $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } else { + Log::record($log, $level); + } + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request() + { + return Request::instance(); + } +} + +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') + { + return Response::create($data, $type, $code, $header); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $replace 模板替换 + * @param integer $code 状态码 + * @return \think\response\View + */ + function view($template = '', $vars = [], $replace = [], $code = 200) + { + return Response::create($template, 'view', $code)->replace($replace)->assign($vars); + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'json', $code, $header, $options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'jsonp', $code, $header, $options); + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'xml', $code, $header, $options); + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param mixed $url 重定向地址 支持Url::build方法的地址 + * @param array|integer $params 额外参数 + * @param integer $code 状态码 + * @param array $with 隐式传参 + * @return \think\response\Redirect + */ + function redirect($url = [], $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + return Response::create($url, 'redirect', $code)->params($params)->with($with); + } +} + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, $message = null, $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 + */ + function halt($var) + { + dump($var); + throw new HttpResponseException(new Response); + } +} + +if (!function_exists('token')) { + /** + * 生成表单令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token($name = '__token__', $type = 'md5') + { + $token = Request::instance()->token($name, $type); + return ''; + } +} + +if (!function_exists('load_relation')) { + /** + * 延迟预载入关联查询 + * @param mixed $resultSet 数据集 + * @param mixed $relation 关联 + * @return array + */ + function load_relation($resultSet, $relation) + { + $item = current($resultSet); + if ($item instanceof Model) { + $item->eagerlyResultSet($resultSet, $relation); + } + return $resultSet; + } +} + +if (!function_exists('collection')) { + /** + * 数组转换为数据集对象 + * @param array $resultSet 数据集数组 + * @return \think\model\Collection|\think\Collection + */ + function collection($resultSet) + { + $item = current($resultSet); + if ($item instanceof Model) { + return \think\model\Collection::make($resultSet); + } else { + return \think\Collection::make($resultSet); + } + } +} diff --git a/source/thinkphp/lang/zh-cn.php b/source/thinkphp/lang/zh-cn.php new file mode 100644 index 0000000..eb7a914 --- /dev/null +++ b/source/thinkphp/lang/zh-cn.php @@ -0,0 +1,136 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +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' => '方法不存在', + 'module not exists' => '模块不存在', + 'controller not exists' => '控制器不存在', + 'class not exists' => '类不存在', + 'property not exists' => '类的属性不存在', + 'template not exists' => '模板文件不存在', + 'illegal controller name' => '非法的控制器名称', + 'illegal action name' => '非法的操作名称', + 'url suffix deny' => '禁止的URL后缀访问', + 'Route Not Found' => '当前访问路由未定义', + 'Undefined db type' => '未定义数据库类型', + 'variable type error' => '变量类型错误', + 'PSR-4 error' => 'PSR-4 规范错误', + 'not support total' => '简洁模式下不能获取数据总数', + 'not support last' => '简洁模式下不能获取最后一页', + 'error session handler' => '错误的SESSION处理器类', + 'not allow php tag' => '模板不允许使用PHP语法', + 'not support' => '不支持', + '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' => '查询表达式错误', + 'not support data' => '不支持的数据表达式', + '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方法', + '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} 创建失败!', + + // Validate Error Message + ':attribute require' => ':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' => '规则错误', +]; diff --git a/source/thinkphp/library/think/App.php b/source/thinkphp/library/think/App.php new file mode 100644 index 0000000..f572b90 --- /dev/null +++ b/source/thinkphp/library/think/App.php @@ -0,0 +1,677 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\exception\RouteNotFoundException; + +/** + * App 应用管理 + * @author liu21st + */ +class App +{ + /** + * @var bool 是否初始化过 + */ + protected static $init = false; + + /** + * @var string 当前模块路径 + */ + public static $modulePath; + + /** + * @var bool 应用调试模式 + */ + public static $debug = true; + + /** + * @var string 应用类库命名空间 + */ + public static $namespace = 'app'; + + /** + * @var bool 应用类库后缀 + */ + public static $suffix = false; + + /** + * @var bool 应用路由检测 + */ + protected static $routeCheck; + + /** + * @var bool 严格路由检测 + */ + protected static $routeMust; + + /** + * @var array 请求调度分发 + */ + protected static $dispatch; + + /** + * @var array 额外加载文件 + */ + protected static $file = []; + + /** + * 执行应用程序 + * @access public + * @param Request $request 请求对象 + * @return Response + * @throws Exception + */ + public static function run(Request $request = null) + { + $request = is_null($request) ? Request::instance() : $request; + + try { + $config = self::initCommon(); + + // 模块/控制器绑定 + if (defined('BIND_MODULE')) { + BIND_MODULE && Route::bind(BIND_MODULE); + } elseif ($config['auto_bind_module']) { + // 入口自动绑定 + $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { + Route::bind($name); + } + } + + $request->filter($config['default_filter']); + + // 默认语言 + Lang::range($config['default_lang']); + // 开启多语言机制 检测当前语言 + $config['lang_switch_on'] && Lang::detect(); + $request->langset(Lang::range()); + + // 加载系统语言包 + Lang::load([ + THINK_PATH . 'lang' . DS . $request->langset() . EXT, + APP_PATH . 'lang' . DS . $request->langset() . EXT, + ]); + + // 监听 app_dispatch + Hook::listen('app_dispatch', self::$dispatch); + // 获取应用调度信息 + $dispatch = self::$dispatch; + + // 未设置调度信息则进行 URL 路由检测 + if (empty($dispatch)) { + $dispatch = self::routeCheck($request, $config); + } + + // 记录当前调度信息 + $request->dispatch($dispatch); + + // 记录路由和请求信息 + if (self::$debug) { + Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); + Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); + Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); + } + + // 监听 app_begin + Hook::listen('app_begin', $dispatch); + + // 请求缓存检查 + $request->cache( + $config['request_cache'], + $config['request_cache_expire'], + $config['request_cache_except'] + ); + + $data = self::exec($dispatch, $config); + } catch (HttpResponseException $exception) { + $data = $exception->getResponse(); + } + + // 清空类的实例化 + Loader::clearInstance(); + + // 输出数据到客户端 + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $type = $request->isAjax() ? + Config::get('default_ajax_return') : + Config::get('default_return_type'); + + $response = Response::create($data, $type); + } else { + $response = Response::create(); + } + + // 监听 app_end + Hook::listen('app_end', $response); + + return $response; + } + + /** + * 初始化应用,并返回配置信息 + * @access public + * @return array + */ + public static function initCommon() + { + if (empty(self::$init)) { + if (defined('APP_NAMESPACE')) { + self::$namespace = APP_NAMESPACE; + } + + Loader::addNamespace(self::$namespace, APP_PATH); + + // 初始化应用 + $config = self::init(); + self::$suffix = $config['class_suffix']; + + // 应用调试模式 + self::$debug = Env::get('app_debug', Config::get('app_debug')); + + if (!self::$debug) { + ini_set('display_errors', 'Off'); + } elseif (!IS_CLI) { + // 重新申请一块比较大的 buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + + ob_start(); + + if (!empty($output)) { + echo $output; + } + + } + + if (!empty($config['root_namespace'])) { + Loader::addNamespace($config['root_namespace']); + } + + // 加载额外文件 + if (!empty($config['extra_file_list'])) { + foreach ($config['extra_file_list'] as $file) { + $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; + if (is_file($file) && !isset(self::$file[$file])) { + include $file; + self::$file[$file] = true; + } + } + } + + // 设置系统时区 + date_default_timezone_set($config['default_timezone']); + + // 监听 app_init + Hook::listen('app_init'); + + self::$init = true; + } + + return Config::get(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return array + */ + private static function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DS : ''; + + // 加载初始化文件 + if (is_file(APP_PATH . $module . 'init' . EXT)) { + include APP_PATH . $module . 'init' . EXT; + } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { + include RUNTIME_PATH . $module . 'init' . EXT; + } else { + // 加载模块配置 + $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); + + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + Config::load($filename, 'database'); + + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) { + $filename = $dir . DS . $file; + Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + // 加载应用状态配置 + if ($config['app_status']) { + Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + Hook::import(include CONF_PATH . $module . 'tags' . EXT); + } + + // 加载公共文件 + $path = APP_PATH . $module; + if (is_file($path . 'common' . EXT)) { + include $path . 'common' . EXT; + } + + // 加载当前模块语言包 + if ($module) { + Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); + } + } + + return Config::get(); + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param array|string $dispatch 调度信息 + * @param string $type 调度类型 + * @return void + */ + public static function dispatch($dispatch, $type = 'module') + { + self::$dispatch = ['type' => $type, $type => $dispatch]; + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|array|\Closure $function 函数或者闭包 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeFunction($function, $vars = []) + { + $reflect = new \ReflectionFunction($function); + $args = self::bindParams($reflect, $vars); + + // 记录执行信息 + self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); + + return $reflect->invokeArgs($args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param string|array $method 方法 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeMethod($method, $vars = []) + { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); + $reflect = new \ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new \ReflectionMethod($method); + } + + $args = self::bindParams($reflect, $vars); + + self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); + + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeClass($class, $vars = []) + { + $reflect = new \ReflectionClass($class); + $constructor = $reflect->getConstructor(); + $args = $constructor ? self::bindParams($constructor, $vars) : []; + + return $reflect->newInstanceArgs($args); + } + + /** + * 绑定参数 + * @access private + * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 + * @param array $vars 变量 + * @return array + */ + private static function bindParams($reflect, $vars = []) + { + // 自动获取请求变量 + if (empty($vars)) { + $vars = Config::get('url_param_type') ? + Request::instance()->route() : + Request::instance()->param(); + } + + $args = []; + if ($reflect->getNumberOfParameters() > 0) { + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + + foreach ($reflect->getParameters() as $param) { + $args[] = self::getParamValue($param, $vars, $type); + } + } + + return $args; + } + + /** + * 获取参数值 + * @access private + * @param \ReflectionParameter $param 参数 + * @param array $vars 变量 + * @param string $type 类别 + * @return array + */ + private static function getParamValue($param, &$vars, $type) + { + $name = $param->getName(); + $class = $param->getClass(); + + if ($class) { + $className = $class->getName(); + $bind = Request::instance()->$name; + + if ($bind instanceof $className) { + $result = $bind; + } else { + if (method_exists($className, 'invoke')) { + $method = new \ReflectionMethod($className, 'invoke'); + + if ($method->isPublic() && $method->isStatic()) { + return $className::invoke(Request::instance()); + } + } + + $result = method_exists($className, 'instance') ? + $className::instance() : + new $className; + } + } elseif (1 == $type && !empty($vars)) { + $result = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $result = $vars[$name]; + } elseif ($param->isDefaultValueAvailable()) { + $result = $param->getDefaultValue(); + } else { + throw new \InvalidArgumentException('method param miss:' . $name); + } + + return $result; + } + + /** + * 执行调用分发 + * @access protected + * @param array $dispatch 调用信息 + * @param array $config 配置信息 + * @return Response|mixed + * @throws \InvalidArgumentException + */ + protected static function exec($dispatch, $config) + { + switch ($dispatch['type']) { + case 'redirect': // 重定向跳转 + $data = Response::create($dispatch['url'], 'redirect') + ->code($dispatch['status']); + break; + case 'module': // 模块/控制器/操作 + $data = self::module( + $dispatch['module'], + $config, + isset($dispatch['convert']) ? $dispatch['convert'] : null + ); + break; + case 'controller': // 执行控制器操作 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = Loader::action( + $dispatch['controller'], + $vars, + $config['url_controller_layer'], + $config['controller_suffix'] + ); + break; + case 'method': // 回调方法 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = self::invokeMethod($dispatch['method'], $vars); + break; + case 'function': // 闭包 + $data = self::invokeFunction($dispatch['function']); + break; + case 'response': // Response 实例 + $data = $dispatch['response']; + break; + default: + throw new \InvalidArgumentException('dispatch type not support'); + } + + return $data; + } + + /** + * 执行模块 + * @access public + * @param array $result 模块/控制器/操作 + * @param array $config 配置参数 + * @param bool $convert 是否自动转换控制器和操作名 + * @return mixed + * @throws HttpException + */ + public static function module($result, $config, $convert = null) + { + if (is_string($result)) { + $result = explode('/', $result); + } + + $request = Request::instance(); + + if ($config['app_multi_module']) { + // 多模块部署 + $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); + $bind = Route::getBind('module'); + $available = false; + + if ($bind) { + // 绑定模块 + list($bindModule) = explode('/', $bind); + + if (empty($result[0])) { + $module = $bindModule; + $available = true; + } elseif ($module == $bindModule) { + $available = true; + } + } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { + $available = true; + } + + // 模块初始化 + if ($module && $available) { + // 初始化模块 + $request->module($module); + $config = self::init($module); + + // 模块请求缓存检查 + $request->cache( + $config['request_cache'], + $config['request_cache_expire'], + $config['request_cache_except'] + ); + } else { + throw new HttpException(404, 'module not exists:' . $module); + } + } else { + // 单一模块部署 + $module = ''; + $request->module($module); + } + + // 设置默认过滤机制 + $request->filter($config['default_filter']); + + // 当前模块路径 + App::$modulePath = APP_PATH . ($module ? $module . DS : ''); + + // 是否自动转换控制器和操作名 + $convert = is_bool($convert) ? $convert : $config['url_convert']; + + // 获取控制器名 + $controller = strip_tags($result[1] ?: $config['default_controller']); + + if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + + $controller = $convert ? strtolower($controller) : $controller; + + // 获取操作名 + $actionName = strip_tags($result[2] ?: $config['default_action']); + if (!empty($config['action_convert'])) { + $actionName = Loader::parseName($actionName, 1); + } else { + $actionName = $convert ? strtolower($actionName) : $actionName; + } + + // 设置当前请求的控制器、操作 + $request->controller(Loader::parseName($controller, 1))->action($actionName); + + // 监听module_init + Hook::listen('module_init', $request); + + try { + $instance = Loader::controller( + $controller, + $config['url_controller_layer'], + $config['controller_suffix'], + $config['empty_controller'] + ); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + // 获取当前操作名 + $action = $actionName . $config['action_suffix']; + + $vars = []; + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + // 严格获取当前操作方法名 + $reflect = new \ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $config['action_suffix']; + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $request->action($actionName); + + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$actionName]; + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + Hook::listen('action_begin', $call); + + return self::invokeMethod($call, $vars); + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @param \think\Request $request 请求实例 + * @param array $config 配置信息 + * @return array + * @throws \think\Exception + */ + public static function routeCheck($request, array $config) + { + $path = $request->path(); + $depr = $config['pathinfo_depr']; + $result = false; + + // 路由检测 + $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; + if ($check) { + // 开启路由 + if (is_file(RUNTIME_PATH . 'route.php')) { + // 读取路由缓存 + $rules = include RUNTIME_PATH . 'route.php'; + is_array($rules) && Route::rules($rules); + } else { + $files = $config['route_config_file']; + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + // 导入路由配置 + $rules = include CONF_PATH . $file . CONF_EXT; + is_array($rules) && Route::import($rules); + } + } + } + + // 路由检测(根据路由定义返回不同的URL调度) + $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); + $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; + + if ($must && false === $result) { + // 路由无效 + throw new RouteNotFoundException(); + } + } + + // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 + if (false === $result) { + $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); + } + + return $result; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $route 是否需要检测路由 + * @param bool $must 是否强制检测路由 + * @return void + */ + public static function route($route, $must = false) + { + self::$routeCheck = $route; + self::$routeMust = $must; + } +} diff --git a/source/thinkphp/library/think/Build.php b/source/thinkphp/library/think/Build.php new file mode 100644 index 0000000..de7c327 --- /dev/null +++ b/source/thinkphp/library/think/Build.php @@ -0,0 +1,235 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Build +{ + /** + * 根据传入的 build 资料创建目录和文件 + * @access public + * @param array $build build 列表 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + * @throws Exception + */ + public static function run(array $build = [], $namespace = 'app', $suffix = false) + { + // 锁定 + $lock = APP_PATH . 'build.lock'; + + // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了 + if (!is_writable($lock)) { + if (!touch($lock)) { + throw new Exception( + '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!
请手动生成项目目录~', + 10006 + ); + } + + foreach ($build as $module => $list) { + if ('__dir__' == $module) { + // 创建目录列表 + self::buildDir($list); + } elseif ('__file__' == $module) { + // 创建文件列表 + self::buildFile($list); + } else { + // 创建模块 + self::module($module, $list, $namespace, $suffix); + } + } + + // 解除锁定 + unlink($lock); + } + } + + /** + * 创建目录 + * @access protected + * @param array $list 目录列表 + * @return void + */ + protected static function buildDir($list) + { + foreach ($list as $dir) { + // 目录不存在则创建目录 + !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true); + } + } + + /** + * 创建文件 + * @access protected + * @param array $list 文件列表 + * @return void + */ + protected static function buildFile($list) + { + foreach ($list as $file) { + // 先创建目录 + if (!is_dir(APP_PATH . dirname($file))) { + mkdir(APP_PATH . dirname($file), 0755, true); + } + + // 再创建文件 + if (!is_file(APP_PATH . $file)) { + file_put_contents( + APP_PATH . $file, + 'php' == pathinfo($file, PATHINFO_EXTENSION) ? " ['config.php', 'common.php'], + '__dir__' => ['controller', 'model', 'view'], + ]; + } + + // 创建子目录和文件 + foreach ($list as $path => $file) { + $modulePath = APP_PATH . $module . DS; + + if ('__dir__' == $path) { + // 生成子目录 + foreach ($file as $dir) { + self::checkDirBuild($modulePath . $dir); + } + } elseif ('__file__' == $path) { + // 生成(空白)文件 + foreach ($file as $name) { + if (!is_file($modulePath . $name)) { + file_put_contents( + $modulePath . $name, + 'php' == pathinfo($name, PATHINFO_EXTENSION) ? " +// +---------------------------------------------------------------------- + +namespace think; + +use think\cache\Driver; + +class Cache +{ + /** + * @var array 缓存的实例 + */ + public static $instance = []; + + /** + * @var int 缓存读取次数 + */ + public static $readTimes = 0; + + /** + * @var int 缓存写入次数 + */ + public static $writeTimes = 0; + + /** + * @var object 操作句柄 + */ + public static $handler; + + /** + * 连接缓存驱动 + * @access public + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @return Driver + */ + public static function connect(array $options = [], $name = false) + { + $type = !empty($options['type']) ? $options['type'] : 'File'; + + if (false === $name) { + $name = md5(serialize($options)); + } + + if (true === $name || !isset(self::$instance[$name])) { + $class = false === strpos($type, '\\') ? + '\\think\\cache\\driver\\' . ucwords($type) : + $type; + + // 记录初始化信息 + App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); + + if (true === $name) { + return new $class($options); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; + } + + /** + * 自动初始化缓存 + * @access public + * @param array $options 配置数组 + * @return Driver + */ + public static function init(array $options = []) + { + if (is_null(self::$handler)) { + if (empty($options) && 'complex' == Config::get('cache.type')) { + $default = Config::get('cache.default'); + // 获取默认缓存配置,并连接 + $options = Config::get('cache.' . $default['type']) ?: $default; + } elseif (empty($options)) { + $options = Config::get('cache'); + } + + self::$handler = self::connect($options); + } + + return self::$handler; + } + + /** + * 切换缓存类型 需要配置 cache.type 为 complex + * @access public + * @param string $name 缓存标识 + * @return Driver + */ + public static function store($name = '') + { + if ('' !== $name && 'complex' == Config::get('cache.type')) { + return self::connect(Config::get('cache.' . $name), strtolower($name)); + } + + return self::init(); + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public static function has($name) + { + self::$readTimes++; + + return self::init()->has($name); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存标识 + * @param mixed $default 默认值 + * @return mixed + */ + public static function get($name, $default = false) + { + self::$readTimes++; + + return self::init()->get($name, $default); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存标识 + * @param mixed $value 存储数据 + * @param int|null $expire 有效时间 0为永久 + * @return boolean + */ + public static function set($name, $value, $expire = null) + { + self::$writeTimes++; + + return self::init()->set($name, $value, $expire); + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public static function inc($name, $step = 1) + { + self::$writeTimes++; + + return self::init()->inc($name, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public static function dec($name, $step = 1) + { + self::$writeTimes++; + + return self::init()->dec($name, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存标识 + * @return boolean + */ + public static function rm($name) + { + self::$writeTimes++; + + return self::init()->rm($name); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public static function clear($tag = null) + { + self::$writeTimes++; + + return self::init()->clear($tag); + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public static function pull($name) + { + self::$readTimes++; + self::$writeTimes++; + + return self::init()->pull($name); + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public static function remember($name, $value, $expire = null) + { + self::$readTimes++; + + return self::init()->remember($name, $value, $expire); + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return Driver + */ + public static function tag($name, $keys = null, $overlay = false) + { + return self::init()->tag($name, $keys, $overlay); + } + +} diff --git a/source/thinkphp/library/think/Collection.php b/source/thinkphp/library/think/Collection.php new file mode 100644 index 0000000..f872476 --- /dev/null +++ b/source/thinkphp/library/think/Collection.php @@ -0,0 +1,467 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; + +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * @var array 数据 + */ + protected $items = []; + + /** + * Collection constructor. + * @access public + * @param array $items 数据 + */ + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + /** + * 创建 Collection 实例 + * @access public + * @param array $items 数据 + * @return static + */ + public static function make($items = []) + { + return new static($items); + } + + /** + * 判断数据是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * 将数据转成数组 + * @access public + * @return array + */ + public function toArray() + { + return array_map(function ($value) { + return ($value instanceof Model || $value instanceof self) ? + $value->toArray() : + $value; + }, $this->items); + } + + /** + * 获取全部的数据 + * @access public + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * 交换数组中的键和值 + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 返回数组中所有的键名组成的新 Collection 实例 + * @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)); + } + + /** + * 合并数组并返回一个新的 Collection 实例 + * @access public + * @param mixed $items 新的数据 + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回差集生成的新 Collection 实例 + * @access public + * @param mixed $items 做比较的数据 + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回交集组成的 Collection 新实例 + * @access public + * @param mixed $items 比较数据 + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->convertToArray($items))); + } + + /** + * 返回并删除数据中的的最后一个元素(出栈) + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 返回并删除数据中首个元素 + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value 值 + * @param mixed $key 键名 + * @return void + */ + public function unshift($value, $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value 值 + * @param mixed $key 键名 + * @return void + */ + public function push($value, $key = null) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * @access public + * @param callable $callback 回调函数 + * @param mixed $initial 初始值 + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序创建一个新的 Collection 实例 + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 把数据分割为新的数组块 + * @access public + * @param int $size 分隔长度 + * @param bool $preserveKeys 是否保持原数据索引 + * @return static + */ + public function chunk($size, $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 给数据中的每个元素执行回调 + * @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; + } + + if (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数过滤数据中的元素 + * @access public + * @param callable|null $callback 回调函数 + * @return static + */ + public function filter(callable $callback = null) + { + return new static(array_filter($this->items, $callback ?: null)); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param mixed $columnKey 键名 + * @param null $indexKey 作为索引值的列 + * @return array + */ + public function column($columnKey, $indexKey = null) + { + if (function_exists('array_column')) { + return array_column($this->items, $columnKey, $indexKey); + } + + $result = []; + foreach ($this->items as $row) { + $key = $value = null; + $keySet = $valueSet = false; + + if (null !== $indexKey && array_key_exists($indexKey, $row)) { + $key = (string) $row[$indexKey]; + $keySet = true; + } + + if (null === $columnKey) { + $valueSet = true; + $value = $row; + } elseif (is_array($row) && array_key_exists($columnKey, $row)) { + $valueSet = true; + $value = $row[$columnKey]; + } + + if ($valueSet) { + if ($keySet) { + $result[$key] = $value; + } else { + $result[] = $value; + } + } + } + + return $result; + } + + /** + * 对数据排序,并返回排序后的数据组成的新 Collection 实例 + * @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); + } + + /** + * 将数据打乱后组成新的 Collection 实例 + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + return new static($items); + } + + /** + * 截取数据并返回新的 Collection 实例 + * @access public + * @param int $offset 起始位置 + * @param int $length 截取长度 + * @param bool $preserveKeys 是否保持原先的键名 + * @return static + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + /** + * 指定的键是否存在 + * @access public + * @param mixed $offset 键名 + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + /** + * 获取指定键对应的值 + * @access public + * @param mixed $offset 键名 + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + /** + * 设置键值 + * @access public + * @param mixed $offset 键名 + * @param mixed $value 值 + * @return void + */ + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + /** + * 删除指定键值 + * @access public + * @param mixed $offset 键名 + * @return void + */ + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + /** + * 统计数据的个数 + * @access public + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * 获取数据的迭代器 + * @access public + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * 将数据反序列化成数组 + * @access public + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为 JSON 字符串 + * @access public + * @param integer $options json 参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 将数据转换成字符串 + * @access public + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * 将数据转换成数组 + * @access protected + * @param mixed $items 数据 + * @return array + */ + protected function convertToArray($items) + { + return $items instanceof self ? $items->all() : (array) $items; + } +} diff --git a/source/thinkphp/library/think/Config.php b/source/thinkphp/library/think/Config.php new file mode 100644 index 0000000..8fa668d --- /dev/null +++ b/source/thinkphp/library/think/Config.php @@ -0,0 +1,214 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Config +{ + /** + * @var array 配置参数 + */ + private static $config = []; + + /** + * @var string 参数作用域 + */ + private static $range = '_sys_'; + + /** + * 设定配置参数的作用域 + * @access public + * @param string $range 作用域 + * @return void + */ + public static function range($range) + { + self::$range = $range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + } + + /** + * 解析配置文件或内容 + * @access public + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public static function parse($config, $type = '', $name = '', $range = '') + { + $range = $range ?: self::$range; + + if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION); + + $class = false !== strpos($type, '\\') ? + $type : + '\\think\\config\\driver\\' . ucwords($type); + + return self::set((new $class())->parse($config), $name, $range); + } + + /** + * 加载配置文件(PHP格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public static function load($file, $name = '', $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + + if (is_file($file)) { + $name = strtolower($name); + $type = pathinfo($file, PATHINFO_EXTENSION); + + if ('php' == $type) { + return self::set(include $file, $name, $range); + } + + if ('yaml' == $type && function_exists('yaml_parse_file')) { + return self::set(yaml_parse_file($file), $name, $range); + } + + return self::parse($file, $type, $name, $range); + } + + return self::$config[$range]; + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持二级配置 . 号分割) + * @param string $range 作用域 + * @return bool + */ + public static function has($name, $range = '') + { + $range = $range ?: self::$range; + + if (!strpos($name, '.')) { + return isset(self::$config[$range][strtolower($name)]); + } + + // 二维数组设置和获取支持 + $name = explode('.', $name, 2); + return isset(self::$config[$range][strtolower($name[0])][$name[1]]); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 . 号分割) + * @param string $range 作用域 + * @return mixed + */ + public static function get($name = null, $range = '') + { + $range = $range ?: self::$range; + + // 无参数时获取所有 + if (empty($name) && isset(self::$config[$range])) { + return self::$config[$range]; + } + + // 非二级配置时直接返回 + if (!strpos($name, '.')) { + $name = strtolower($name); + return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null; + } + + // 二维数组设置和获取支持 + $name = explode('.', $name, 2); + $name[0] = strtolower($name[0]); + + if (!isset(self::$config[$range][$name[0]])) { + // 动态载入额外配置 + $module = Request::instance()->module(); + $file = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT; + + is_file($file) && self::load($file, $name[0]); + } + + return isset(self::$config[$range][$name[0]][$name[1]]) ? + self::$config[$range][$name[0]][$name[1]] : + null; + } + + /** + * 设置配置参数 name 为数组则为批量设置 + * @access public + * @param string|array $name 配置参数名(支持二级配置 . 号分割) + * @param mixed $value 配置值 + * @param string $range 作用域 + * @return mixed + */ + public static function set($name, $value = null, $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + + // 字符串则表示单个配置设置 + if (is_string($name)) { + if (!strpos($name, '.')) { + self::$config[$range][strtolower($name)] = $value; + } else { + // 二维数组 + $name = explode('.', $name, 2); + self::$config[$range][strtolower($name[0])][$name[1]] = $value; + } + + return $value; + } + + // 数组则表示批量设置 + if (is_array($name)) { + if (!empty($value)) { + self::$config[$range][$value] = isset(self::$config[$range][$value]) ? + array_merge(self::$config[$range][$value], $name) : + $name; + + return self::$config[$range][$value]; + } + + return self::$config[$range] = array_merge( + self::$config[$range], array_change_key_case($name) + ); + } + + // 为空直接返回已有配置 + return self::$config[$range]; + } + + /** + * 重置配置参数 + * @access public + * @param string $range 作用域 + * @return void + */ + public static function reset($range = '') + { + $range = $range ?: self::$range; + + if (true === $range) { + self::$config = []; + } else { + self::$config[$range] = []; + } + } +} diff --git a/source/thinkphp/library/think/Console.php b/source/thinkphp/library/think/Console.php new file mode 100644 index 0000000..32b2572 --- /dev/null +++ b/source/thinkphp/library/think/Console.php @@ -0,0 +1,863 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Command; +use think\console\command\Help as HelpCommand; +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 +{ + /** + * @var string 命令名称 + */ + private $name; + + /** + * @var string 命令版本 + */ + private $version; + + /** + * @var Command[] 命令 + */ + private $commands = []; + + /** + * @var bool 是否需要帮助信息 + */ + private $wantHelps = false; + + /** + * @var bool 是否捕获异常 + */ + private $catchExceptions = true; + + /** + * @var bool 是否自动退出执行 + */ + private $autoExit = true; + + /** + * @var InputDefinition 输入定义 + */ + private $definition; + + /** + * @var string 默认执行的命令 + */ + private $defaultCommand; + + /** + * @var array 默认提供的命令 + */ + private static $defaultCommands = [ + "think\\console\\command\\Help", + "think\\console\\command\\Lists", + "think\\console\\command\\Build", + "think\\console\\command\\Clear", + "think\\console\\command\\make\\Controller", + "think\\console\\command\\make\\Model", + "think\\console\\command\\optimize\\Autoload", + "think\\console\\command\\optimize\\Config", + "think\\console\\command\\optimize\\Route", + "think\\console\\command\\optimize\\Schema", + ]; + + /** + * Console constructor. + * @access public + * @param string $name 名称 + * @param string $version 版本 + * @param null|string $user 执行用户 + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null) + { + $this->name = $name; + $this->version = $version; + + if ($user) { + $this->setUser($user); + } + + $this->defaultCommand = 'list'; + $this->definition = $this->getDefaultInputDefinition(); + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + + /** + * 设置执行用户 + * @param $user + */ + public function setUser($user) + { + $user = posix_getpwnam($user); + if ($user) { + posix_setuid($user['uid']); + posix_setgid($user['gid']); + } + } + + /** + * 初始化 Console + * @access public + * @param bool $run 是否运行 Console + * @return int|Console + */ + public static function init($run = true) + { + static $console; + + if (!$console) { + $config = Config::get('console'); + // 实例化 console + $console = new self($config['name'], $config['version'], $config['user']); + + // 读取指令集 + if (is_file(CONF_PATH . 'command' . EXT)) { + $commands = include CONF_PATH . 'command' . EXT; + + if (is_array($commands)) { + foreach ($commands as $command) { + class_exists($command) && + is_subclass_of($command, "\\think\\console\\Command") && + $console->add(new $command()); // 注册指令 + } + } + } + } + + return $run ? $console->run() : $console; + } + + /** + * 调用命令 + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output + */ + public static function call($command, array $parameters = [], $driver = 'buffer') + { + $console = self::init(false); + + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $console->setCatchExceptions(false); + $console->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + */ + 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) ?: 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]); + } + + return $this->doRunCommand($this->find($name), $input, $output); + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition 输入定义 + * @return $this; + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + + return $this; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * 获取帮助信息 + * @access public + * @return string + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * 设置是否捕获异常 + * @access public + * @param bool $boolean 是否捕获 + * @return $this + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + + return $this; + } + + /** + * 设置是否自动退出 + * @access public + * @param bool $boolean 是否自动退出 + * @return $this + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + + return $this; + } + + /** + * 获取名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置名称 + * @access public + * @param string $name 名称 + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * 获取版本 + * @access public + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * 设置版本 + * @access public + * @param string $version 版本信息 + * @return $this + */ + public function setVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { + return sprintf( + '%s version %s', + $this->getName(), + $this->getVersion() + ); + } + + return 'Console Tool'; + } + + /** + * 注册一个指令 + * @access public + * @param string $name 指令名称 + * @return Command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * 批量添加指令 + * @access public + * @param Command[] $commands 指令实例 + * @return $this + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) $this->add($command); + + return $this; + } + + /** + * 添加一个指令 + * @access public + * @param Command $command 命令实例 + * @return Command|bool + */ + public function add(Command $command) + { + if (!$command->isEnabled()) { + $command->setConsole(null); + return false; + } + + $command->setConsole($this); + + 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 get($name) + { + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException( + sprintf('The command "%s" does not exist.', $name) + ); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces() + { + $namespaces = []; + + foreach ($this->commands as $command) { + $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($namespace) + { + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + + $allNamespaces = $this->getNamespaces(); + $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($name) + { + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $allCommands = array_keys($this->commands); + $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); + } + + if (count($commands) > 1) { + $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { + $commandName = $commandList[$nameOrAlias]->getName(); + + return $commandName === $nameOrAlias || !in_array($commandName, $commands); + }); + } + + $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->get($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + */ + public function all($namespace = null) + { + if (null === $namespace) return $this->commands; + + $commands = []; + + foreach ($this->commands as $name => $command) { + $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1); + + if ($ext === $namespace) $commands[$name] = $command; + } + + return $commands; + } + + /** + * 获取可能的指令名 + * @access public + * @param array $names 指令名 + * @return array + */ + public static function getAbbreviations($names) + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return void + */ + protected function configureIO(Input $input, Output $output) + { + 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); + } else { + if ($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) + { + return $input->getFirstArgument(); + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition() + { + 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 protected + * @return Command[] + */ + protected function getDefaultCommands() + { + $defaultCommands = []; + + foreach (self::$defaultCommands as $class) { + if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) { + $defaultCommands[] = new $class(); + } + } + + return $defaultCommands; + } + + /** + * 添加默认指令 + * @access public + * @param array $classes 指令 + * @return void + */ + public static function addDefaultCommands(array $classes) + { + self::$defaultCommands = array_merge(self::$defaultCommands, $classes); + } + + /** + * 获取可能的建议 + * @access private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions($abbrevs) + { + 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 string $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name 指令名称 + * @param array|\Traversable $collection 建议集合 + * @return array + */ + private function findAlternatives($name, $collection) + { + $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 public + * @param string $commandName 指令名称 + * @return $this + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + + return $this; + } + + /** + * 返回所有的命名空间 + * @access private + * @param string $name 指令名称 + * @return array + */ + private function extractAllNamespaces($name) + { + $namespaces = []; + + foreach (explode(':', $name, -1) as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + +} diff --git a/source/thinkphp/library/think/Controller.php b/source/thinkphp/library/think/Controller.php new file mode 100644 index 0000000..77225b7 --- /dev/null +++ b/source/thinkphp/library/think/Controller.php @@ -0,0 +1,229 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ValidateException; +use traits\controller\Jump; + +Loader::import('controller/Jump', TRAIT_PATH, EXT); + +class Controller +{ + use Jump; + + /** + * @var \think\View 视图类实例 + */ + protected $view; + + /** + * @var \think\Request Request 实例 + */ + protected $request; + + /** + * @var bool 验证失败是否抛出异常 + */ + protected $failException = false; + + /** + * @var bool 是否批量验证 + */ + protected $batchValidate = false; + + /** + * @var array 前置操作方法列表 + */ + protected $beforeActionList = []; + + /** + * 构造方法 + * @access public + * @param Request $request Request 对象 + */ + public function __construct(Request $request = null) + { + $this->view = View::instance(Config::get('template'), Config::get('view_replace_str')); + $this->request = is_null($request) ? Request::instance() : $request; + + // 控制器初始化 + $this->_initialize(); + + // 前置操作方法 + if ($this->beforeActionList) { + foreach ($this->beforeActionList as $method => $options) { + is_numeric($method) ? + $this->beforeAction($options) : + $this->beforeAction($method, $options); + } + } + } + + /** + * 初始化操作 + * @access protected + */ + protected function _initialize() + { + } + + /** + * 前置操作 + * @access protected + * @param string $method 前置操作方法名 + * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]] + * @return void + */ + protected function beforeAction($method, $options = []) + { + if (isset($options['only'])) { + if (is_string($options['only'])) { + $options['only'] = explode(',', $options['only']); + } + + if (!in_array($this->request->action(), $options['only'])) { + return; + } + } elseif (isset($options['except'])) { + if (is_string($options['except'])) { + $options['except'] = explode(',', $options['except']); + } + + if (in_array($this->request->action(), $options['except'])) { + return; + } + } + + call_user_func([$this, $method]); + } + + /** + * 加载模板输出 + * @access protected + * @param string $template 模板文件名 + * @param array $vars 模板输出变量 + * @param array $replace 模板替换 + * @param array $config 模板参数 + * @return mixed + */ + protected function fetch($template = '', $vars = [], $replace = [], $config = []) + { + return $this->view->fetch($template, $vars, $replace, $config); + } + + /** + * 渲染内容输出 + * @access protected + * @param string $content 模板内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + protected function display($content = '', $vars = [], $replace = [], $config = []) + { + return $this->view->display($content, $vars, $replace, $config); + } + + /** + * 模板变量赋值 + * @access protected + * @param mixed $name 要显示的模板变量 + * @param mixed $value 变量的值 + * @return $this + */ + protected function assign($name, $value = '') + { + $this->view->assign($name, $value); + + return $this; + } + + /** + * 初始化模板引擎 + * @access protected + * @param array|string $engine 引擎参数 + * @return $this + */ + protected function engine($engine) + { + $this->view->engine($engine); + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + protected function validateFailException($fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @param mixed $callback 回调方法(闭包) + * @return array|string|true + * @throws ValidateException + */ + protected function validate($data, $validate, $message = [], $batch = false, $callback = null) + { + if (is_array($validate)) { + $v = Loader::validate(); + $v->rule($validate); + } else { + // 支持场景 + if (strpos($validate, '.')) { + list($validate, $scene) = explode('.', $validate); + } + + $v = Loader::validate($validate); + + !empty($scene) && $v->scene($scene); + } + + // 批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + // 设置错误信息 + if (is_array($message)) { + $v->message($message); + } + + // 使用回调验证 + if ($callback && is_callable($callback)) { + call_user_func_array($callback, [$v, &$data]); + } + + if (!$v->check($data)) { + if ($this->failException) { + throw new ValidateException($v->getError()); + } + + return $v->getError(); + } + + return true; + } +} diff --git a/source/thinkphp/library/think/Cookie.php b/source/thinkphp/library/think/Cookie.php new file mode 100644 index 0000000..61b47cc --- /dev/null +++ b/source/thinkphp/library/think/Cookie.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Cookie +{ + /** + * @var array cookie 设置参数 + */ + protected static $config = [ + 'prefix' => '', // cookie 名称前缀 + 'expire' => 0, // cookie 保存时间 + 'path' => '/', // cookie 保存路径 + 'domain' => '', // cookie 有效域名 + 'secure' => false, // cookie 启用安全传输 + 'httponly' => false, // httponly 设置 + 'setcookie' => true, // 是否使用 setcookie + ]; + + /** + * @var bool 是否完成初始化了 + */ + protected static $init; + + /** + * Cookie初始化 + * @access public + * @param array $config 配置参数 + * @return void + */ + public static function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('cookie'); + } + + self::$config = array_merge(self::$config, array_change_key_case($config)); + + if (!empty(self::$config['httponly'])) { + ini_set('session.cookie_httponly', 1); + } + + self::$init = true; + } + + /** + * 设置或者获取 cookie 作用域(前缀) + * @access public + * @param string $prefix 前缀 + * @return string| + */ + public static function prefix($prefix = '') + { + if (empty($prefix)) { + return self::$config['prefix']; + } + + return self::$config['prefix'] = $prefix; + } + + /** + * Cookie 设置、获取、删除 + * @access public + * @param string $name cookie 名称 + * @param mixed $value cookie 值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function set($name, $value = '', $option = null) + { + !isset(self::$init) && self::init(); + + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) { + $option = ['expire' => $option]; + } elseif (is_string($option)) { + parse_str($option, $option); + } + + $config = array_merge(self::$config, array_change_key_case($option)); + } else { + $config = self::$config; + } + + $name = $config['prefix'] . $name; + + // 设置 cookie + if (is_array($value)) { + array_walk_recursive($value, 'self::jsonFormatProtect', 'encode'); + $value = 'think:' . json_encode($value); + } + + $expire = !empty($config['expire']) ? + $_SERVER['REQUEST_TIME'] + intval($config['expire']) : + 0; + + if ($config['setcookie']) { + setcookie( + $name, $value, $expire, $config['path'], $config['domain'], + $config['secure'], $config['httponly'] + ); + } + + $_COOKIE[$name] = $value; + } + + /** + * 永久保存 Cookie 数据 + * @access public + * @param string $name cookie 名称 + * @param mixed $value cookie 值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + self::set($name, $value, $option); + } + + /** + * 判断是否有 Cookie 数据 + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return bool + */ + public static function has($name, $prefix = null) + { + !isset(self::$init) && self::init(); + + $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + + return isset($_COOKIE[$prefix . $name]); + } + + /** + * 获取 Cookie 的值 + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return mixed + */ + public static function get($name = '', $prefix = null) + { + !isset(self::$init) && self::init(); + + $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + $key = $prefix . $name; + + if ('' == $name) { + // 获取全部 + if ($prefix) { + $value = []; + + foreach ($_COOKIE as $k => $val) { + if (0 === strpos($k, $prefix)) { + $value[$k] = $val; + } + + } + } else { + $value = $_COOKIE; + } + } elseif (isset($_COOKIE[$key])) { + $value = $_COOKIE[$key]; + + if (0 === strpos($value, 'think:')) { + $value = json_decode(substr($value, 6), true); + array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); + } + } else { + $value = null; + } + + return $value; + } + + /** + * 删除 Cookie + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return void + */ + public static function delete($name, $prefix = null) + { + !isset(self::$init) && self::init(); + + $config = self::$config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + $name = $prefix . $name; + + if ($config['setcookie']) { + setcookie( + $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + } + + // 删除指定 cookie + unset($_COOKIE[$name]); + } + + /** + * 清除指定前缀的所有 cookie + * @access public + * @param string|null $prefix cookie 前缀 + * @return void + */ + public static function clear($prefix = null) + { + if (empty($_COOKIE)) { + return; + } + + !isset(self::$init) && self::init(); + + // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀 + $config = self::$config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + + if ($prefix) { + foreach ($_COOKIE as $key => $val) { + if (0 === strpos($key, $prefix)) { + if ($config['setcookie']) { + setcookie( + $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + } + + unset($_COOKIE[$key]); + } + } + } + } + + /** + * json 转换时的格式保护 + * @access protected + * @param mixed $val 要转换的值 + * @param string $key 键名 + * @param string $type 转换类别 + * @return void + */ + protected static function jsonFormatProtect(&$val, $key, $type = 'encode') + { + if (!empty($val) && true !== $val) { + $val = 'decode' == $type ? urldecode($val) : urlencode($val); + } + } +} diff --git a/source/thinkphp/library/think/Db.php b/source/thinkphp/library/think/Db.php new file mode 100644 index 0000000..80f08d2 --- /dev/null +++ b/source/thinkphp/library/think/Db.php @@ -0,0 +1,180 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\db\Connection; +use think\db\Query; + +/** + * Class Db + * @package think + * @method Query table(string $table) static 指定数据表(含前缀) + * @method Query name(string $name) static 指定数据表(不含前缀) + * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 + * @method Query union(mixed $union, boolean $all = false) static UNION查询 + * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method Query order(mixed $field, string $order = null) static 查询ORDER + * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method mixed value(string $field) static 获取某个字段的值 + * @method array column(string $field, string $key = '') static 获取某个列的值 + * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 + * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 + * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID + * @method integer insertAll(array $dataSet) static 插入多条记录 + * @method integer update(array $data) static 更新记录 + * @method integer delete(mixed $data = null) static 删除记录 + * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 + * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 + * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 + * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 + * @method mixed transaction(callable $callback) static 执行数据库事务 + * @method void startTrans() static 启动事务 + * @method void commit() static 用于非自动提交状态下面的查询提交 + * @method void rollback() static 事务回滚 + * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 + * @method string quote(string $str) static SQL指令安全过滤 + * @method string getLastInsID($sequence = null) static 获取最近插入的ID + */ +class Db +{ + /** + * @var Connection[] 数据库连接实例 + */ + private static $instance = []; + + /** + * @var int 查询次数 + */ + public static $queryTimes = 0; + + /** + * @var int 执行次数 + */ + public static $executeTimes = 0; + + /** + * 数据库初始化,并取得数据库类实例 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return Connection + * @throws Exception + */ + public static function connect($config = [], $name = false) + { + if (false === $name) { + $name = md5(serialize($config)); + } + + if (true === $name || !isset(self::$instance[$name])) { + // 解析连接参数 支持数组和字符串 + $options = self::parseConfig($config); + + if (empty($options['type'])) { + throw new \InvalidArgumentException('Undefined db type'); + } + + $class = false !== strpos($options['type'], '\\') ? + $options['type'] : + '\\think\\db\\connector\\' . ucwords($options['type']); + + // 记录初始化信息 + if (App::$debug) { + Log::record('[ DB ] INIT ' . $options['type'], 'info'); + } + + if (true === $name) { + $name = md5(serialize($config)); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; + } + + /** + * 清除连接实例 + * @access public + * @return void + */ + public static function clear() + { + self::$instance = []; + } + + /** + * 数据库连接参数解析 + * @access private + * @param mixed $config 连接参数 + * @return array + */ + private static function parseConfig($config) + { + if (empty($config)) { + $config = Config::get('database'); + } elseif (is_string($config) && false === strpos($config, '/')) { + $config = Config::get($config); // 支持读取配置参数 + } + + return is_string($config) ? self::parseDsn($config) : $config; + } + + /** + * DSN 解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @access private + * @param string $dsnStr 数据库 DSN 字符串解析 + * @return array + */ + private static function parseDsn($dsnStr) + { + $info = parse_url($dsnStr); + + if (!$info) { + return []; + } + + $dsn = [ + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', + 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', + ]; + + if (isset($info['query'])) { + parse_str($info['query'], $dsn['params']); + } else { + $dsn['params'] = []; + } + + return $dsn; + } + + /** + * 调用驱动类的方法 + * @access public + * @param string $method 方法名 + * @param array $params 参数 + * @return mixed + */ + public static function __callStatic($method, $params) + { + return call_user_func_array([self::connect(), $method], $params); + } +} diff --git a/source/thinkphp/library/think/Debug.php b/source/thinkphp/library/think/Debug.php new file mode 100644 index 0000000..df48748 --- /dev/null +++ b/source/thinkphp/library/think/Debug.php @@ -0,0 +1,252 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\response\Redirect; + +class Debug +{ + /** + * @var array 区间时间信息 + */ + protected static $info = []; + + /** + * @var array 区间内存信息 + */ + protected static $mem = []; + + /** + * 记录时间(微秒)和内存使用情况 + * @access public + * @param string $name 标记位置 + * @param mixed $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存) + * @return void + */ + public static function remark($name, $value = '') + { + self::$info[$name] = is_float($value) ? $value : microtime(true); + + if ('time' != $value) { + self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); + self::$mem['peak'][$name] = memory_get_peak_usage(); + } + } + + /** + * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getRangeTime($start, $end, $dec = 6) + { + if (!isset(self::$info[$end])) { + self::$info[$end] = microtime(true); + } + + return number_format((self::$info[$end] - self::$info[$start]), $dec); + } + + /** + * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位 + * @access public + * @param integer $dec 小数位 + * @return string + */ + public static function getUseTime($dec = 6) + { + return number_format((microtime(true) - THINK_START_TIME), $dec); + } + + /** + * 获取当前访问的吞吐率情况 + * @access public + * @return string + */ + public static function getThroughputRate() + { + return number_format(1 / self::getUseTime(), 2) . 'req/s'; + } + + /** + * 记录区间的内存使用情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getRangeMem($start, $end, $dec = 2) + { + if (!isset(self::$mem['mem'][$end])) { + self::$mem['mem'][$end] = memory_get_usage(); + } + + $size = self::$mem['mem'][$end] - self::$mem['mem'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计从开始到统计时的内存使用情况 + * @access public + * @param integer $dec 小数位 + * @return string + */ + public static function getUseMem($dec = 2) + { + $size = memory_get_usage() - THINK_START_MEM; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计区间的内存峰值情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getMemPeak($start, $end, $dec = 2) + { + if (!isset(self::$mem['peak'][$end])) { + self::$mem['peak'][$end] = memory_get_peak_usage(); + } + + $size = self::$mem['peak'][$end] - self::$mem['peak'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 获取文件加载信息 + * @access public + * @param bool $detail 是否显示详细 + * @return integer|array + */ + public static function getFile($detail = false) + { + $files = get_included_files(); + + if ($detail) { + $info = []; + + foreach ($files as $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } + + return count($files); + } + + /** + * 浏览器友好的变量输出 + * @access public + * @param mixed $var 变量 + * @param boolean $echo 是否输出(默认为 true,为 false 则返回输出字符串) + * @param string|null $label 标签(默认为空) + * @param integer $flags htmlspecialchars 的标志 + * @return null|string + */ + public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) + { + $label = (null === $label) ? '' : rtrim($label) . ':'; + + ob_start(); + var_dump($var); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean()); + + if (IS_CLI) { + $output = PHP_EOL . $label . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, $flags); + } + + $output = '
' . $label . $output . '
'; + } + + if ($echo) { + echo($output); + return; + } + + return $output; + } + + /** + * 调试信息注入到响应中 + * @access public + * @param Response $response 响应实例 + * @param string $content 返回的字符串 + * @return void + */ + public static function inject(Response $response, &$content) + { + $config = Config::get('trace'); + $type = isset($config['type']) ? $config['type'] : 'Html'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type); + + unset($config['type']); + + if (!class_exists($class)) { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** @var \think\debug\Console|\think\debug\Html $trace */ + $trace = new $class($config); + + if ($response instanceof Redirect) { + // TODO 记录 + } else { + $output = $trace->output($response, Log::getLog()); + + 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/source/thinkphp/library/think/Env.php b/source/thinkphp/library/think/Env.php new file mode 100644 index 0000000..0a8b250 --- /dev/null +++ b/source/thinkphp/library/think/Env.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Env +{ + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 . 号分割) + * @param string $default 默认值 + * @return mixed + */ + public static function get($name, $default = null) + { + $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name))); + + if (false !== $result) { + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + return $result; + } + + return $default; + } +} diff --git a/source/thinkphp/library/think/Error.php b/source/thinkphp/library/think/Error.php new file mode 100644 index 0000000..5f361d5 --- /dev/null +++ b/source/thinkphp/library/think/Error.php @@ -0,0 +1,136 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use think\exception\ThrowableError; + +class Error +{ + /** + * 注册异常处理 + * @access public + * @return void + */ + public static function register() + { + error_reporting(E_ALL); + set_error_handler([__CLASS__, 'appError']); + set_exception_handler([__CLASS__, 'appException']); + register_shutdown_function([__CLASS__, 'appShutdown']); + } + + /** + * 异常处理 + * @access public + * @param \Exception|\Throwable $e 异常 + * @return void + */ + public static function appException($e) + { + if (!$e instanceof \Exception) { + $e = new ThrowableError($e); + } + + $handler = self::getExceptionHandler(); + $handler->report($e); + + if (IS_CLI) { + $handler->renderForConsole(new ConsoleOutput, $e); + } else { + $handler->render($e)->send(); + } + } + + /** + * 错误处理 + * @access public + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @return void + * @throws ErrorException + */ + public static function appError($errno, $errstr, $errfile = '', $errline = 0) + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + + // 符合异常处理的则将错误信息托管至 think\exception\ErrorException + if (error_reporting() & $errno) { + throw $exception; + } + + self::getExceptionHandler()->report($exception); + } + + /** + * 异常中止处理 + * @access public + * @return void + */ + public static function appShutdown() + { + // 将错误信息托管至 think\ErrorException + if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { + self::appException(new ErrorException( + $error['type'], $error['message'], $error['file'], $error['line'] + )); + } + + // 写入日志 + Log::save(); + } + + /** + * 确定错误类型是否致命 + * @access protected + * @param int $type 错误类型 + * @return bool + */ + protected static function isFatal($type) + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * 获取异常处理的实例 + * @access public + * @return Handle + */ + public static function getExceptionHandler() + { + static $handle; + + if (!$handle) { + // 异常处理 handle + $class = Config::get('exception_handle'); + + if ($class && is_string($class) && class_exists($class) && + is_subclass_of($class, "\\think\\exception\\Handle") + ) { + $handle = new $class; + } else { + $handle = new Handle; + + if ($class instanceof \Closure) { + $handle->setRender($class); + } + + } + } + + return $handle; + } +} diff --git a/source/thinkphp/library/think/Exception.php b/source/thinkphp/library/think/Exception.php new file mode 100644 index 0000000..1ef06bd --- /dev/null +++ b/source/thinkphp/library/think/Exception.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Exception extends \Exception +{ + /** + * @var array 保存异常页面显示的额外 Debug 数据 + */ + 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 需要显示的数据,必须为关联数组 + * @return void + */ + final protected function setData($label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外 Debug 数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/source/thinkphp/library/think/File.php b/source/thinkphp/library/think/File.php new file mode 100644 index 0000000..d2ed220 --- /dev/null +++ b/source/thinkphp/library/think/File.php @@ -0,0 +1,478 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use SplFileObject; + +class File extends SplFileObject +{ + /** + * @var string 错误信息 + */ + private $error = ''; + + /** + * @var string 当前完整文件名 + */ + protected $filename; + + /** + * @var string 上传文件名 + */ + protected $saveName; + + /** + * @var string 文件上传命名规则 + */ + protected $rule = 'date'; + + /** + * @var array 文件上传验证规则 + */ + protected $validate = []; + + /** + * @var bool 单元测试 + */ + protected $isTest; + + /** + * @var array 上传文件信息 + */ + protected $info; + + /** + * @var array 文件 hash 信息 + */ + protected $hash = []; + + /** + * File constructor. + * @access public + * @param string $filename 文件名称 + * @param string $mode 访问模式 + */ + public function __construct($filename, $mode = 'r') + { + parent::__construct($filename, $mode); + $this->filename = $this->getRealPath() ?: $this->getPathname(); + } + + /** + * 设置是否是单元测试 + * @access public + * @param bool $test 是否是测试 + * @return $this + */ + public function isTest($test = false) + { + $this->isTest = $test; + + return $this; + } + + /** + * 设置上传信息 + * @access public + * @param array $info 上传文件信息 + * @return $this + */ + public function setUploadInfo($info) + { + $this->info = $info; + + return $this; + } + + /** + * 获取上传文件的信息 + * @access public + * @param string $name 信息名称 + * @return array|string + */ + public function getInfo($name = '') + { + return isset($this->info[$name]) ? $this->info[$name] : $this->info; + } + + /** + * 获取上传文件的文件名 + * @access public + * @return string + */ + public function getSaveName() + { + return $this->saveName; + } + + /** + * 设置上传文件的保存文件名 + * @access public + * @param string $saveName 保存名称 + * @return $this + */ + public function setSaveName($saveName) + { + $this->saveName = $saveName; + + return $this; + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type 类型 + * @return string + */ + public function hash($type = 'sha1') + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->filename); + } + + return $this->hash[$type]; + } + + /** + * 检查目录是否可写 + * @access protected + * @param string $path 目录 + * @return boolean + */ + protected function checkPath($path) + { + if (is_dir($path) || mkdir($path, 0755, true)) { + return true; + } + + $this->error = ['directory {:path} creation failed', ['path' => $path]]; + + return false; + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime() + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->filename); + } + + /** + * 设置文件的命名规则 + * @access public + * @param string $rule 文件命名规则 + * @return $this + */ + public function rule($rule) + { + $this->rule = $rule; + + return $this; + } + + /** + * 设置上传文件的验证规则 + * @access public + * @param array $rule 验证规则 + * @return $this + */ + public function validate(array $rule = []) + { + $this->validate = $rule; + + return $this; + } + + /** + * 检测是否合法的上传文件 + * @access public + * @return bool + */ + public function isValid() + { + return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename); + } + + /** + * 检测上传文件 + * @access public + * @param array $rule 验证规则 + * @return bool + */ + public function check($rule = []) + { + $rule = $rule ?: $this->validate; + + /* 检查文件大小 */ + if (isset($rule['size']) && !$this->checkSize($rule['size'])) { + $this->error = 'filesize not match'; + return false; + } + + /* 检查文件 Mime 类型 */ + if (isset($rule['type']) && !$this->checkMime($rule['type'])) { + $this->error = 'mimetype to upload is not allowed'; + return false; + } + + /* 检查文件后缀 */ + if (isset($rule['ext']) && !$this->checkExt($rule['ext'])) { + $this->error = 'extensions to upload is not allowed'; + return false; + } + + /* 检查图像文件 */ + if (!$this->checkImg()) { + $this->error = 'illegal image files'; + return false; + } + + return true; + } + + /** + * 检测上传文件后缀 + * @access public + * @param array|string $ext 允许后缀 + * @return bool + */ + public function checkExt($ext) + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + return in_array($extension, $ext); + } + + /** + * 检测图像文件 + * @access public + * @return bool + */ + public function checkImg() + { + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true + return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]); + } + + /** + * 判断图像类型 + * @access protected + * @param string $image 图片名称 + * @return bool|int + */ + 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 integer $size 最大大小 + * @return bool + */ + public function checkSize($size) + { + return $this->getSize() <= $size; + } + + /** + * 检测上传文件类型 + * @access public + * @param array|string $mime 允许类型 + * @return bool + */ + public function checkMime($mime) + { + $mime = is_string($mime) ? explode(',', $mime) : $mime; + + return in_array(strtolower($this->getMime()), $mime); + } + + /** + * 移动文件 + * @access public + * @param string $path 保存路径 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param boolean $replace 同名文件是否覆盖 + * @return false|File + */ + public function move($path, $savename = true, $replace = true) + { + // 文件上传失败,捕获错误代码 + if (!empty($this->info['error'])) { + $this->error($this->info['error']); + return false; + } + + // 检测合法性 + if (!$this->isValid()) { + $this->error = 'upload illegal files'; + return false; + } + + // 验证上传 + if (!$this->check()) { + return false; + } + + $path = rtrim($path, DS) . DS; + // 文件保存命名规则 + $saveName = $this->buildSaveName($savename); + $filename = $path . $saveName; + + // 检测目录 + if (false === $this->checkPath(dirname($filename))) { + return false; + } + + // 不覆盖同名文件 + if (!$replace && is_file($filename)) { + $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; + return false; + } + + /* 移动文件 */ + if ($this->isTest) { + rename($this->filename, $filename); + } elseif (!move_uploaded_file($this->filename, $filename)) { + $this->error = 'upload write error'; + return false; + } + + // 返回 File 对象实例 + $file = new self($filename); + $file->setSaveName($saveName)->setUploadInfo($this->info); + + return $file; + } + + /** + * 获取保存文件名 + * @access protected + * @param string|bool $savename 保存的文件名 默认自动生成 + * @return string + */ + protected function buildSaveName($savename) + { + // 自动生成文件名 + if (true === $savename) { + if ($this->rule instanceof \Closure) { + $savename = call_user_func_array($this->rule, [$this]); + } else { + switch ($this->rule) { + case 'date': + $savename = date('Ymd') . DS . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . DS . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . DS . md5(microtime(true)); + } + } + } + } elseif ('' === $savename || false === $savename) { + $savename = $this->getInfo('name'); + } + + if (!strpos($savename, '.')) { + $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); + } + + return $savename; + } + + /** + * 获取错误代码信息 + * @access private + * @param int $errorNo 错误号 + * @return $this + */ + private function error($errorNo) + { + switch ($errorNo) { + case 1: + case 2: + $this->error = 'upload File size exceeds the maximum value'; + break; + case 3: + $this->error = 'only the portion of file is uploaded'; + break; + case 4: + $this->error = 'no file to uploaded'; + break; + case 6: + $this->error = 'upload temp dir not found'; + break; + case 7: + $this->error = 'file write error'; + break; + default: + $this->error = 'unknown upload error'; + } + + return $this; + } + + /** + * 获取错误信息(支持多语言) + * @access public + * @return string + */ + public function getError() + { + if (is_array($this->error)) { + list($msg, $vars) = $this->error; + } else { + $msg = $this->error; + $vars = []; + } + + return Lang::has($msg) ? Lang::get($msg, $vars) : $msg; + } + + /** + * 魔法方法,获取文件的 hash 值 + * @access public + * @param string $method 方法名 + * @param mixed $args 调用参数 + * @return string + */ + public function __call($method, $args) + { + return $this->hash($method); + } +} diff --git a/source/thinkphp/library/think/Hook.php b/source/thinkphp/library/think/Hook.php new file mode 100644 index 0000000..a69ce54 --- /dev/null +++ b/source/thinkphp/library/think/Hook.php @@ -0,0 +1,148 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Hook +{ + /** + * @var array 标签 + */ + private static $tags = []; + + /** + * 动态添加行为扩展到某个标签 + * @access public + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @param bool $first 是否放到开头执行 + * @return void + */ + public static function add($tag, $behavior, $first = false) + { + isset(self::$tags[$tag]) || self::$tags[$tag] = []; + + if (is_array($behavior) && !is_callable($behavior)) { + if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) { + unset($behavior['_overlay']); + self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior); + } else { + unset($behavior['_overlay']); + self::$tags[$tag] = $behavior; + } + } elseif ($first) { + array_unshift(self::$tags[$tag], $behavior); + } else { + self::$tags[$tag][] = $behavior; + } + } + + /** + * 批量导入插件 + * @access public + * @param array $tags 插件信息 + * @param boolean $recursive 是否递归合并 + * @return void + */ + public static function import(array $tags, $recursive = true) + { + if ($recursive) { + foreach ($tags as $tag => $behavior) { + self::add($tag, $behavior); + } + } else { + self::$tags = $tags + self::$tags; + } + } + + /** + * 获取插件信息 + * @access public + * @param string $tag 插件位置(留空获取全部) + * @return array + */ + public static function get($tag = '') + { + if (empty($tag)) { + return self::$tags; + } + + return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; + } + + /** + * 监听标签的行为 + * @access public + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @param mixed $extra 额外参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public static function listen($tag, &$params = null, $extra = null, $once = false) + { + $results = []; + + foreach (static::get($tag) as $key => $name) { + $results[$key] = self::exec($name, $tag, $params, $extra); + + // 如果返回 false,或者仅获取一个有效返回则中断行为执行 + if (false === $results[$key] || (!is_null($results[$key]) && $once)) { + break; + } + } + + return $once ? end($results) : $results; + } + + /** + * 执行某个行为 + * @access public + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param mixed $params 传人的参数 + * @param mixed $extra 额外参数 + * @return mixed + */ + public static function exec($class, $tag = '', &$params = null, $extra = null) + { + App::$debug && Debug::remark('behavior_start', 'time'); + + $method = Loader::parseName($tag, 1, false); + + if ($class instanceof \Closure) { + $result = call_user_func_array($class, [ & $params, $extra]); + $class = 'Closure'; + } elseif (is_array($class)) { + list($class, $method) = $class; + + $result = (new $class())->$method($params, $extra); + $class = $class . '->' . $method; + } elseif (is_object($class)) { + $result = $class->$method($params, $extra); + $class = get_class($class); + } elseif (strpos($class, '::')) { + $result = call_user_func_array($class, [ & $params, $extra]); + } else { + $obj = new $class(); + $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; + $result = $obj->$method($params, $extra); + } + + if (App::$debug) { + Debug::remark('behavior_end', 'time'); + Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); + } + + return $result; + } + +} diff --git a/source/thinkphp/library/think/Lang.php b/source/thinkphp/library/think/Lang.php new file mode 100644 index 0000000..a50d838 --- /dev/null +++ b/source/thinkphp/library/think/Lang.php @@ -0,0 +1,265 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Lang +{ + /** + * @var array 语言数据 + */ + private static $lang = []; + + /** + * @var string 语言作用域 + */ + private static $range = 'zh-cn'; + + /** + * @var string 语言自动侦测的变量 + */ + protected static $langDetectVar = 'lang'; + + /** + * @var string 语言 Cookie 变量 + */ + protected static $langCookieVar = 'think_var'; + + /** + * @var int 语言 Cookie 的过期时间 + */ + protected static $langCookieExpire = 3600; + + /** + * @var array 允许语言列表 + */ + protected static $allowLangList = []; + + /** + * @var array Accept-Language 转义为对应语言包名称 系统默认配置 + */ + protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn']; + + /** + * 设定当前的语言 + * @access public + * @param string $range 语言作用域 + * @return string + */ + public static function range($range = '') + { + if ($range) { + self::$range = $range; + } + + return self::$range; + } + + /** + * 设置语言定义(不区分大小写) + * @access public + * @param string|array $name 语言变量 + * @param string $value 语言值 + * @param string $range 语言作用域 + * @return mixed + */ + public static function set($name, $value = null, $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$lang[$range])) { + self::$lang[$range] = []; + } + + if (is_array($name)) { + return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range]; + } + + return self::$lang[$range][strtolower($name)] = $value; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param array|string $file 语言文件 + * @param string $range 语言作用域 + * @return mixed + */ + public static function load($file, $range = '') + { + $range = $range ?: self::$range; + $file = is_string($file) ? [$file] : $file; + + if (!isset(self::$lang[$range])) { + self::$lang[$range] = []; + } + + $lang = []; + + foreach ($file as $_file) { + if (is_file($_file)) { + // 记录加载信息 + App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); + + $_lang = include $_file; + + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } + } + } + + if (!empty($lang)) { + self::$lang[$range] = $lang + self::$lang[$range]; + } + + return self::$lang[$range]; + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return mixed + */ + public static function has($name, $range = '') + { + $range = $range ?: self::$range; + + return isset(self::$lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public static function get($name = null, $vars = [], $range = '') + { + $range = $range ?: self::$range; + + // 空参数返回所有定义 + if (empty($name)) { + return self::$lang[$range]; + } + + $key = strtolower($name); + $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $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 + * @return string + */ + public static function detect() + { + $langSet = ''; + + if (isset($_GET[self::$langDetectVar])) { + // url 中设置了语言变量 + $langSet = strtolower($_GET[self::$langDetectVar]); + } elseif (isset($_COOKIE[self::$langCookieVar])) { + // Cookie 中设置了语言变量 + $langSet = strtolower($_COOKIE[self::$langCookieVar]); + } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + // 自动侦测浏览器语言 + preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); + $langSet = strtolower($matches[1]); + $acceptLangs = Config::get('header_accept_lang'); + + if (isset($acceptLangs[$langSet])) { + $langSet = $acceptLangs[$langSet]; + } elseif (isset(self::$acceptLanguage[$langSet])) { + $langSet = self::$acceptLanguage[$langSet]; + } + } + + // 合法的语言 + if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { + self::$range = $langSet ?: self::$range; + } + + return self::$range; + } + + /** + * 设置语言自动侦测的变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public static function setLangDetectVar($var) + { + self::$langDetectVar = $var; + } + + /** + * 设置语言的 cookie 保存变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public static function setLangCookieVar($var) + { + self::$langCookieVar = $var; + } + + /** + * 设置语言的 cookie 的过期时间 + * @access public + * @param string $expire 过期时间 + * @return void + */ + public static function setLangCookieExpire($expire) + { + self::$langCookieExpire = $expire; + } + + /** + * 设置允许的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public static function setAllowLangList($list) + { + self::$allowLangList = $list; + } +} diff --git a/source/thinkphp/library/think/Loader.php b/source/thinkphp/library/think/Loader.php new file mode 100644 index 0000000..d813a5d --- /dev/null +++ b/source/thinkphp/library/think/Loader.php @@ -0,0 +1,677 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Loader +{ + /** + * @var array 实例数组 + */ + protected static $instance = []; + + /** + * @var array 类名映射 + */ + protected static $classMap = []; + + /** + * @var array 命名空间别名 + */ + protected static $namespaceAlias = []; + + /** + * @var array PSR-4 命名空间前缀长度映射 + */ + private static $prefixLengthsPsr4 = []; + + /** + * @var array PSR-4 的加载目录 + */ + private static $prefixDirsPsr4 = []; + + /** + * @var array PSR-4 加载失败的回退目录 + */ + private static $fallbackDirsPsr4 = []; + + /** + * @var array PSR-0 命名空间前缀映射 + */ + private static $prefixesPsr0 = []; + + /** + * @var array PSR-0 加载失败的回退目录 + */ + private static $fallbackDirsPsr0 = []; + + /** + * @var array 需要加载的文件 + */ + private static $files = []; + + /** + * 自动加载 + * @access public + * @param string $class 类名 + * @return bool + */ + public static function autoload($class) + { + // 检测命名空间别名 + if (!empty(self::$namespaceAlias)) { + $namespace = dirname($class); + if (isset(self::$namespaceAlias[$namespace])) { + $original = self::$namespaceAlias[$namespace] . '\\' . basename($class); + if (class_exists($original)) { + return class_alias($original, $class, false); + } + } + } + + if ($file = self::findFile($class)) { + // 非 Win 环境不严格区分大小写 + if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) { + __include_file($file); + return true; + } + } + + return false; + } + + /** + * 查找文件 + * @access private + * @param string $class 类名 + * @return bool|string + */ + private static function findFile($class) + { + // 类库映射 + if (!empty(self::$classMap[$class])) { + return self::$classMap[$class]; + } + + // 查找 PSR-4 + $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; + $first = $class[0]; + + if (isset(self::$prefixLengthsPsr4[$first])) { + foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach (self::$prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // 查找 PSR-4 fallback dirs + foreach (self::$fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr4)) { + return $file; + } + } + + // 查找 PSR-0 + if (false !== $pos = strrpos($class, '\\')) { + // namespace class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DS) . EXT; + } + + if (isset(self::$prefixesPsr0[$first])) { + foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // 查找 PSR-0 fallback dirs + foreach (self::$fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr0)) { + return $file; + } + } + + // 找不到则设置映射为 false 并返回 + return self::$classMap[$class] = false; + } + + /** + * 注册 classmap + * @access public + * @param string|array $class 类名 + * @param string $map 映射 + * @return void + */ + public static function addClassMap($class, $map = '') + { + if (is_array($class)) { + self::$classMap = array_merge(self::$classMap, $class); + } else { + self::$classMap[$class] = $map; + } + } + + /** + * 注册命名空间 + * @access public + * @param string|array $namespace 命名空间 + * @param string $path 路径 + * @return void + */ + public static function addNamespace($namespace, $path = '') + { + if (is_array($namespace)) { + foreach ($namespace as $prefix => $paths) { + self::addPsr4($prefix . '\\', rtrim($paths, DS), true); + } + } else { + self::addPsr4($namespace . '\\', rtrim($path, DS), true); + } + } + + /** + * 添加 PSR-0 命名空间 + * @access private + * @param array|string $prefix 空间前缀 + * @param array $paths 路径 + * @param bool $prepend 预先设置的优先级更高 + * @return void + */ + private static function addPsr0($prefix, $paths, $prepend = false) + { + if (!$prefix) { + self::$fallbackDirsPsr0 = $prepend ? + array_merge((array) $paths, self::$fallbackDirsPsr0) : + array_merge(self::$fallbackDirsPsr0, (array) $paths); + } else { + $first = $prefix[0]; + + if (!isset(self::$prefixesPsr0[$first][$prefix])) { + self::$prefixesPsr0[$first][$prefix] = (array) $paths; + } else { + self::$prefixesPsr0[$first][$prefix] = $prepend ? + array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) : + array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths); + } + } + } + + /** + * 添加 PSR-4 空间 + * @access private + * @param array|string $prefix 空间前缀 + * @param string $paths 路径 + * @param bool $prepend 预先设置的优先级更高 + * @return void + */ + private static function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + self::$fallbackDirsPsr4 = $prepend ? + array_merge((array) $paths, self::$fallbackDirsPsr4) : + array_merge(self::$fallbackDirsPsr4, (array) $paths); + + } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException( + "A non-empty PSR-4 prefix must end with a namespace separator." + ); + } + + self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + self::$prefixDirsPsr4[$prefix] = (array) $paths; + + } else { + self::$prefixDirsPsr4[$prefix] = $prepend ? + // Prepend directories for an already registered namespace. + array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) : + // Append directories for an already registered namespace. + array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths); + } + } + + /** + * 注册命名空间别名 + * @access public + * @param array|string $namespace 命名空间 + * @param string $original 源文件 + * @return void + */ + public static function addNamespaceAlias($namespace, $original = '') + { + if (is_array($namespace)) { + self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace); + } else { + self::$namespaceAlias[$namespace] = $original; + } + } + + /** + * 注册自动加载机制 + * @access public + * @param callable $autoload 自动加载处理方法 + * @return void + */ + public static function register($autoload = null) + { + // 注册系统自动加载 + spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + + // Composer 自动加载支持 + if (is_dir(VENDOR_PATH . 'composer')) { + if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) { + require VENDOR_PATH . 'composer' . DS . 'autoload_static.php'; + + $declaredClass = get_declared_classes(); + $composerClass = array_pop($declaredClass); + + foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { + if (property_exists($composerClass, $attr)) { + self::${$attr} = $composerClass::${$attr}; + } + } + } else { + self::registerComposerLoader(); + } + } + + // 注册命名空间定义 + self::addNamespace([ + 'think' => LIB_PATH . 'think' . DS, + 'behavior' => LIB_PATH . 'behavior' . DS, + 'traits' => LIB_PATH . 'traits' . DS, + ]); + + // 加载类库映射文件 + if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { + self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); + } + + self::loadComposerAutoloadFiles(); + + // 自动加载 extend 目录 + self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS); + } + + /** + * 注册 composer 自动加载 + * @access private + * @return void + */ + private static function registerComposerLoader() + { + if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) { + $map = require VENDOR_PATH . 'composer/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + self::addPsr0($namespace, $path); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) { + $map = require VENDOR_PATH . 'composer/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + self::addPsr4($namespace, $path); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) { + $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php'; + if ($classMap) { + self::addClassMap($classMap); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) { + self::$files = require VENDOR_PATH . 'composer/autoload_files.php'; + } + } + + // 加载composer autofile文件 + public static function loadComposerAutoloadFiles() + { + foreach (self::$files as $fileIdentifier => $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + __require_file($file); + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } + } + } + + /** + * 导入所需的类库 同 Java 的 Import 本函数有缓存功能 + * @access public + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return bool + */ + public static function import($class, $baseUrl = '', $ext = EXT) + { + static $_file = []; + $key = $class . $baseUrl; + $class = str_replace(['.', '#'], [DS, '.'], $class); + + if (isset($_file[$key])) { + return true; + } + + if (empty($baseUrl)) { + list($name, $class) = explode(DS, $class, 2); + + if (isset(self::$prefixDirsPsr4[$name . '\\'])) { + // 注册的命名空间 + $baseUrl = self::$prefixDirsPsr4[$name . '\\']; + } elseif ('@' == $name) { + // 加载当前模块应用类库 + $baseUrl = App::$modulePath; + } elseif (is_dir(EXTEND_PATH . $name)) { + $baseUrl = EXTEND_PATH . $name . DS; + } else { + // 加载其它模块的类库 + $baseUrl = APP_PATH . $name . DS; + } + } elseif (substr($baseUrl, -1) != DS) { + $baseUrl .= DS; + } + + // 如果类存在则导入类库文件 + if (is_array($baseUrl)) { + foreach ($baseUrl as $path) { + if (is_file($filename = $path . DS . $class . $ext)) { + break; + } + } + } else { + $filename = $baseUrl . $class . $ext; + } + + if (!empty($filename) && + is_file($filename) && + (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME)) + ) { + __include_file($filename); + $_file[$key] = true; + + return true; + } + + return false; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + $uid = $name . $layer; + + if (isset(self::$instance[$uid])) { + return self::$instance[$uid]; + } + + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $model = new $class(); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + + if (class_exists($class)) { + $model = new $class(); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + return self::$instance[$uid] = $model; + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return App::invokeClass($class); + } + + if ($empty) { + $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix); + + if (class_exists($emptyClass)) { + return new $emptyClass(Request::instance()); + } + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object|false + * @throws ClassNotFoundException + */ + public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: Config::get('default_validate'); + + if (empty($name)) { + return new Validate; + } + + $uid = $name . $layer; + if (isset(self::$instance[$uid])) { + return self::$instance[$uid]; + } + + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $validate = new $class; + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + + if (class_exists($class)) { + $validate = new $class; + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + return self::$instance[$uid] = $validate; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected static function getModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $module = Request::instance()->module(); + $class = $name; + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = Request::instance()->module(); + } + + $class = self::parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 数据库初始化 并取得数据库类实例 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Connection + */ + public static function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller(); + $class = self::controller($module, $layer, $appendSuffix); + + if ($class) { + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars); + } + + return false; + } + + /** + * 字符串命名风格转换 + * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格 + * @access public + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + public static function parseName($name, $type = 0, $ucfirst = true) + { + 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), "_")); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix 是否添加类名后缀 + * @return string + */ + public static function parseClass($module, $layer, $name, $appendSuffix = false) + { + + $array = explode('\\', str_replace(['/', '.'], '\\', $name)); + $class = self::parseName(array_pop($array), 1); + $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return App::$namespace . '\\' . + ($module ? $module . '\\' : '') . + $layer . '\\' . $path . $class; + } + + /** + * 初始化类的实例 + * @access public + * @return void + */ + public static function clearInstance() + { + self::$instance = []; + } +} + +// 作用范围隔离 + +/** + * include + * @param string $file 文件路径 + * @return mixed + */ +function __include_file($file) +{ + return include $file; +} + +/** + * require + * @param string $file 文件路径 + * @return mixed + */ +function __require_file($file) +{ + return require $file; +} diff --git a/source/thinkphp/library/think/Log.php b/source/thinkphp/library/think/Log.php new file mode 100644 index 0000000..c064306 --- /dev/null +++ b/source/thinkphp/library/think/Log.php @@ -0,0 +1,237 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +/** + * Class Log + * @package think + * + * @method void log($msg) static 记录一般日志 + * @method void error($msg) static 记录错误日志 + * @method void info($msg) static 记录一般信息日志 + * @method void sql($msg) static 记录 SQL 查询日志 + * @method void notice($msg) static 记录提示日志 + * @method void alert($msg) static 记录报警日志 + */ +class Log +{ + const LOG = 'log'; + const ERROR = 'error'; + const INFO = 'info'; + const SQL = 'sql'; + const NOTICE = 'notice'; + const ALERT = 'alert'; + const DEBUG = 'debug'; + + /** + * @var array 日志信息 + */ + protected static $log = []; + + /** + * @var array 配置参数 + */ + protected static $config = []; + + /** + * @var array 日志类型 + */ + protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; + + /** + * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动 + */ + protected static $driver; + + /** + * @var string 当前日志授权 key + */ + protected static $key; + + /** + * 日志初始化 + * @access public + * @param array $config 配置参数 + * @return void + */ + public static function init($config = []) + { + $type = isset($config['type']) ? $config['type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type); + + self::$config = $config; + unset($config['type']); + + if (class_exists($class)) { + self::$driver = new $class($config); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + // 记录初始化信息 + App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info'); + } + + /** + * 获取日志信息 + * @access public + * @param string $type 信息类型 + * @return array|string + */ + public static function getLog($type = '') + { + return $type ? self::$log[$type] : self::$log; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public static function record($msg, $type = 'log') + { + self::$log[$type][] = $msg; + + // 命令行下面日志写入改进 + IS_CLI && self::save(); + } + + /** + * 清空日志信息 + * @access public + * @return void + */ + public static function clear() + { + self::$log = []; + } + + /** + * 设置当前日志记录的授权 key + * @access public + * @param string $key 授权 key + * @return void + */ + public static function key($key) + { + self::$key = $key; + } + + /** + * 检查日志写入权限 + * @access public + * @param array $config 当前日志配置参数 + * @return bool + */ + public static function check($config) + { + return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']); + } + + /** + * 保存调试信息 + * @access public + * @return bool + */ + public static function save() + { + // 没有需要保存的记录则直接返回 + if (empty(self::$log)) { + return true; + } + + is_null(self::$driver) && self::init(Config::get('log')); + + // 检测日志写入权限 + if (!self::check(self::$config)) { + return false; + } + + if (empty(self::$config['level'])) { + // 获取全部日志 + $log = self::$log; + if (!App::$debug && isset($log['debug'])) { + unset($log['debug']); + } + } else { + // 记录允许级别 + $log = []; + foreach (self::$config['level'] as $level) { + if (isset(self::$log[$level])) { + $log[$level] = self::$log[$level]; + } + } + } + + if ($result = self::$driver->save($log, true)) { + self::$log = []; + } + + Hook::listen('log_write_done', $log); + + return $result; + } + + /** + * 实时写入日志信息 并支持行为 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @param bool $force 是否强制写入 + * @return bool + */ + public static function write($msg, $type = 'log', $force = false) + { + $log = self::$log; + + // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录 + if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) { + return false; + } + + // 封装日志信息 + $log[$type][] = $msg; + + // 监听 log_write + Hook::listen('log_write', $log); + + is_null(self::$driver) && self::init(Config::get('log')); + + // 写入日志 + if ($result = self::$driver->save($log, false)) { + self::$log = []; + } + + return $result; + } + + /** + * 静态方法调用 + * @access public + * @param string $method 调用方法 + * @param mixed $args 参数 + * @return void + */ + public static function __callStatic($method, $args) + { + if (in_array($method, self::$type)) { + array_push($args, $method); + + call_user_func_array('\\think\\Log::record', $args); + } + } + +} diff --git a/source/thinkphp/library/think/Model.php b/source/thinkphp/library/think/Model.php new file mode 100644 index 0000000..2dc27b4 --- /dev/null +++ b/source/thinkphp/library/think/Model.php @@ -0,0 +1,2350 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use BadMethodCallException; +use InvalidArgumentException; +use think\db\Query; +use think\exception\ValidateException; +use think\model\Collection as ModelCollection; +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\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; + +/** + * Class Model + * @package think + * @mixin Query + */ +abstract class Model implements \JsonSerializable, \ArrayAccess +{ + // 数据库查询对象池 + protected static $links = []; + // 数据库配置 + protected $connection = []; + // 父关联模型对象 + protected $parent; + // 数据库查询对象 + protected $query; + // 当前模型名称 + protected $name; + // 数据表名称 + protected $table; + // 当前类名称 + protected $class; + // 回调事件 + private static $event = []; + // 错误信息 + protected $error; + // 字段验证规则 + protected $validate; + // 数据表主键 复合主键使用数组定义 不设置则自动获取 + protected $pk; + // 数据表字段信息 留空则自动获取 + protected $field = []; + // 数据排除字段 + protected $except = []; + // 数据废弃字段 + protected $disuse = []; + // 只读字段 + protected $readonly = []; + // 显示属性 + protected $visible = []; + // 隐藏属性 + protected $hidden = []; + // 追加属性 + protected $append = []; + // 数据信息 + protected $data = []; + // 原始数据 + protected $origin = []; + // 关联模型 + protected $relation = []; + + // 保存自动完成列表 + protected $auto = []; + // 新增自动完成列表 + protected $insert = []; + // 更新自动完成列表 + protected $update = []; + // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + protected $autoWriteTimestamp; + // 创建时间字段 + protected $createTime = 'create_time'; + // 更新时间字段 + protected $updateTime = 'update_time'; + // 时间字段取出后的默认时间格式 + protected $dateFormat; + // 字段类型或者格式转换 + protected $type = []; + // 是否为更新数据 + protected $isUpdate = false; + // 是否使用Replace + protected $replace = false; + // 是否强制更新所有数据 + protected $force = false; + // 更新条件 + protected $updateWhere; + // 验证失败是否抛出异常 + protected $failException = false; + // 全局查询范围 + protected $useGlobalScope = true; + // 是否采用批量验证 + protected $batchValidate = false; + // 查询数据集对象 + protected $resultSetType; + // 关联自动写入 + protected $relationWrite; + + /** + * 初始化过的模型. + * + * @var array + */ + protected static $initialized = []; + + /** + * 是否从主库读取(主从分布式有效) + * @var array + */ + protected static $readMaster; + + /** + * 构造方法 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + if (is_object($data)) { + $this->data = get_object_vars($data); + } else { + $this->data = $data; + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + // 当前类名 + $this->class = get_called_class(); + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', $this->class); + $this->name = basename($name); + if (Config::get('class_suffix')) { + $suffix = basename(dirname($name)); + $this->name = substr($this->name, 0, -strlen($suffix)); + } + } + + if (is_null($this->autoWriteTimestamp)) { + // 自动写入时间戳 + $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp'); + } + + if (is_null($this->dateFormat)) { + // 设置时间戳格式 + $this->dateFormat = $this->getQuery()->getConfig('datetime_format'); + } + + if (is_null($this->resultSetType)) { + $this->resultSetType = $this->getQuery()->getConfig('resultset_type'); + } + // 执行初始化操作 + $this->initialize(); + } + + /** + * 是否从主库读取数据(主从分布有效) + * @access public + * @param bool $all 是否所有模型生效 + * @return $this + */ + public function readMaster($all = false) + { + $model = $all ? '*' : $this->class; + + static::$readMaster[$model] = true; + return $this; + } + + /** + * 创建模型的查询对象 + * @access protected + * @return Query + */ + protected function buildQuery() + { + // 合并数据库配置 + if (!empty($this->connection)) { + if (is_array($this->connection)) { + $connection = array_merge(Config::get('database'), $this->connection); + } else { + $connection = $this->connection; + } + } else { + $connection = []; + } + + $con = Db::connect($connection); + // 设置当前模型 确保查询返回模型对象 + $queryClass = $this->query ?: $con->getConfig('query'); + $query = new $queryClass($con, $this); + + if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) { + $query->master(true); + } + + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->setTable($this->table); + } else { + $query->name($this->name); + } + + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; + } + + /** + * 创建新的模型实例 + * @access public + * @param array|object $data 数据 + * @param bool $isUpdate 是否为更新 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance($data = [], $isUpdate = false, $where = null) + { + return (new static($data))->isUpdate($isUpdate, $where); + } + + /** + * 获取当前模型的查询对象 + * @access public + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function getQuery($buildNewQuery = false) + { + if ($buildNewQuery) { + return $this->buildQuery(); + } elseif (!isset(self::$links[$this->class])) { + // 创建模型查询对象 + self::$links[$this->class] = $this->buildQuery(); + } + + return self::$links[$this->class]; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool $useBaseQuery 是否调用全局查询范围 + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function db($useBaseQuery = true, $buildNewQuery = true) + { + $query = $this->getQuery($buildNewQuery); + + // 全局作用域 + if ($useBaseQuery && method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access protected + * @return void + */ + protected function initialize() + { + $class = get_class($this); + if (!isset(static::$initialized[$class])) { + static::$initialized[$class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + { + } + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent($model) + { + $this->parent = $model; + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据或者属性名 + * @param mixed $value 值 + * @return $this + */ + public function data($data, $value = null) + { + if (is_string($data)) { + $this->data[$data] = $value; + } else { + // 清空数据 + $this->data = []; + if (is_object($data)) { + $data = get_object_vars($data); + } + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } else { + $this->data = $data; + } + } + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData($name = null) + { + if (is_null($name)) { + return $this->data; + } elseif (array_key_exists($name, $this->data)) { + return $this->data[$name]; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); + } + } + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $auto; + return $this; + } + + /** + * 更新是否强制写入数据 而不做比较 + * @access public + * @param bool $force + * @return $this + */ + public function force($force = true) + { + $this->force = $force; + return $this; + } + + /** + * 修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setAttr($name, $value, $data = []) + { + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp($name); + } else { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data), $this->relation); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + return $this; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @return mixed + */ + public function getRelation($name = null) + { + if (is_null($name)) { + return $this->relation; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + return; + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @return $this + */ + public function setRelation($name, $value) + { + $this->relation[$name] = $value; + return $this; + } + + /** + * 自动写入时间戳 + * @access public + * @param string $name 时间戳字段 + * @return mixed + */ + protected function autoWriteTimestamp($name) + { + if (isset($this->type[$name])) { + $type = $this->type[$name]; + if (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + switch ($type) { + case 'datetime': + case 'date': + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(time(), $format); + break; + case 'timestamp': + case 'integer': + default: + $value = time(); + break; + } + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(time(), $this->dateFormat); + } else { + $value = $this->formatDateTime(time(), $this->dateFormat, true); + } + return $value; + } + + /** + * 时间日期字段格式化处理 + * @access public + * @param mixed $time 时间日期表达式 + * @param mixed $format 日期格式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($time, $format, $timestamp = false) + { + if (false !== strpos($format, '\\')) { + $time = new $format($time); + } elseif (!$timestamp && false !== $format) { + $time = date($format, $time); + } + return $time; + } + + /** + * 数据写入 类型转换 + * @access public + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($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, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $format = !empty($param) ? $param : $this->dateFormat; + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime($value, $format); + 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; + + } + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr($name) + { + try { + $notFound = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $notFound = true; + $value = null; + } + + // 检测属性获取器 + $method = 'get' . Loader::parseName($name, 1) . 'Attr'; + if (method_exists($this, $method)) { + $value = $this->$method($value, $this->data, $this->relation); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$name]); + } elseif (in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(strtotime($value), $this->dateFormat); + } else { + $value = $this->formatDateTime($value, $this->dateFormat); + } + } elseif ($notFound) { + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); + // 不存在该字段 获取关联数据 + $value = $this->getRelationData($modelRelation); + // 保存关联对象值 + $this->relation[$name] = $value; + } else { + throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); + } + } + return $value; + } + + /** + * 获取关联模型数据 + * @access public + * @param Relation $modelRelation 模型关联对象 + * @return mixed + * @throws BadMethodCallException + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) { + $value = $this->parent; + } else { + // 首先获取关联数据 + if (method_exists($modelRelation, 'getRelation')) { + $value = $modelRelation->getRelation(); + } else { + throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation'); + } + } + return $value; + } + + /** + * 数据读取 类型转换 + * @access public + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($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, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($value, $format); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(strtotime($value), $format); + } + 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 array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->append = $override ? $append : array_merge($this->append, $append); + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $relation 关联方法 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($relation, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + + $relation = Loader::parseName($relation, 1, false); + + // 获取关联数据 + 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); + } else { + $this->data[$key] = $model->getAttr($attr); + } + } + } + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->visible = $override ? $visible : array_merge($this->visible, $visible); + return $this; + } + + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + if ($visible) { + $array[] = $key; + } + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + return $array; + } + + /** + * 转换子模型对象 + * @access protected + * @param Model|ModelCollection $model + * @param $visible + * @param $hidden + * @param $key + * @return array + */ + protected function subToArray($model, $visible, $hidden, $key) + { + // 关联模型对象 + if (isset($visible[$key])) { + $model->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $model->hidden($hidden[$key]); + } + return $model->toArray(); + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray() + { + $item = []; + $visible = []; + $hidden = []; + + $data = array_merge($this->data, $this->relation); + + // 过滤属性 + if (!empty($this->visible)) { + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($data, array_flip($array)); + } elseif (!empty($this->hidden)) { + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($data, array_flip($array)); + } + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + $item[$key] = $this->subToArray($val, $visible, $hidden, $key); + } elseif (is_array($val) && reset($val) instanceof Model) { + // 关联模型数据集 + $arr = []; + foreach ($val as $k => $value) { + $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); + } + $item[$key] = $arr; + } else { + // 模型属性 + $item[$key] = $this->getAttr($key); + } + } + // 追加属性(必须定义获取器) + if (!empty($this->append)) { + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + + if (method_exists($modelRelation, 'getBindAttr')) { + $bindAttr = $modelRelation->getBindAttr(); + if ($bindAttr) { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + continue; + } + } + $item[$name] = $value; + } else { + $item[$name] = $this->getAttr($name); + } + } + } + } + return !empty($item) ? $item : []; + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } + + /** + * 转换当前模型数据集为数据集对象 + * @access public + * @param array|\think\Collection $collection 数据集 + * @return \think\Collection + */ + public function toCollection($collection) + { + if ($this->resultSetType) { + if ('collection' == $this->resultSetType) { + $collection = new ModelCollection($collection); + } elseif (false !== strpos($this->resultSetType, '\\')) { + $class = $this->resultSetType; + $collection = new $class($collection); + } + } + return $collection; + } + + /** + * 关联数据一起更新 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + $this->relationWrite = $relation; + return $this; + } + + /** + * 获取模型对象的主键 + * @access public + * @param string $name 模型名 + * @return mixed + */ + public function getPk($name = '') + { + if (!empty($name)) { + $table = $this->getQuery()->getTable($name); + return $this->getQuery()->getPk($table); + } elseif (empty($this->pk)) { + $this->pk = $this->getQuery()->getPk(); + } + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk($key) + { + $pk = $this->getPk(); + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + return false; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace($replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return integer|false + */ + public function save($data = [], $where = [], $sequence = null) + { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + + // 数据自动验证 + if (!empty($data)) { + if (!$this->validateData($data)) { + return false; + } + + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } + + if (!empty($where)) { + $this->isUpdate = true; + $this->updateWhere = $where; + } + + // 自动关联写入 + if (!empty($this->relationWrite)) { + $relation = []; + foreach ($this->relationWrite as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $relation[$key] = []; + foreach ($name as $val) { + if (isset($this->data[$val])) { + $relation[$key][$val] = $this->data[$val]; + unset($this->data[$val]); + } + } + } else { + $relation[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $relation[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $relation[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + $pk = $this->getPk(); + if ($this->isUpdate) { + // 自动更新 + $this->autoCompleteData($this->update); + + // 事件回调 + if (false === $this->trigger('before_update', $this)) { + return false; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } + return 0; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + + $array = []; + + foreach ((array) $pk as $key) { + if (isset($data[$key])) { + $array[$key] = $data[$key]; + unset($data[$key]); + } + } + + if (!empty($array)) { + $where = $array; + } + + // 检测字段 + $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update)); + + // 模型更新 + if (!empty($allowFields)) { + $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data); + } else { + $result = $this->getQuery()->where($where)->update($data); + } + + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } + + // 更新回调 + $this->trigger('after_update', $this); + + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间和更新时间 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($this->data[$this->createTime])) { + $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); + } + if ($this->updateTime && !isset($this->data[$this->updateTime])) { + $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } + } + + if (false === $this->trigger('before_insert', $this)) { + return false; + } + + // 检测字段 + $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert)); + if (!empty($allowFields)) { + $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence); + } else { + $result = $this->getQuery()->insert($this->data, $this->replace, false, $sequence); + } + + // 获取自动增长主键 + if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) { + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } + } + } + + // 关联写入 + if (isset($relation)) { + foreach ($relation as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + + // 标记为更新 + $this->isUpdate = true; + + // 新增回调 + $this->trigger('after_insert', $this); + } + // 写入回调 + $this->trigger('after_write', $this); + + // 重新记录原始数据 + $this->origin = $this->data; + + return $result; + } + + protected function checkAllowField($auto = []) + { + if (true === $this->field) { + $this->field = $this->getQuery()->getTableInfo('', 'fields'); + $field = $this->field; + } elseif (!empty($this->field)) { + $field = array_merge($this->field, $auto); + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + } elseif (!empty($this->except)) { + $fields = $this->getQuery()->getTableInfo('', 'fields'); + $field = array_diff($fields, (array) $this->except); + $this->field = $field; + } else { + $field = []; + } + + if ($this->disuse) { + // 废弃字段 + $field = array_diff($field, (array) $this->disuse); + } + return $field; + } + + protected function autoRelationUpdate($relation) + { + foreach ($relation as $name => $val) { + if ($val instanceof Model) { + $val->save(); + } else { + unset($this->data[$name]); + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->save($val); + } + } + } + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData() + { + if ($this->force) { + $data = $this->data; + } else { + $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; + }); + } + + if (!empty($this->readonly)) { + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + } + + return $data; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + // 更新条件 + $where = $this->getWhere(); + + $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime); + if (true !== $result) { + $this->data[$field] += $step; + } + + return $result; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + // 更新条件 + $where = $this->getWhere(); + $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime); + if (true !== $result) { + $this->data[$field] -= $step; + } + + return $result; + } + + /** + * 获取更新条件 + * @access protected + * @return mixed + */ + protected function getWhere() + { + // 删除条件 + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->data[$pk])) { + $where = [$pk => $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + * @throws \Exception + */ + public function saveAll($dataSet, $replace = true) + { + if ($this->validate) { + // 数据批量验证 + $validate = $this->validate; + foreach ($dataSet as $data) { + if (!$this->validateData($data, $validate)) { + return false; + } + } + } + + $result = []; + $db = $this->getQuery(); + $db->startTrans(); + try { + $pk = $this->getPk(); + if (is_string($pk) && $replace) { + $auto = true; + } + foreach ($dataSet as $key => $data) { + if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data, [], $this->field); + } else { + $result[$key] = self::create($data, $this->field); + } + } + $db->commit(); + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 设置允许写入的字段 + * @access public + * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段 + * @return $this + */ + public function allowField($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->field = $field; + return $this; + } + + /** + * 设置排除写入的字段 + * @access public + * @param string|array $field 排除允许写入的字段 + * @return $this + */ + public function except($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->except = $field; + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param mixed $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->readonly = $field; + return $this; + } + + /** + * 是否为更新数据 + * @access public + * @param bool $update + * @param mixed $where + * @return $this + */ + public function isUpdate($update = true, $where = null) + { + $this->isUpdate = $update; + if (!empty($where)) { + $this->updateWhere = $where; + } + return $this; + } + + /** + * 数据自动完成 + * @access public + * @param array $auto 要自动更新的字段列表 + * @return void + */ + protected function autoCompleteData($auto = []) + { + foreach ($auto as $field => $value) { + if (is_integer($field)) { + $field = $value; + $value = null; + } + + if (!isset($this->data[$field])) { + $default = null; + } else { + $default = $this->data[$field]; + } + + $this->setAttr($field, !is_null($value) ? $value : $default); + } + } + + /** + * 删除当前的记录 + * @access public + * @return integer + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + // 删除条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->getQuery()->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->delete(); + } + } + } + + $this->trigger('after_delete', $this); + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 设置自动完成的字段( 规则通过修改器定义) + * @access public + * @param array $fields 需要自动完成的字段 + * @return $this + */ + public function auto($fields) + { + $this->auto = $fields; + return $this; + } + + /** + * 设置字段验证 + * @access public + * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 + * @param array $msg 提示信息 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($rule = true, $msg = [], $batch = false) + { + if (is_array($rule)) { + $this->validate = [ + 'rule' => $rule, + 'msg' => $msg, + ]; + } else { + $this->validate = true === $rule ? $this->name : $rule; + } + $this->batchValidate = $batch; + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access public + * @param bool $fail 是否抛出异常 + * @return $this + */ + public function validateFailException($fail = true) + { + $this->failException = $fail; + return $this; + } + + /** + * 自动验证数据 + * @access protected + * @param array $data 验证数据 + * @param mixed $rule 验证规则 + * @param bool $batch 批量验证 + * @return bool + */ + protected function validateData($data, $rule = null, $batch = null) + { + $info = is_null($rule) ? $this->validate : $rule; + + if (!empty($info)) { + if (is_array($info)) { + $validate = Loader::validate(); + $validate->rule($info['rule']); + $validate->message($info['msg']); + } else { + $name = is_string($info) ? $info : $this->name; + if (strpos($name, '.')) { + list($name, $scene) = explode('.', $name); + } + $validate = Loader::validate($name); + if (!empty($scene)) { + $validate->scene($scene); + } + } + $batch = is_null($batch) ? $this->batchValidate : $batch; + + if (!$validate->batch($batch)->check($data)) { + $this->error = $validate->getError(); + if ($this->failException) { + throw new ValidateException($this->error); + } else { + return false; + } + } + $this->validate = null; + } + return true; + } + + /** + * 返回模型的错误信息 + * @access public + * @return string|array + */ + public function getError() + { + return $this->error; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 + * @return void + */ + public static function event($event, $callback, $override = false) + { + $class = get_called_class(); + if ($override) { + self::$event[$class][$event] = []; + } + self::$event[$class][$event][] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 传入参数(引用) + * @return bool + */ + protected function trigger($event, &$params) + { + if (isset(self::$event[$this->class][$event])) { + foreach (self::$event[$this->class][$event] as $callback) { + if (is_callable($callback)) { + $result = call_user_func_array($callback, [ & $params]); + if (false === $result) { + return false; + } + } + } + } + return true; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array|true $field 允许字段 + * @return $this + */ + public static function create($data = [], $field = null) + { + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } + $model->isUpdate(false)->save($data, []); + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 + * @return $this + */ + public static function update($data = [], $where = [], $field = null) + { + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } + $result = $model->isUpdate(true)->save($data, $where); + return $model; + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public static function get($data, $with = [], $cache = false) + { + if (is_null($data)) { + return; + } + + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + $query = static::parseQuery($data, $with, $cache); + return $query->find($data); + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static[]|false + * @throws exception\DbException + */ + public static function all($data = null, $with = [], $cache = false) + { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + $query = static::parseQuery($data, $with, $cache); + return $query->select($data); + } + + /** + * 分析查询表达式 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return Query + */ + protected static function parseQuery(&$data, $with, $cache) + { + $result = self::with($with)->cache($cache); + if (is_array($data) && key($data) !== 0) { + $result = $result->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $result]); + $data = null; + } elseif ($data instanceof Query) { + $result = $data->with($with)->cache($cache); + $data = null; + } + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @return integer 成功删除的记录数 + */ + public static function destroy($data) + { + $model = new static(); + $query = $model->db(); + if (empty($data) && 0 !== $data) { + return 0; + } elseif (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } + $resultSet = $query->select($data); + $count = 0; + if ($resultSet) { + foreach ($resultSet as $data) { + $result = $data->delete(); + $count += $result; + } + } + return $count; + } + + /** + * 命名范围 + * @access public + * @param string|array|\Closure $name 命名范围名称 逗号分隔 + * @internal mixed ...$params 参数调用 + * @return Query + */ + public static function scope($name) + { + $model = new static(); + $query = $model->db(); + $params = func_get_args(); + array_shift($params); + array_unshift($params, $query); + if ($name instanceof \Closure) { + call_user_func_array($name, $params); + } elseif (is_string($name)) { + $name = explode(',', $name); + } + if (is_array($name)) { + foreach ($name as $scope) { + $method = 'scope' . trim($scope); + if (method_exists($model, $method)) { + call_user_func_array([$model, $method], $params); + } + } + } + return $query; + } + + /** + * 设置是否使用全局查询范围 + * @param bool $use 是否启用全局查询范围 + * @access public + * @return Query + */ + public static function useGlobalScope($use) + { + $model = new static(); + return $model->db($use); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Relation|Query + */ + public static function has($relation, $operator = '>=', $count = 1, $id = '*') + { + $relation = (new static())->$relation(); + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); + } + return $relation->has($operator, $count, $id); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Relation|Query + */ + public static function hasWhere($relation, $where = [], $fields = null) + { + return (new static())->$relation()->hasWhere($where, $fields); + } + + /** + * 解析模型的完整命名空间 + * @access public + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (false === strpos($model, '\\')) { + $path = explode('\\', get_called_class()); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param string|array $relations 关联名 + * @return $this + */ + public function relationQuery($relations) + { + if (is_string($relations)) { + $relations = explode(',', $relations); + } + + 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, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $method = Loader::parseName($relation, 1, false); + $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); + } + return $this; + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @return Model + */ + public function eagerlyResult(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param string|array $relation 关联名 + * @return void + */ + public function relationCount(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = $this->$relation()->relationCount($result, $closure); + if (!isset($name)) { + $name = Loader::parseName($relation) . '_count'; + } + $result->setAttr($name, $count); + } + } + + /** + * 获取模型的默认外键名 + * @access public + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + return Loader::parseName($name) . '_id'; + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return HasOne + */ + public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasOne($this, $model, $foreignKey, $localKey, $joinType); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return BelongsTo + */ + public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey($model); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @return HasMany + */ + public function hasMany($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $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 当前模型主键 + * @return HasManyThrough + */ + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey($through); + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Loader::parseName(basename(str_replace('\\', '/', $model))); + $table = $table ?: Loader::parseName($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: get_class($this); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: get_class($this); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + public function __call($method, $args) + { + $query = $this->db(true, false); + if (method_exists($this, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + call_user_func_array([$this, $method], $args); + return $this; + } else { + return call_user_func_array([$query, $method], $args); + } + } + + public static function __callStatic($method, $args) + { + $model = new static(); + $query = $model->db(); + if (method_exists($model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + + call_user_func_array([$model, $method], $args); + return $query; + } else { + return call_user_func_array([$query, $method], $args); + } + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + try { + if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { + return true; + } else { + $this->getAttr($name); + return true; + } + } catch (InvalidArgumentException $e) { + return false; + } + + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->data[$name], $this->relation[$name]); + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + /** + * 模型事件快捷方法 + * @param $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + +} diff --git a/source/thinkphp/library/think/Paginator.php b/source/thinkphp/library/think/Paginator.php new file mode 100644 index 0000000..3655567 --- /dev/null +++ b/source/thinkphp/library/think/Paginator.php @@ -0,0 +1,409 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; + +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** @var bool 是否为简洁模式 */ + protected $simple = false; + + /** @var Collection 数据集 */ + protected $items; + + /** @var integer 当前页 */ + protected $currentPage; + + /** @var integer 最后一页 */ + protected $lastPage; + + /** @var integer|null 数据总数 */ + protected $total; + + /** @var integer 每页的数量 */ + protected $listRows; + + /** @var bool 是否有下一页 */ + protected $hasMore; + + /** @var array 一些配置 */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + /** @var mixed simple模式下的下个元素 */ + protected $nextItem; + + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $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); + if ($this->hasMore) { + $this->nextItem = $items->slice($this->listRows, 1); + } + $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; + } + + /** + * @param $items + * @param $listRows + * @param null $currentPage + * @param bool $simple + * @param null $total + * @param array $options + * @return Paginator + */ + public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + return new static($items, $listRows, $currentPage, $total, $simple, $options); + } + + protected function setCurrentPage($currentPage) + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @param $page + * @return string + */ + protected function url($page) + { + 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]', $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, null, '&'); + } + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage($varPage = 'page', $default = 1) + { + $page = (int) Request::instance()->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) { + return $page; + } + + return $default; + } + + /** + * 自动获取当前的path + * @return string + */ + public static function getCurrentPath() + { + return Request::instance()->baseUrl(); + } + + public function total() + { + if ($this->simple) { + throw new \DomainException('not support total'); + } + return $this->total; + } + + public function listRows() + { + return $this->listRows; + } + + public function currentPage() + { + return $this->currentPage; + } + + public function lastPage() + { + if ($this->simple) { + throw new \DomainException('not support last'); + } + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @return boolean + */ + public function hasPages() + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @param string|null $fragment + * @return $this + */ + public function fragment($fragment) + { + $this->options['fragment'] = $fragment; + return $this; + } + + /** + * 添加URL参数 + * + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (!is_array($key)) { + $queries = [$key => $value]; + } else { + $queries = $key; + } + + foreach ($queries as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @return string + */ + protected function buildFragment() + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @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 + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + if ($this->simple) { + return [ + 'per_page' => $this->listRows, + 'current_page' => $this->currentPage, + 'has_more' => $this->hasMore, + 'next_item' => $this->nextItem, + 'data' => $this->items->toArray(), + ]; + } else { + return [ + 'total' => $this->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) + { + $collection = $this->getCollection(); + + $result = call_user_func_array([$collection, $name], $arguments); + + if ($result === $collection) { + return $this; + } + + return $result; + } + +} diff --git a/source/thinkphp/library/think/Process.php b/source/thinkphp/library/think/Process.php new file mode 100644 index 0000000..6f3faa3 --- /dev/null +++ b/source/thinkphp/library/think/Process.php @@ -0,0 +1,1205 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\process\exception\Failed as ProcessFailedException; +use think\process\exception\Timeout as ProcessTimeoutException; +use think\process\pipes\Pipes; +use think\process\pipes\Unix as UnixPipes; +use think\process\pipes\Windows as WindowsPipes; +use think\process\Utils; + +class Process +{ + + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + + private $useFileHandles = false; + + /** @var Pipes */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * @var array + */ + 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 string $commandline 指令 + * @param string|null $cwd 工作目录 + * @param array|null $env 环境变量 + * @param string|null $input 输入 + * @param int|float|null $timeout 超时时间 + * @param array $options proc_open的选项 + * @throws \RuntimeException + * @api + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = []) + { + if (!function_exists('proc_open')) { + throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->input = $input; + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === DS; + $this->pty = false; + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); + $this->options = array_replace([ + 'suppress_errors' => true, + 'binary_pipes' => true, + ], $options); + } + + public function __destruct() + { + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * 运行指令 + * @param callback|null $callback + * @return int + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * 运行指令 + * @param callable|null $callback + * @return self + * @throws \RuntimeException + * @throws ProcessFailedException + */ + public function mustRun($callback = null) + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + if (0 !== $this->run($callback)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * 启动进程并写到 STDIN 输入后返回。 + * @param callable|null $callback + * @throws \RuntimeException + * @throws \RuntimeException + * @throws \LogicException + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + if ($this->outputDisabled && null !== $callback) { + throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if ('\\' === DS && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); + } + $commandline .= '"'; + + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); + + if (!is_resource($this->process)) { + throw new \RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * 重启进程 + * @param callable|null $callback + * @return Process + * @throws \RuntimeException + * @throws \RuntimeException + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * 等待要终止的进程 + * @param callable|null $callback + * @return int + */ + public function wait($callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DS || !$running; + $this->readPipes(true, $close); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * 获取PID + * @return int|null + * @throws \RuntimeException + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * 将一个 POSIX 信号发送到进程中 + * @param int $signal + * @return Process + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * 禁用从底层过程获取输出和错误输出。 + * @return Process + */ + 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; + } + + /** + * 开启从底层过程获取输出和错误输出。 + * @return Process + * @throws \RuntimeException + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * 输出是否禁用 + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * 获取当前的输出管道 + * @return string + * @throws \LogicException + * @throws \LogicException + * @api + */ + public function getOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + + return $this->stdout; + } + + /** + * 以增量方式返回的输出结果。 + * @return string + */ + public function getIncrementalOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空输出 + * @return Process + */ + public function clearOutput() + { + $this->stdout = ''; + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * 返回当前的错误输出的过程 (STDERR)。 + * @return string + */ + public function getErrorOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + + return $this->stderr; + } + + /** + * 以增量方式返回 errorOutput + * @return string + */ + public function getIncrementalErrorOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空 errorOutput + * @return Process + */ + public function clearErrorOutput() + { + $this->stderr = ''; + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * 获取退出码 + * @return null|int + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * 获取退出文本 + * @return null|string + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * 检查是否成功 + * @return bool + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * 是否未捕获的信号已被终止子进程 + * @return bool + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * 返回导致子进程终止其执行的数。 + * @return int + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * 检查子进程信号是否已停止 + * @return bool + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * 返回导致子进程停止其执行的数。 + * @return int + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * 检查是否正在运行 + * @return bool + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * 检查是否已开始 + * @return bool + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * 检查是否已终止 + * @return bool + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * 获取当前的状态 + * @return string + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * 终止进程 + */ + public function stop() + { + if ($this->isRunning()) { + if ('\\' === DS && !$this->isSigchildEnabled()) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode > 0) { + throw new \RuntimeException('Unable to kill the process'); + } + } else { + $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`); + foreach ($pids as $pid) { + if (is_numeric($pid)) { + posix_kill($pid, 9); + } + } + } + } + + $this->updateStatus(false); + if ($this->processInformation['running']) { + $this->close(); + } + + return $this->exitcode; + } + + /** + * 添加一行输出 + * @param string $line + */ + public function addOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stdout .= $line; + } + + /** + * 添加一行错误输出 + * @param string $line + */ + public function addErrorOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stderr .= $line; + } + + /** + * 获取被执行的指令 + * @return string + */ + public function getCommandLine() +{ + return $this->commandline; + } + + /** + * 设置指令 + * @param string $commandline + * @return self + */ + public function setCommandLine($commandline) +{ + $this->commandline = $commandline; + + return $this; + } + + /** + * 获取超时时间 + * @return float|null + */ + public function getTimeout() +{ + return $this->timeout; + } + + /** + * 获取idle超时时间 + * @return float|null + */ + public function getIdleTimeout() +{ + return $this->idleTimeout; + } + + /** + * 设置超时时间 + * @param int|float|null $timeout + * @return self + */ + public function setTimeout($timeout) +{ + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置idle超时时间 + * @param int|float|null $timeout + * @return self + */ + 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; + } + + /** + * 设置TTY + * @param bool $tty + * @return self + */ + public function setTty($tty) +{ + if ('\\' === DS && $tty) { + throw new \RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { + throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * 检查是否是tty模式 + * @return bool + */ + public function isTty() +{ + return $this->tty; + } + + /** + * 设置pty模式 + * @param bool $bool + * @return self + */ + public function setPty($bool) +{ + $this->pty = (bool) $bool; + + return $this; + } + + /** + * 是否是pty模式 + * @return bool + */ + public function isPty() +{ + return $this->pty; + } + + /** + * 获取工作目录 + * @return string|null + */ + public function getWorkingDirectory() +{ + if (null === $this->cwd) { + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * 设置工作目录 + * @param string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) +{ + $this->cwd = $cwd; + + return $this; + } + + /** + * 获取环境变量 + * @return array + */ + public function getEnv() +{ + return $this->env; + } + + /** + * 设置环境变量 + * @param array $env + * @return self + */ + public function setEnv(array $env) +{ + $env = array_filter($env, function ($value) { + return !is_array($value); + }); + + $this->env = []; + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + + return $this; + } + + /** + * 获取输入 + * @return null|string + */ + public function getInput() +{ + return $this->input; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) +{ + if ($this->isRunning()) { + throw new \LogicException('Input can not be set while the process is running.'); + } + + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 获取proc_open的选项 + * @return array + */ + public function getOptions() +{ + return $this->options; + } + + /** + * 设置proc_open的选项 + * @param array $options + * @return self + */ + public function setOptions(array $options) +{ + $this->options = $options; + + return $this; + } + + /** + * 是否兼容windows + * @return bool + */ + public function getEnhanceWindowsCompatibility() +{ + return $this->enhanceWindowsCompatibility; + } + + /** + * 设置是否兼容windows + * @param bool $enhance + * @return self + */ + public function setEnhanceWindowsCompatibility($enhance) +{ + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 返回是否 sigchild 兼容模式激活 + * @return bool + */ + public function getEnhanceSigchildCompatibility() +{ + return $this->enhanceSigchildCompatibility; + } + + /** + * 激活 sigchild 兼容性模式。 + * @param bool $enhance + * @return self + */ + public function setEnhanceSigchildCompatibility($enhance) +{ + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 是否超时 + */ + public function checkTimeout() +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE); + } + } + + /** + * 是否支持pty + * @return bool + */ + public static function isPtySupported() +{ + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === DS) { + return $result = false; + } + + $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes); + if (is_resource($proc)) { + proc_close($proc); + + return $result = true; + } + + return $result = false; + } + + /** + * 创建所需的 proc_open 的描述符 + * @return array + */ + private function getDescriptors() +{ + if ('\\' === DS) { + $this->processPipes = WindowsPipes::create($this, $this->input); + } else { + $this->processPipes = UnixPipes::create($this, $this->input); + } + $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + + $descriptors = array_merge($descriptors, [['pipe', 'w']]); + + $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * 建立 wait () 使用的回调。 + * @param callable|null $callback + * @return callable + */ + protected function buildCallback($callback) +{ + $out = self::OUT; + $callback = function ($type, $data) use ($callback, $out) { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * 更新状态 + * @param bool $blocking + */ + protected function updateStatus($blocking) +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + + $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true); + + if (!$this->processInformation['running']) { + $this->close(); + } + } + + /** + * 是否开启 '--enable-sigchild' + * @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 = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * 验证是否超时 + * @param int|float|null $timeout + * @return float|null + */ + private function validateTimeout($timeout) +{ + $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; + } + + /** + * 读取pipes + * @param bool $blocking + * @param bool $close + */ + private function readPipes($blocking, $close) +{ + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } + } + } + + /** + * 捕获退出码 + */ + private function captureExitCode() +{ + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + /** + * 关闭资源 + * @return int 退出码 + */ + private function close() +{ + $this->processPipes->close(); + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } else { + $exitcode = -1; + } + + $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] + && 0 < $this->processInformation['termsig'] + ) { + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + return $this->exitcode; + } + + /** + * 重置数据 + */ + private function resetProcessData() +{ + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * 将一个 POSIX 信号发送到进程中。 + * @param int $signal + * @param bool $throwException + * @return bool + */ + private function doSignal($signal, $throwException) +{ + if (!$this->isRunning()) { + if ($throwException) { + throw new \LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ($this->isSigchildEnabled()) { + if ($throwException) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + return false; + } + + if (true !== @proc_terminate($this->process, $signal)) { + if ($throwException) { + throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + + $this->latestSignal = $signal; + + return true; + } + + /** + * 确保进程已经开启 + * @param string $functionName + */ + private function requireProcessIsStarted($functionName) +{ + if (!$this->isStarted()) { + throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * 确保进程已经终止 + * @param string $functionName + */ + private function requireProcessIsTerminated($functionName) +{ + if (!$this->isTerminated()) { + throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } +} diff --git a/source/thinkphp/library/think/Request.php b/source/thinkphp/library/think/Request.php new file mode 100644 index 0000000..5997a76 --- /dev/null +++ b/source/thinkphp/library/think/Request.php @@ -0,0 +1,1690 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Request +{ + /** + * @var object 对象实例 + */ + protected static $instance; + + protected $method; + /** + * @var string 域名(含协议和端口) + */ + protected $domain; + + /** + * @var string URL地址 + */ + protected $url; + + /** + * @var string 基础URL + */ + protected $baseUrl; + + /** + * @var string 当前执行的文件 + */ + protected $baseFile; + + /** + * @var string 访问的ROOT地址 + */ + protected $root; + + /** + * @var string pathinfo + */ + protected $pathinfo; + + /** + * @var string pathinfo(不含后缀) + */ + protected $path; + + /** + * @var array 当前路由信息 + */ + protected $routeInfo = []; + + /** + * @var array 环境变量 + */ + protected $env; + + /** + * @var array 当前调度信息 + */ + protected $dispatch = []; + protected $module; + protected $controller; + protected $action; + // 当前语言集 + protected $langset; + + /** + * @var array 请求参数 + */ + protected $param = []; + protected $get = []; + protected $post = []; + protected $request = []; + protected $route = []; + protected $put; + protected $session = []; + protected $file = []; + protected $cookie = []; + protected $server = []; + 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,*/*', + ]; + + protected $content; + + // 全局过滤规则 + protected $filter; + // Hook扩展方法 + protected static $hook = []; + // 绑定的属性 + protected $bind = []; + // php://input + protected $input; + // 请求缓存 + protected $cache; + // 缓存是否检查 + protected $isCheckCache; + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 构造函数 + * @access protected + * @param array $options 参数 + */ + protected function __construct($options = []) + { + foreach ($options as $name => $item) { + if (property_exists($this, $name)) { + $this->$name = $item; + } + } + if (is_null($this->filter)) { + $this->filter = Config::get('default_filter'); + } + + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public function __call($method, $args) + { + if (array_key_exists($method, self::$hook)) { + array_unshift($args, $this); + return call_user_func_array(self::$hook[$method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + + /** + * Hook 方法注入 + * @access public + * @param string|array $method 方法名 + * @param mixed $callback callable + * @return void + */ + public static function hook($method, $callback = null) + { + if (is_array($method)) { + self::$hook = array_merge(self::$hook, $method); + } else { + self::$hook[$method] = $callback; + } + } + + /** + * 初始化 + * @access public + * @param array $options 参数 + * @return \think\Request + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) { + self::$instance = new static($options); + } + return self::$instance; + } + + /** + * 销毁当前请求对象 + * @access public + * @return void + */ + public static function destroy() + { + if (!is_null(self::$instance)) { + self::$instance = null; + } + } + + /** + * 创建一个URL请求 + * @access public + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content + * @return \think\Request + */ + public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + { + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + $info = parse_url($uri); + if (isset($info['host'])) { + $server['SERVER_NAME'] = $info['host']; + $server['HTTP_HOST'] = $info['host']; + } + if (isset($info['scheme'])) { + if ('https' === $info['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + if (isset($info['port'])) { + $server['SERVER_PORT'] = $info['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; + } + if (isset($info['user'])) { + $server['PHP_AUTH_USER'] = $info['user']; + } + if (isset($info['pass'])) { + $server['PHP_AUTH_PW'] = $info['pass']; + } + if (!isset($info['path'])) { + $info['path'] = '/'; + } + $options = []; + $options[strtolower($method)] = $params; + $queryString = ''; + if (isset($info['query'])) { + parse_str(html_entity_decode($info['query']), $query); + if (!empty($params)) { + $params = array_replace($query, $params); + $queryString = http_build_query($params, '', '&'); + } else { + $params = $query; + $queryString = $info['query']; + } + } elseif (!empty($params)) { + $queryString = http_build_query($params, '', '&'); + } + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); + $server['QUERY_STRING'] = $queryString; + $options['cookie'] = $cookie; + $options['param'] = $params; + $options['file'] = $files; + $options['server'] = $server; + $options['url'] = $server['REQUEST_URI']; + $options['baseUrl'] = $info['path']; + $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); + $options['method'] = $server['REQUEST_METHOD']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; + $options['content'] = $content; + self::$instance = new self($options); + return self::$instance; + } + + /** + * 设置或获取当前包含协议的域名 + * @access public + * @param string $domain 域名 + * @return string + */ + public function domain($domain = null) + { + if (!is_null($domain)) { + $this->domain = $domain; + return $this; + } elseif (!$this->domain) { + $this->domain = $this->scheme() . '://' . $this->host(); + } + return $this->domain; + } + + /** + * 设置或获取当前完整URL 包括QUERY_STRING + * @access public + * @param string|true $url URL地址 true 带域名获取 + * @return string + */ + public function url($url = null) + { + if (!is_null($url) && true !== $url) { + $this->url = $url; + return $this; + } elseif (!$this->url) { + if (IS_CLI) { + $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { + $this->url = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $this->url = $_SERVER['REQUEST_URI']; + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { + $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); + } else { + $this->url = ''; + } + } + return true === $url ? $this->domain() . $this->url : $this->url; + } + + /** + * 设置或获取当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return string + */ + public function baseUrl($url = null) + { + if (!is_null($url) && true !== $url) { + $this->baseUrl = $url; + return $this; + } elseif (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 设置或获取当前执行的文件 SCRIPT_NAME + * @access public + * @param string $file 当前执行的文件 + * @return string + */ + public function baseFile($file = null) + { + if (!is_null($file) && true !== $file) { + $this->baseFile = $file; + return $this; + } elseif (!$this->baseFile) { + $url = ''; + if (!IS_CLI) { + $script_name = basename($_SERVER['SCRIPT_FILENAME']); + if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['SCRIPT_NAME']; + } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { + $url = $_SERVER['PHP_SELF']; + } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['ORIG_SCRIPT_NAME']; + } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { + $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; + } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { + $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); + } + } + $this->baseFile = $url; + } + return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置或获取URL访问根地址 + * @access public + * @param string $url URL地址 + * @return string + */ + public function root($url = null) + { + if (!is_null($url) && true !== $url) { + $this->root = $url; + return $this; + } elseif (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + return true === $url ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo() + { + if (is_null($this->pathinfo)) { + if (isset($_GET[Config::get('var_pathinfo')])) { + // 判断URL里面是否有兼容模式参数 + $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; + unset($_GET[Config::get('var_pathinfo')]); + } elseif (IS_CLI) { + // CLI模式下 index.php module/controller/action/params/... + $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } + + // 分析PATHINFO信息 + if (!isset($_SERVER['PATH_INFO'])) { + foreach (Config::get('pathinfo_fetch') as $type) { + if (!empty($_SERVER[$type])) { + $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? + substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + break; + } + } + } + $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); + } + return $this->pathinfo; + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access public + * @return string + */ + public function path() + { + if (is_null($this->path)) { + $suffix = Config::get('url_html_suffix'); + $pathinfo = $this->pathinfo(); + if (false === $suffix) { + // 禁止伪静态访问 + $this->path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); + } + } + return $this->path; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext() + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time($float = false) + { + return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; + } + + /** + * 当前请求的资源类型 + * @access public + * @return false|string + */ + public function type() + { + $accept = $this->server('HTTP_ACCEPT'); + if (empty($accept)) { + return false; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + return false; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = '') + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 当前的请求类型 + * @access public + * @param bool $method true 获取原始请求类型 + * @return string + */ + public function method($method = false) + { + if (true === $method) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($_POST[Config::get('var_method')])) { + $method = strtoupper($_POST[Config::get('var_method')]); + if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) { + $this->method = $method; + $this->{$this->method}($_POST); + } else { + $this->method = 'POST'; + } + unset($_POST[Config::get('var_method')]); + } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + $this->method = strtoupper($_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() + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost() + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut() + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete() + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead() + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch() + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions() + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli() + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi() + { + 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 (true === $name) { + // 获取包含文件上传信息的数组 + $file = $this->file(); + $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + return $this->input($data, '', $default, $filter); + } + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置获取路由参数 + * @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)) { + $this->param = []; + $this->mergeParam = false; + return $this->route = array_merge($this->route, $name); + } + 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 (empty($this->get)) { + $this->get = $_GET; + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->get = array_merge($this->get, $name); + } + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 设置获取POST参数 + * @access public + * @param string $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (empty($this->post)) { + $content = $this->input; + if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { + $this->post = (array) json_decode($content, true); + } else { + $this->post = $_POST; + } + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->post = array_merge($this->post, $name); + } + 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_null($this->put)) { + $content = $this->input; + if (false !== strpos($this->contentType(), 'application/json')) { + $this->put = (array) json_decode($content, true); + } else { + parse_str($content, $this->put); + } + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); + } + + return $this->input($this->put, $name, $default, $filter); + } + + /** + * 设置获取DELETE参数 + * @access public + * @param string|array $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 string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @param string $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (empty($this->request)) { + $this->request = $_REQUEST; + } + if (is_array($name)) { + $this->param = []; + $this->mergeParam = false; + return $this->request = array_merge($this->request, $name); + } + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取session数据 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function session($name = '', $default = null, $filter = '') + { + if (empty($this->session)) { + $this->session = Session::get(); + } + if (is_array($name)) { + return $this->session = array_merge($this->session, $name); + } + return $this->input($this->session, $name, $default, $filter); + } + + /** + * 获取cookie参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie($name = '', $default = null, $filter = '') + { + if (empty($this->cookie)) { + $this->cookie = Cookie::get(); + } + if (is_array($name)) { + return $this->cookie = array_merge($this->cookie, $name); + } elseif (!empty($name)) { + $data = Cookie::has($name) ? Cookie::get($name) : $default; + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function server($name = '', $default = null, $filter = '') + { + if (empty($this->server)) { + $this->server = $_SERVER; + } + if (is_array($name)) { + return $this->server = array_merge($this->server, $name); + } + return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 获取上传的文件信息 + * @access public + * @param string|array $name 名称 + * @return null|array|\think\File + */ + public function file($name = '') + { + if (empty($this->file)) { + $this->file = isset($_FILES) ? $_FILES : []; + } + if (is_array($name)) { + return $this->file = array_merge($this->file, $name); + } + $files = $this->file; + if (!empty($files)) { + // 处理上传文件 + $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 (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { + continue; + } + $temp['key'] = $key; + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { + continue; + } + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + } + if (strpos($name, '.')) { + list($name, $sub) = explode('.', $name); + } + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + return; + } + + /** + * 获取环境变量 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function env($name = '', $default = null, $filter = '') + { + if (empty($this->env)) { + $this->env = $_ENV; + } + if (is_array($name)) { + return $this->env = array_merge($this->env, $name); + } + return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 设置或者获取当前的Header + * @access public + * @param string|array $name header名称 + * @param string $default 默认值 + * @return string + */ + public function header($name = '', $default = null) + { + if (empty($this->header)) { + $header = []; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->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']; + } + } + $this->header = array_change_key_case($header); + } + if (is_array($name)) { + return $this->header = array_merge($this->header, $name); + } + if ('' === $name) { + return $this->header; + } + $name = str_replace('_', '-', strtolower($name)); + return isset($this->header[$name]) ? $this->header[$name] : $default; + } + + /** + * 获取变量 支持过滤和默认值 + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input($data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + list($name, $type) = explode('/', $name); + } else { + $type = 's'; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + // 无输入数据,返回默认值 + return $default; + } + } + if (is_object($data)) { + return $data; + } + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } else { + $this->filter = $filter; + } + } + + protected function getFilter($filter, $default) + { + 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; + } + + /** + * 递归过滤给定的值 + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + private 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 (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 $this->filterExp($value); + } + + /** + * 过滤表单中的表达式 + * @param string $value + * @return void + */ + public function filterExp(&$value) + { + // 过滤查询特殊字符 + if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOT EXISTS|NOTEXISTS|EXISTS|NOT NULL|NOTNULL|NULL|BETWEEN TIME|NOT BETWEEN TIME|NOTBETWEEN TIME|NOTIN|NOT IN|IN)$/i', $value)) { + $value .= ' '; + } + // TODO 其他安全过滤 + } + + /** + * 强制类型转换 + * @param string $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, $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': + default: + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + } + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return mixed + */ + public function has($name, $type = 'param', $checkEmpty = false) + { + if (empty($this->$type)) { + $param = $this->$type(); + } else { + $param = $this->$type; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function only($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + $item = []; + foreach ($name as $key) { + if (isset($param[$key])) { + $item[$key] = $param[$key]; + } + } + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl() + { + $server = array_merge($_SERVER, $this->server); + if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { + return true; + } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { + return true; + } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { + return true; + } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { + return true; + } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) { + return true; + } + return false; + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax($ajax = false) + { + $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); + $result = ('xmlhttprequest' == $value) ? true : false; + if (true === $ajax) { + return $result; + } else { + $result = $this->param(Config::get('var_ajax')) ? true : $result; + $this->mergeParam = false; + return $result; + } + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax($pjax = false) + { + $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + if (true === $pjax) { + return $result; + } else { + $result = $this->param(Config::get('var_pjax')) ? true : $result; + $this->mergeParam = false; + return $result; + } + } + + /** + * 获取客户端IP地址 + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @return mixed + */ + public function ip($type = 0, $adv = true) + { + $type = $type ? 1 : 0; + static $ip = null; + if (null !== $ip) { + return $ip[$type]; + } + + $httpAgentIp = Config::get('http_agent_ip'); + + if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { + $ip = $_SERVER[$httpAgentIp]; + } elseif ($adv) { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $pos = array_search('unknown', $arr); + if (false !== $pos) { + unset($arr[$pos]); + } + $ip = trim(current($arr)); + } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + // IP地址合法验证 + $long = sprintf("%u", ip2long($ip)); + $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; + return $ip[$type]; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile() + { + if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { + return true; + } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { + return true; + } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { + return true; + } elseif (isset($_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', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } else { + return false; + } + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme() + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query() + { + return $this->server('QUERY_STRING'); + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host($strict = false) + { + if (isset($_SERVER['HTTP_X_REAL_HOST'])) { + $host = $_SERVER['HTTP_X_REAL_HOST']; + } else { + $host = $this->server('HTTP_HOST'); + } + + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return integer + */ + public function port() + { + return $this->server('SERVER_PORT'); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return integer + */ + public function protocol() + { + return $this->server('SERVER_PROTOCOL'); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return integer + */ + public function remotePort() + { + return $this->server('REMOTE_PORT'); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + return ''; + } + + /** + * 获取当前请求的路由信息 + * @access public + * @param array $route 路由名称 + * @return array + */ + public function routeInfo($route = []) + { + if (!empty($route)) { + $this->routeInfo = $route; + } else { + return $this->routeInfo; + } + } + + /** + * 设置或者获取当前请求的调度信息 + * @access public + * @param array $dispatch 调度信息 + * @return array + */ + public function dispatch($dispatch = null) + { + if (!is_null($dispatch)) { + $this->dispatch = $dispatch; + } + return $this->dispatch; + } + + /** + * 设置或者获取当前的模块名 + * @access public + * @param string $module 模块名 + * @return string|Request + */ + public function module($module = null) + { + if (!is_null($module)) { + $this->module = $module; + return $this; + } else { + return $this->module ?: ''; + } + } + + /** + * 设置或者获取当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return string|Request + */ + public function controller($controller = null) + { + if (!is_null($controller)) { + $this->controller = $controller; + return $this; + } else { + return $this->controller ?: ''; + } + } + + /** + * 设置或者获取当前的操作名 + * @access public + * @param string $action 操作名 + * @return string|Request + */ + public function action($action = null) + { + if (!is_null($action) && !is_bool($action)) { + $this->action = $action; + return $this; + } else { + $name = $this->action ?: ''; + return true === $action ? $name : strtolower($name); + } + } + + /** + * 设置或者获取当前的语言 + * @access public + * @param string $lang 语言名 + * @return string|Request + */ + public function langset($lang = null) + { + if (!is_null($lang)) { + $this->langset = $lang; + return $this; + } else { + return $this->langset ?: ''; + } + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent() + { + if (is_null($this->content)) { + $this->content = $this->input; + } + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function token($name = '__token__', $type = 'md5') + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); + if ($this->isAjax()) { + header($name . ': ' . $token); + } + Session::set($name, $token); + return $token; + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function cache($key, $expire = null, $except = [], $tag = null) + { + if (!is_array($except)) { + $tag = $except; + $except = []; + } + + if (false !== $key && $this->isGet() && !$this->isCheckCache) { + // 标记请求缓存检查 + $this->isCheckCache = true; + if (false === $expire) { + // 关闭当前缓存 + return; + } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; + } + } + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); + } else { + return; + } + } + if (isset($fun)) { + $key = $fun($key); + } + + if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { + // 读取缓存 + $response = Response::create()->code(304); + throw new \think\exception\HttpResponseException($response); + } elseif (Cache::has($key)) { + list($content, $header) = Cache::get($key); + $response = Response::create($content)->header($header); + throw new \think\exception\HttpResponseException($response); + } else { + $this->cache = [$key, $expire, $tag]; + } + } + } + + /** + * 读取请求缓存设置 + * @access public + * @return array + */ + public function getCache() + { + return $this->cache; + } + + /** + * 设置当前请求绑定的对象实例 + * @access public + * @param string|array $name 绑定的对象标识 + * @param mixed $obj 绑定的对象实例 + * @return mixed + */ + public function bind($name, $obj = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $obj; + } + } + + public function __set($name, $value) + { + $this->bind[$name] = $value; + } + + public function __get($name) + { + return isset($this->bind[$name]) ? $this->bind[$name] : null; + } + + public function __isset($name) + { + return isset($this->bind[$name]); + } +} diff --git a/source/thinkphp/library/think/Response.php b/source/thinkphp/library/think/Response.php new file mode 100644 index 0000000..c5c1520 --- /dev/null +++ b/source/thinkphp/library/think/Response.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\response\Json as JsonResponse; +use think\response\Jsonp as JsonpResponse; +use think\response\Redirect as RedirectResponse; +use think\response\View as ViewResponse; +use think\response\Xml as XmlResponse; + +class Response +{ + // 原始数据 + protected $data; + + // 当前的contentType + protected $contentType = 'text/html'; + + // 字符集 + protected $charset = 'utf-8'; + + //状态 + protected $code = 200; + + // 输出参数 + protected $options = []; + // header参数 + protected $header = []; + + protected $content = null; + + /** + * 构造函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 + */ + public function __construct($data = '', $code = 200, array $header = [], $options = []) + { + $this->data($data); + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->contentType($this->contentType, $this->charset); + $this->header = array_merge($this->header, $header); + $this->code = $code; + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code + * @param array $header + * @param array $options 输出参数 + * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse + */ + public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + if (class_exists($class)) { + $response = new $class($data, $code, $header, $options); + } else { + $response = new static($data, $code, $header, $options); + } + + return $response; + } + + /** + * 发送数据到客户端 + * @access public + * @return mixed + * @throws \InvalidArgumentException + */ + public function send() + { + // 监听response_send + Hook::listen('response_send', $this); + + // 处理输出数据 + $data = $this->getContent(); + + // Trace调试注入 + if (Env::get('app_trace', Config::get('app_trace'))) { + Debug::inject($this, $data); + } + + if (200 == $this->code) { + $cache = Request::instance()->getCache(); + if ($cache) { + $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; + $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; + Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); + } + } + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + if (is_null($val)) { + header($name); + } else { + header($name . ':' . $val); + } + } + } + + echo $data; + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + + // 监听response_end + Hook::listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + Session::flush(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options($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 string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($name, $value = null) + { + if (is_array($name)) { + $this->header = array_merge($this->header, $name); + } else { + $this->header[$name] = $value; + } + return $this; + } + + /** + * 设置页面输出内容 + * @param $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状态 + * @param integer $code 状态码 + * @return $this + */ + public function code($code) + { + $this->code = $code; + return $this; + } + + /** + * LastModified + * @param string $time + * @return $this + */ + public function lastModified($time) + { + $this->header['Last-Modified'] = $time; + return $this; + } + + /** + * Expires + * @param string $time + * @return $this + */ + public function expires($time) + { + $this->header['Expires'] = $time; + return $this; + } + + /** + * ETag + * @param string $eTag + * @return $this + */ + public function eTag($eTag) + { + $this->header['ETag'] = $eTag; + return $this; + } + + /** + * 页面缓存控制 + * @param string $cache 状态码 + * @return $this + */ + public function cacheControl($cache) + { + $this->header['Cache-control'] = $cache; + return $this; + } + + /** + * 页面输出类型 + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType($contentType, $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + return $this; + } + + /** + * 获取头部信息 + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader($name = '') + { + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } else { + return $this->header; + } + } + + /** + * 获取原始数据 + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @return mixed + */ + public function getContent() + { + 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; + } + + /** + * 获取状态码 + * @return integer + */ + public function getCode() + { + return $this->code; + } +} diff --git a/source/thinkphp/library/think/Route.php b/source/thinkphp/library/think/Route.php new file mode 100644 index 0000000..ab53aa2 --- /dev/null +++ b/source/thinkphp/library/think/Route.php @@ -0,0 +1,1645 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\HttpException; + +class Route +{ + // 路由规则 + private static $rules = [ + 'get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], + '*' => [], + 'alias' => [], + 'domain' => [], + 'pattern' => [], + 'name' => [], + ]; + + // REST路由操作方法定义 + private static $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '/:id/edit', 'edit'], + 'read' => ['get', '/:id', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/:id', 'update'], + 'delete' => ['delete', '/:id', 'delete'], + ]; + + // 不同请求类型的方法前缀 + private static $methodPrefix = [ + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', + ]; + + // 子域名 + private static $subDomain = ''; + // 域名绑定 + private static $bind = []; + // 当前分组信息 + private static $group = []; + // 当前子域名绑定 + private static $domainBind; + private static $domainRule; + // 当前域名 + private static $domain; + // 当前路由执行过程中的参数 + private static $option = []; + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return void + */ + public static function pattern($name = null, $rule = '') + { + if (is_array($name)) { + self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); + } else { + self::$rules['pattern'][$name] = $rule; + } + } + + /** + * 注册子域名部署规则 + * @access public + * @param string|array $domain 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function domain($domain, $rule = '', $option = [], $pattern = []) + { + if (is_array($domain)) { + foreach ($domain as $key => $item) { + self::domain($key, $item, $option, $pattern); + } + } elseif ($rule instanceof \Closure) { + // 执行闭包 + self::setDomain($domain); + call_user_func_array($rule, []); + self::setDomain(null); + } elseif (is_array($rule)) { + self::setDomain($domain); + self::group('', function () use ($rule) { + // 动态注册域名的路由规则 + self::registerRules($rule); + }, $option, $pattern); + self::setDomain(null); + } else { + self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; + } + } + + private static function setDomain($domain) + { + self::$domain = $domain; + } + + /** + * 设置路由绑定 + * @access public + * @param mixed $bind 绑定信息 + * @param string $type 绑定类型 默认为module 支持 namespace class controller + * @return mixed + */ + public static function bind($bind, $type = 'module') + { + self::$bind = ['type' => $type, $type => $bind]; + } + + /** + * 设置或者获取路由标识 + * @access public + * @param string|array $name 路由命名标识 数组表示批量设置 + * @param array $value 路由地址及变量信息 + * @return array + */ + public static function name($name = '', $value = null) + { + if (is_array($name)) { + return self::$rules['name'] = $name; + } elseif ('' === $name) { + return self::$rules['name']; + } elseif (!is_null($value)) { + self::$rules['name'][strtolower($name)][] = $value; + } else { + $name = strtolower($name); + return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; + } + } + + /** + * 读取路由绑定 + * @access public + * @param string $type 绑定类型 + * @return mixed + */ + public static function getBind($type) + { + return isset(self::$bind[$type]) ? self::$bind[$type] : null; + } + + /** + * 导入配置文件的路由规则 + * @access public + * @param array $rule 路由规则 + * @param string $type 请求类型 + * @return void + */ + public static function import(array $rule, $type = '*') + { + // 检查域名部署 + if (isset($rule['__domain__'])) { + self::domain($rule['__domain__']); + unset($rule['__domain__']); + } + + // 检查变量规则 + if (isset($rule['__pattern__'])) { + self::pattern($rule['__pattern__']); + unset($rule['__pattern__']); + } + + // 检查路由别名 + if (isset($rule['__alias__'])) { + self::alias($rule['__alias__']); + unset($rule['__alias__']); + } + + // 检查资源路由 + if (isset($rule['__rest__'])) { + self::resource($rule['__rest__']); + unset($rule['__rest__']); + } + + self::registerRules($rule, strtolower($type)); + } + + // 批量注册路由 + protected static function registerRules($rules, $type = '*') + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (empty($val)) { + continue; + } + if (is_string($key) && 0 === strpos($key, '[')) { + $key = substr($key, 1, -1); + self::group($key, $val); + } elseif (is_array($val)) { + self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + } else { + self::setRule($key, $val, $type); + } + } + } + + /** + * 注册路由规则 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) + { + $group = self::getGroup('name'); + + if (!is_null($group)) { + // 路由分组 + $option = array_merge(self::getGroup('option'), $option); + $pattern = array_merge(self::getGroup('pattern'), $pattern); + } + + $type = strtolower($type); + + if (strpos($type, '|')) { + $option['method'] = $type; + $type = '*'; + } + if (is_array($rule) && empty($route)) { + foreach ($rule as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, $val[1]); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $option1 = null; + $pattern1 = null; + $route = $val; + } + self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group); + } + } else { + self::setRule($rule, $route, $type, $option, $pattern, $group); + } + + } + + /** + * 设置路由规则 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param string $group 所属分组 + * @return void + */ + protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') + { + if (is_array($rule)) { + $name = $rule[0]; + $rule = $rule[1]; + } elseif (is_string($route)) { + $name = $route; + } + if (!isset($option['complete_match'])) { + if (Config::get('route_complete_match')) { + $option['complete_match'] = true; + } elseif ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + + if ('$' == substr($rule, -1, 1)) { + $rule = substr($rule, 0, -1); + } + + if ('/' != $rule || $group) { + $rule = trim($rule, '/'); + } + $vars = self::parseVar($rule); + if (isset($name)) { + $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; + $suffix = isset($option['ext']) ? $option['ext'] : null; + self::name($name, [$key, $vars, self::$domain, $suffix]); + } + if (isset($option['modular'])) { + $route = $option['modular'] . '/' . $route; + } + if ($group) { + if ('*' != $type) { + $option['method'] = $type; + } + if (self::$domain) { + self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + } else { + if ('*' != $type && isset(self::$rules['*'][$rule])) { + unset(self::$rules['*'][$rule]); + } + if (self::$domain) { + self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + if ('*' == $type) { + // 注册路由快捷方式 + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) { + self::$rules['domain'][self::$domain][$method][$rule] = true; + } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) { + self::$rules[$method][$rule] = true; + } + } + } + } + } + + /** + * 设置当前执行的参数信息 + * @access public + * @param array $options 参数信息 + * @return mixed + */ + protected static function setOption($options = []) + { + self::$option[] = $options; + } + + /** + * 获取当前执行的所有参数信息 + * @access public + * @return array + */ + public static function getOption() + { + return self::$option; + } + + /** + * 获取当前的分组信息 + * @access public + * @param string $type 分组信息名称 name option pattern + * @return mixed + */ + public static function getGroup($type) + { + if (isset(self::$group[$type])) { + return self::$group[$type]; + } else { + return 'name' == $type ? null : []; + } + } + + /** + * 设置当前的路由分组 + * @access public + * @param string $name 分组名称 + * @param array $option 分组路由参数 + * @param array $pattern 分组变量规则 + * @return void + */ + public static function setGroup($name, $option = [], $pattern = []) + { + self::$group['name'] = $name; + self::$group['option'] = $option ?: []; + self::$group['pattern'] = $pattern ?: []; + } + + /** + * 注册路由分组 + * @access public + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $routes 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function group($name, $routes, $option = [], $pattern = []) + { + if (is_array($name)) { + $option = $name; + $name = isset($option['name']) ? $option['name'] : ''; + } + // 分组 + $currentGroup = self::getGroup('name'); + if ($currentGroup) { + $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); + } + if (!empty($name)) { + if ($routes instanceof \Closure) { + $currentOption = self::getGroup('option'); + $currentPattern = self::getGroup('pattern'); + self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + self::setGroup($currentGroup, $currentOption, $currentPattern); + if ($currentGroup != $name) { + self::$rules['*'][$name]['route'] = ''; + self::$rules['*'][$name]['var'] = self::parseVar($name); + self::$rules['*'][$name]['option'] = $option; + self::$rules['*'][$name]['pattern'] = $pattern; + } + } else { + $item = []; + $completeMatch = Config::get('route_complete_match'); + foreach ($routes as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $route = $val; + } + + $options = isset($option1) ? $option1 : $option; + $patterns = isset($pattern1) ? $pattern1 : $pattern; + if ('$' == substr($key, -1, 1)) { + // 是否完整匹配 + $options['complete_match'] = true; + $key = substr($key, 0, -1); + } elseif ($completeMatch) { + $options['complete_match'] = true; + } + $key = trim($key, '/'); + $vars = self::parseVar($key); + $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; + // 设置路由标识 + $suffix = isset($options['ext']) ? $options['ext'] : null; + self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); + } + self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; + } + + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (!isset(self::$rules[$method][$name])) { + self::$rules[$method][$name] = true; + } elseif (is_array(self::$rules[$method][$name])) { + self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); + } + } + + } elseif ($routes instanceof \Closure) { + // 闭包注册 + $currentOption = self::getGroup('option'); + $currentPattern = self::getGroup('pattern'); + self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + self::setGroup($currentGroup, $currentOption, $currentPattern); + } else { + // 批量注册路由 + self::rule($routes, '', '*', $option, $pattern); + } + } + + /** + * 注册路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function any($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, '*', $option, $pattern); + } + + /** + * 注册GET路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function get($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'GET', $option, $pattern); + } + + /** + * 注册POST路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function post($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'POST', $option, $pattern); + } + + /** + * 注册PUT路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function put($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'PUT', $option, $pattern); + } + + /** + * 注册DELETE路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function delete($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'DELETE', $option, $pattern); + } + + /** + * 注册PATCH路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function patch($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'PATCH', $option, $pattern); + } + + /** + * 注册资源路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function resource($rule, $route = '', $option = [], $pattern = []) + { + if (is_array($rule)) { + foreach ($rule as $key => $val) { + if (is_array($val)) { + list($val, $option, $pattern) = array_pad($val, 3, []); + } + self::resource($key, $val, $option, $pattern); + } + } else { + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + foreach ($array as $val) { + $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); + } + $rule = implode('/', $item) . '/' . $last; + } + // 注册资源路由 + foreach (self::$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], ':id') && isset($option['var'][$last])) { + $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); + } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { + $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); + } + $item = ltrim($rule . $val[1], '/'); + $option['rest'] = $key; + self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); + } + } + } + + /** + * 注册控制器路由 操作方法对应不同的请求后缀 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function controller($rule, $route = '', $option = [], $pattern = []) + { + foreach (self::$methodPrefix as $type => $val) { + self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + } + } + + /** + * 注册别名路由 + * @access public + * @param string|array $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return void + */ + public static function alias($rule = null, $route = '', $option = []) + { + if (is_array($rule)) { + self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); + } else { + self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; + } + } + + /** + * 设置不同请求类型下面的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return void + */ + public static function setMethodPrefix($method, $prefix = '') + { + if (is_array($method)) { + self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); + } else { + self::$methodPrefix[strtolower($method)] = $prefix; + } + } + + /** + * rest方法定义和修改 + * @access public + * @param string|array $name 方法名称 + * @param array|bool $resource 资源 + * @return void + */ + public static function rest($name, $resource = []) + { + if (is_array($name)) { + self::$rest = $resource ? $name : array_merge(self::$rest, $name); + } else { + self::$rest[$name] = $resource; + } + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return void + */ + public static function miss($route, $method = '*', $option = []) + { + self::rule('__miss__', $route, $method, $option, []); + } + + /** + * 注册一个自动解析的URL路由 + * @access public + * @param string $route 路由地址 + * @return void + */ + public static function auto($route) + { + self::rule('__auto__', $route, '*', [], []); + } + + /** + * 获取或者批量设置路由定义 + * @access public + * @param mixed $rules 请求类型或者路由定义数组 + * @return array + */ + public static function rules($rules = '') + { + if (is_array($rules)) { + self::$rules = $rules; + } elseif ($rules) { + return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; + } else { + $rules = self::$rules; + unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); + return $rules; + } + } + + /** + * 检测子域名部署 + * @access public + * @param Request $request Request请求对象 + * @param array $currentRules 当前路由规则 + * @param string $method 请求类型 + * @return void + */ + public static function checkDomain($request, &$currentRules, $method = 'get') + { + // 域名规则 + $rules = self::$rules['domain']; + // 开启子域名部署 支持二级和三级域名 + if (!empty($rules)) { + $host = $request->host(true); + if (isset($rules[$host])) { + // 完整域名或者IP配置 + $item = $rules[$host]; + } else { + $rootDomain = Config::get('url_domain_root'); + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); + } else { + $domain = explode('.', $host, -2); + } + // 子域名配置 + if (!empty($domain)) { + // 当前子域名 + $subDomain = implode('.', $domain); + self::$subDomain = $subDomain; + $domain2 = array_pop($domain); + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + if ($subDomain && isset($rules[$subDomain])) { + // 子域名配置 + $item = $rules[$subDomain]; + } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $rules['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($rules['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $rules['*']; + $panDomain = $domain2; + } + } + } + } + if (!empty($item)) { + if (isset($panDomain)) { + // 保存当前泛域名 + $request->route(['__domain__' => $panDomain]); + } + if (isset($item['[bind]'])) { + // 解析子域名部署规则 + list($rule, $option, $pattern) = $item['[bind]']; + if (!empty($option['https']) && !$request->isSsl()) { + // https检测 + throw new HttpException(404, 'must use https request:' . $host); + } + + if (strpos($rule, '?')) { + // 传入其它参数 + $array = parse_url($rule); + $result = $array['path']; + parse_str($array['query'], $params); + if (isset($panDomain)) { + $pos = array_search('*', $params); + if (false !== $pos) { + // 泛域名作为参数 + $params[$pos] = $panDomain; + } + } + $_GET = array_merge($_GET, $params); + } else { + $result = $rule; + } + + if (0 === strpos($result, '\\')) { + // 绑定到命名空间 例如 \app\index\behavior + self::$bind = ['type' => 'namespace', 'namespace' => $result]; + } elseif (0 === strpos($result, '@')) { + // 绑定到类 例如 @app\index\controller\User + self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; + } else { + // 绑定到模块/控制器 例如 index/user + self::$bind = ['type' => 'module', 'module' => $result]; + } + self::$domainBind = true; + } else { + self::$domainRule = $item; + $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; + } + } + } + } + + /** + * 检测URL路由 + * @access public + * @param Request $request Request请求对象 + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $checkDomain 是否检测域名规则 + * @return false|array + */ + public static function check($request, $url, $depr = '/', $checkDomain = false) + { + //检查解析缓存 + if (!App::$debug && Config::get('route_check_cache')) { + $key = self::getCheckCacheKey($request); + if (Cache::has($key)) { + list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key); + return self::parseRule($rule, $route, $pathinfo, $option, $matches, true); + } + } + + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace($depr, '|', $url); + + if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) { + // 检测路由别名 + $result = self::checkRouteAlias($request, $url, $depr); + if (false !== $result) { + return $result; + } + } + $method = strtolower($request->method()); + // 获取当前请求类型的路由规则 + $rules = isset(self::$rules[$method]) ? self::$rules[$method] : []; + // 检测域名部署 + if ($checkDomain) { + self::checkDomain($request, $rules, $method); + } + // 检测URL绑定 + $return = self::checkUrlBind($url, $rules, $depr); + if (false !== $return) { + return $return; + } + if ('|' != $url) { + $url = rtrim($url, '|'); + } + $item = str_replace('|', '/', $url); + if (isset($rules[$item])) { + // 静态路由规则检测 + $rule = $rules[$item]; + if (true === $rule) { + $rule = self::getRouteExpress($item); + } + if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { + self::setOption($rule['option']); + return self::parseRule($item, $rule['route'], $url, $rule['option']); + } + } + + // 路由规则检测 + if (!empty($rules)) { + return self::checkRoute($request, $rules, $url, $depr); + } + return false; + } + + private static function getRouteExpress($key) + { + return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key]; + } + + /** + * 检测路由规则 + * @access private + * @param Request $request + * @param array $rules 路由规则 + * @param string $url URL地址 + * @param string $depr URL分割符 + * @param string $group 路由分组名 + * @param array $options 路由参数(分组) + * @return mixed + */ + private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) + { + foreach ($rules as $key => $item) { + if (true === $item) { + $item = self::getRouteExpress($key); + } + if (!isset($item['rule'])) { + continue; + } + $rule = $item['rule']; + $route = $item['route']; + $vars = $item['var']; + $option = $item['option']; + $pattern = $item['pattern']; + + // 检查参数有效性 + if (!self::checkOption($option, $request)) { + continue; + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); + } + + if (is_array($rule)) { + // 分组路由 + $pos = strpos(str_replace('<', ':', $key), ':'); + if (false !== $pos) { + $str = substr($key, 0, $pos); + } else { + $str = $key; + } + if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + continue; + } + self::setOption($option); + $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); + if (false !== $result) { + return $result; + } + } elseif ($route) { + if ('__miss__' == $rule || '__auto__' == $rule) { + // 指定特殊路由 + $var = trim($rule, '__'); + ${$var} = $item; + continue; + } + if ($group) { + $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + self::setOption($option); + if (isset($options['bind_model']) && isset($option['bind_model'])) { + $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); + } + $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); + if (false !== $result) { + return $result; + } + } + } + if (isset($auto)) { + // 自动解析URL地址 + return self::parseUrl($auto['route'] . '/' . $url, $depr); + } elseif (isset($miss)) { + // 未匹配所有路由的路由规则处理 + return self::parseRule('', $miss['route'], $url, $miss['option']); + } + return false; + } + + /** + * 检测路由别名 + * @access private + * @param Request $request + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @return mixed + */ + private static function checkRouteAlias($request, $url, $depr) + { + $array = explode('|', $url); + $alias = array_shift($array); + $item = self::$rules['alias'][$alias]; + + if (is_array($item)) { + list($rule, $option) = $item; + $action = $array[0]; + if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { + // 允许操作 + return false; + } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { + // 排除操作 + return false; + } + if (isset($option['method'][$action])) { + $option['method'] = $option['method'][$action]; + } + } else { + $rule = $item; + } + $bind = implode('|', $array); + // 参数有效性检查 + if (isset($option) && !self::checkOption($option, $request)) { + // 路由不匹配 + return false; + } elseif (0 === strpos($rule, '\\')) { + // 路由到类 + return self::bindToClass($bind, substr($rule, 1), $depr); + } elseif (0 === strpos($rule, '@')) { + // 路由到控制器类 + return self::bindToController($bind, substr($rule, 1), $depr); + } else { + // 路由到模块/控制器 + return self::bindToModule($bind, $rule, $depr); + } + } + + /** + * 检测URL绑定 + * @access private + * @param string $url URL地址 + * @param array $rules 路由规则 + * @param string $depr URL分隔符 + * @return mixed + */ + private static function checkUrlBind(&$url, &$rules, $depr = '/') + { + if (!empty(self::$bind)) { + $type = self::$bind['type']; + $bind = self::$bind[$type]; + // 记录绑定信息 + App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); + // 如果有URL绑定 则进行绑定检测 + switch ($type) { + case 'class': + // 绑定到类 + return self::bindToClass($url, $bind, $depr); + case 'controller': + // 绑定到控制器类 + return self::bindToController($url, $bind, $depr); + case 'namespace': + // 绑定到命名空间 + return self::bindToNamespace($url, $bind, $depr); + } + } + return false; + } + + /** + * 绑定到类 + * @access public + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToClass($url, $class, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; + } + + /** + * 绑定到命名空间 + * @access public + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToNamespace($url, $namespace, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); + $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); + if (!empty($array[2])) { + self::parseUrlParams($array[2]); + } + return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; + } + + /** + * 绑定到控制器类 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器名 (支持带模块名 index/user ) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToController($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; + } + + /** + * 绑定到模块/控制器 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToModule($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'module', 'module' => $controller . '/' . $action]; + } + + /** + * 路由参数有效性检查 + * @access private + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + private static function checkOption($option, $request) + { + if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) + || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 + || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 + || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 + || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 + || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 + || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 + || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 + || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 + || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 + ) { + return false; + } + return true; + } + + /** + * 检测路由规则 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $url URL地址 + * @param array $pattern 变量规则 + * @param array $option 路由参数 + * @param string $depr URL分隔符(全局) + * @return array|false + */ + private static function checkRule($rule, $route, $url, $pattern, $option, $depr) + { + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + // 检查路由的参数分隔符 + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); + } + + $len1 = substr_count($url, '|'); + $len2 = substr_count($rule, '/'); + // 多余参数是否合并 + $merge = !empty($option['merge_extra_vars']); + if ($merge && $len1 > $len2) { + $url = str_replace('|', $depr, $url); + $url = implode('|', explode($depr, $url, $len2 + 1)); + } + + if ($len1 >= $len2 || strpos($rule, '[')) { + if (!empty($option['complete_match'])) { + // 完整匹配 + if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { + return false; + } + } + $pattern = array_merge(self::$rules['pattern'], $pattern); + if (false !== $match = self::match($url, $rule, $pattern)) { + // 匹配到路由规则 + return self::parseRule($rule, $route, $url, $option, $match); + } + } + return false; + } + + /** + * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... + * @access public + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $autoSearch 是否自动深度搜索控制器 + * @return array + */ + public static function parseUrl($url, $depr = '/', $autoSearch = false) + { + + if (isset(self::$bind['module'])) { + $bind = str_replace('/', $depr, self::$bind['module']); + // 如果有模块/控制器绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + $url = str_replace($depr, '|', $url); + list($path, $var) = self::parseUrlPath($url); + $route = [null, null, null]; + if (isset($path)) { + // 解析模块 + $module = Config::get('app_multi_module') ? array_shift($path) : null; + if ($autoSearch) { + // 自动搜索控制器 + $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); + $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; + $item = []; + $find = false; + foreach ($path as $val) { + $item[] = $val; + $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; + $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; + if (is_file($file)) { + $find = true; + break; + } else { + $dir .= DS . Loader::parseName($val); + } + } + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } + } else { + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + } + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + // 解析额外参数 + self::parseUrlParams(empty($path) ? '' : implode('|', $path)); + // 封装路由 + $route = [$module, $controller, $action]; + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + $name2 = ''; + if (empty($module) || isset($bind) && $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + } + return ['type' => 'module', 'module' => $route]; + } + + /** + * 解析URL的pathinfo参数和变量 + * @access private + * @param string $url URL地址 + * @return array + */ + private static function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } else { + $path = [$url]; + } + return [$path, $var]; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param string $rule 路由规则 + * @param array $pattern 变量规则 + * @return array|false + */ + private static function match($url, $rule, $pattern) + { + $m2 = explode('/', $rule); + $m1 = explode('|', $url); + + $var = []; + foreach ($m2 as $key => $val) { + // val中定义了多个变量 + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + $value = []; + $replace = []; + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; + } else { + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; + } + $value[] = $name; + } + $val = str_replace($matches[0], $replace, $val); + if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { + array_shift($match); + foreach ($value as $k => $name) { + if (isset($match[$k])) { + $var[$name] = $match[$k]; + } + } + continue; + } else { + return false; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $val = substr($val, 1, -1); + $optional = true; + } else { + $optional = false; + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + if (!$optional && !isset($m1[$key])) { + return false; + } + if (isset($m1[$key]) && isset($pattern[$name])) { + // 检查变量规则 + if ($pattern[$name] instanceof \Closure) { + $result = call_user_func_array($pattern[$name], [$m1[$key]]); + if (false === $result) { + return false; + } + } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { + return false; + } + } + $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; + } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { + return false; + } + } + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + /** + * 解析规则路由 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $pathinfo URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @param bool $fromCache 通过缓存解析 + * @return array + */ + private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false) + { + $request = Request::instance(); + + //保存解析缓存 + if (Config::get('route_check_cache') && !$fromCache) { + try { + $key = self::getCheckCacheKey($request); + Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]); + } catch (\Exception $e) { + + } + } + + // 解析路由规则 + if ($rule) { + $rule = explode('/', $rule); + // 获取URL地址中的参数 + $paths = explode('|', $pathinfo); + foreach ($rule as $item) { + $fun = ''; + if (0 === strpos($item, '[:')) { + $item = substr($item, 1, -1); + } + if (0 === strpos($item, ':')) { + $var = substr($item, 1); + $matches[$var] = array_shift($paths); + } else { + // 过滤URL中的静态变量 + array_shift($paths); + } + } + } else { + $paths = explode('|', $pathinfo); + } + + // 获取路由地址规则 + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + foreach ($matches as $key => $val) { + if (false !== strpos($route, ':' . $key)) { + $route = str_replace(':' . $key, $val, $route); + } + } + } + + // 绑定模型数据 + if (isset($option['bind_model'])) { + $bind = []; + foreach ($option['bind_model'] as $key => $val) { + if ($val instanceof \Closure) { + $result = call_user_func_array($val, [$matches]); + } else { + if (is_array($val)) { + $fields = explode('&', $val[1]); + $model = $val[0]; + $exception = isset($val[2]) ? $val[2] : true; + } else { + $fields = ['id']; + $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) { + $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + if (!empty($result)) { + $bind[$key] = $result; + } + } + $request->bind($bind); + } + + if (!empty($option['response'])) { + Hook::add('response_send', $option['response']); + } + + // 解析额外参数 + self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); + // 记录匹配的路由信息 + $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); + + // 检测路由after行为 + if (!empty($option['after_behavior'])) { + if ($option['after_behavior'] instanceof \Closure) { + $result = call_user_func_array($option['after_behavior'], []); + } else { + foreach ((array) $option['after_behavior'] as $behavior) { + $result = Hook::exec($behavior, ''); + if (!is_null($result)) { + break; + } + } + } + // 路由规则重定向 + if ($result instanceof Response) { + return ['type' => 'response', 'response' => $result]; + } elseif (is_array($result)) { + return $result; + } + } + + if ($route instanceof \Closure) { + // 执行闭包 + $result = ['type' => 'function', 'function' => $route]; + } elseif (0 === strpos($route, '/') || strpos($route, '://')) { + // 路由到重定向地址 + $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; + } elseif (false !== strpos($route, '\\')) { + // 路由到方法 + list($path, $var) = self::parseUrlPath($route); + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + $result = ['type' => 'method', 'method' => $method, 'var' => $var]; + } elseif (0 === strpos($route, '@')) { + // 路由到控制器 + $route = substr($route, 1); + list($route, $var) = self::parseUrlPath($route); + $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; + $request->action(array_pop($route)); + $request->controller($route ? array_pop($route) : Config::get('default_controller')); + $request->module($route ? array_pop($route) : Config::get('default_module')); + App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); + } else { + // 路由到模块/控制器/操作 + $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false); + } + // 开启请求缓存 + if ($request->isGet() && isset($option['cache'])) { + $cache = $option['cache']; + if (is_array($cache)) { + list($key, $expire, $tag) = array_pad($cache, 3, null); + } else { + $key = str_replace('|', '/', $pathinfo); + $expire = $cache; + $tag = null; + } + $request->cache($key, $expire, $tag); + } + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access private + * @param string $url URL地址 + * @param bool $convert 是否自动转换URL地址 + * @return array + */ + private static function parseModule($url, $convert = false) + { + list($path, $var) = self::parseUrlPath($url); + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = Request::instance()->method(); + if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) { + // 操作方法前缀支持 + $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action; + } + // 设置当前请求的路由变量 + Request::instance()->route($var); + // 路由到模块/控制器/操作 + return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert]; + } + + /** + * 解析URL地址中的参数Request对象 + * @access private + * @param string $url 路由规则 + * @param array $var 变量 + * @return void + */ + private static function parseUrlParams($url, &$var = []) + { + if ($url) { + if (Config::get('url_param_type')) { + $var += explode('|', $url); + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + // 设置当前请求的参数 + Request::instance()->route($var); + } + + // 分析路由规则中的变量 + private static function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + foreach (explode('/', $rule) as $val) { + $optional = false; + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $optional = true; + } else { + $optional = false; + } + $var[$name] = $optional ? 2 : 1; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $optional = true; + $val = substr($val, 1, -1); + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + $var[$name] = $optional ? 2 : 1; + } + } + return $var; + } + + /** + * 获取路由解析缓存的key + * @param Request $request + * @return string + */ + private static function getCheckCacheKey(Request $request) + { + static $key; + + if (empty($key)) { + if ($callback = Config::get('route_check_cache_key')) { + $key = call_user_func($callback, $request); + } else { + $key = "{$request->host(true)}|{$request->method()}|{$request->path()}"; + } + } + + return $key; + } +} diff --git a/source/thinkphp/library/think/Session.php b/source/thinkphp/library/think/Session.php new file mode 100644 index 0000000..61150bc --- /dev/null +++ b/source/thinkphp/library/think/Session.php @@ -0,0 +1,366 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Session +{ + protected static $prefix = ''; + protected static $init = null; + + /** + * 设置或者获取session作用域(前缀) + * @param string $prefix + * @return string|void + */ + public static function prefix($prefix = '') + { + empty(self::$init) && self::boot(); + if (empty($prefix) && null !== $prefix) { + return self::$prefix; + } else { + self::$prefix = $prefix; + } + } + + /** + * session初始化 + * @param array $config + * @return void + * @throws \think\Exception + */ + public static function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('session'); + } + // 记录初始化信息 + App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); + $isDoStart = false; + if (isset($config['use_trans_sid'])) { + ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); + } + + // 启动session + if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.auto_start', 0); + $isDoStart = true; + } + + if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) { + self::$prefix = $config['prefix']; + } + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { + session_id($_REQUEST[$config['var_session_id']]); + } elseif (isset($config['id']) && !empty($config['id'])) { + session_id($config['id']); + } + if (isset($config['name'])) { + session_name($config['name']); + } + if (isset($config['path'])) { + session_save_path($config['path']); + } + if (isset($config['domain'])) { + ini_set('session.cookie_domain', $config['domain']); + } + if (isset($config['expire'])) { + ini_set('session.gc_maxlifetime', $config['expire']); + ini_set('session.cookie_lifetime', $config['expire']); + } + if (isset($config['secure'])) { + ini_set('session.cookie_secure', $config['secure']); + } + if (isset($config['httponly'])) { + ini_set('session.cookie_httponly', $config['httponly']); + } + if (isset($config['use_cookies'])) { + ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); + } + if (isset($config['cache_limiter'])) { + session_cache_limiter($config['cache_limiter']); + } + if (isset($config['cache_expire'])) { + session_cache_expire($config['cache_expire']); + } + if (!empty($config['type'])) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类 + if (!class_exists($class) || !session_set_save_handler(new $class($config))) { + throw new ClassNotFoundException('error session handler:' . $class, $class); + } + } + if ($isDoStart) { + session_start(); + self::$init = true; + } else { + self::$init = false; + } + } + + /** + * session自动启动或者初始化 + * @return void + */ + public static function boot() + { + if (is_null(self::$init)) { + self::init(); + } elseif (false === self::$init) { + if (PHP_SESSION_ACTIVE != session_status()) { + session_start(); + } + self::$init = true; + } + } + + /** + * session设置 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function set($name, $value = '', $prefix = null) + { + empty(self::$init) && self::boot(); + + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (strpos($name, '.')) { + // 二维数组赋值 + list($name1, $name2) = explode('.', $name); + if ($prefix) { + $_SESSION[$prefix][$name1][$name2] = $value; + } else { + $_SESSION[$name1][$name2] = $value; + } + } elseif ($prefix) { + $_SESSION[$prefix][$name] = $value; + } else { + $_SESSION[$name] = $value; + } + } + + /** + * session获取 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public static function get($name = '', $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if ('' == $name) { + // 获取全部的session + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + } elseif ($prefix) { + // 获取session + if (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + $value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null; + } else { + $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null; + } + } else { + if (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + $value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null; + } else { + $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null; + } + } + return $value; + } + + /** + * session获取并删除 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public static function pull($name, $prefix = null) + { + $result = self::get($name, $prefix); + if ($result) { + self::delete($name, $prefix); + return $result; + } else { + return; + } + } + + /** + * session设置 下一次请求有效 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function flash($name, $value) + { + self::set($name, $value); + if (!self::has('__flash__.__time__')) { + self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + self::push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @return void + */ + public static function flush() + { + if (self::$init) { + $item = self::get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + self::delete($item); + self::set('__flash__', []); + } + } + } + } + + /** + * 删除session数据 + * @param string|array $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function delete($name, $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (is_array($name)) { + foreach ($name as $key) { + self::delete($key, $prefix); + } + } elseif (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + if ($prefix) { + unset($_SESSION[$prefix][$name1][$name2]); + } else { + unset($_SESSION[$name1][$name2]); + } + } else { + if ($prefix) { + unset($_SESSION[$prefix][$name]); + } else { + unset($_SESSION[$name]); + } + } + } + + /** + * 清空session数据 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function clear($prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if ($prefix) { + unset($_SESSION[$prefix]); + } else { + $_SESSION = []; + } + } + + /** + * 判断session数据 + * @param string $name session名称 + * @param string|null $prefix + * @return bool + */ + public static function has($name, $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (strpos($name, '.')) { + // 支持数组 + list($name1, $name2) = explode('.', $name); + return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]); + } else { + return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]); + } + } + + /** + * 添加数据到一个session数组 + * @param string $key + * @param mixed $value + * @return void + */ + public static function push($key, $value) + { + $array = self::get($key); + if (is_null($array)) { + $array = []; + } + $array[] = $value; + self::set($key, $array); + } + + /** + * 启动session + * @return void + */ + public static function start() + { + session_start(); + self::$init = true; + } + + /** + * 销毁session + * @return void + */ + public static function destroy() + { + if (!empty($_SESSION)) { + $_SESSION = []; + } + session_unset(); + session_destroy(); + self::$init = null; + } + + /** + * 重新生成session_id + * @param bool $delete 是否删除关联会话文件 + * @return void + */ + public static function regenerate($delete = false) + { + session_regenerate_id($delete); + } + + /** + * 暂停session + * @return void + */ + public static function pause() + { + // 暂停session + session_write_close(); + self::$init = false; + } +} diff --git a/source/thinkphp/library/think/Template.php b/source/thinkphp/library/think/Template.php new file mode 100644 index 0000000..9ba0ff3 --- /dev/null +++ b/source/thinkphp/library/think/Template.php @@ -0,0 +1,1139 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\TemplateNotFoundException; +use think\template\TagLib; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + // 模板变量 + protected $data = []; + // 引擎配置 + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_base' => '', + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DS, + '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|'', 为空时自动识别 + ]; + + private $literal = []; + private $includeFile = []; // 记录所有模板包含的文件路径及更新时间 + protected $storage; + + /** + * 构造函数 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config['cache_path'] = TEMP_PATH; + $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 mixed $name + * @param mixed $value + * @return void + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + } + + /** + * 模板引擎参数赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 模板引擎配置项 + * @access public + * @param array|string $config + * @return string|void|array + */ + public function config($config) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } elseif (isset($this->config[$config])) { + return $this->config[$config]; + } else { + return; + } + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get($name = '') + { + if ('' == $name) { + return $this->data; + } else { + $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 $template 模板文件 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + if ($config) { + $this->config($config); + } + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 读取渲染缓存 + $cacheContent = Cache::get($this->config['cache_id']); + if (false !== $cacheContent) { + echo $cacheContent; + return; + } + } + $template = $this->parseTemplateFile($template); + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($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(); + ob_implicit_flush(0); + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + // 获取并清空缓存 + $content = ob_get_clean(); + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 缓存页面输出 + Cache::set($this->config['cache_id'], $content, $this->config['cache_time']); + } + echo $content; + } + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($content, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + if ($config) { + $this->config($config); + } + $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 Template + */ + public function layout($name, $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 boolean + */ + private function checkCache($cacheFile) + { + // 未开启缓存功能 + if (!$this->config['tpl_cache']) { + return false; + } + // 缓存文件不存在 + if (!is_file($cacheFile)) { + return false; + } + // 读取缓存文件失败 + if (!$handle = @fopen($cacheFile, "r")) { + return false; + } + // 读取第一行 + preg_match('/\/\*(.+?)\*\//', fgets($handle), $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 public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache($cacheId) + { + if ($cacheId && $this->config['display_cache']) { + // 缓存页面输出 + return Cache::has($cacheId); + } + return false; + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(&$content, $cacheFile) + { + // 判断是否启用布局 + 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)/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 = []; + return; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(&$content) + { + // 内容为空不解析 + 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); + return; + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws \think\Exception + */ + private function parsePhp(&$content) + { + // 短标签的情况要将' . "\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); + } + return; + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(&$content) + { + $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); + return; + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(&$content) + { + $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); + } + return; + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(&$content, $restore = false) + { + $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); + } + return; + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(&$content, $sort = false) + { + $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(&$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + return explode(',', $matches['name']); + } + return; + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib($tagLib, &$content, $hide = false) + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + /** @var Taglib $tLib */ + $tLib = new $className($this); + $tLib->parseTag($content, $hide ? '' : $tagLib); + return; + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr($str, $name = null) + { + $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]; + } else { + return $array; + } + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(&$content) + { + $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 '?': + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = ''; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = ''; + break; + default: + $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); + } + return; + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(&$varStr) + { + $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 ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } elseif ('$Request' == $first) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')'; + } 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); + } + return; + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @return void + */ + public function parseVarFunction(&$varStr) + { + if (false == strpos($varStr, '|')) { + return; + } + static $_varFunctionList = []; + $_key = md5($varStr); + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + // 取得变量名称 + $name = 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]); + switch ($fun) { + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + if (!in_array($fun, $template_deny_funs)) { + 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; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar($vars) + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + if ($vars) { + switch ($type) { + case 'SERVER': + $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; + break; + case 'GET': + $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; + break; + case 'POST': + $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; + break; + case 'COOKIE': + $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; + break; + case 'SESSION': + $parseStr = '\\think\\Session::get(\'' . $param . '\')'; + break; + case 'ENV': + $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; + break; + case 'REQUEST': + $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; + break; + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'LANG': + $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; + break; + case 'CONFIG': + $parseStr = '\\think\\Config::get(\'' . $param . '\')'; + break; + default: + $parseStr = '\'\''; + break; + } + } else { + switch ($type) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_VERSION'; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + if (defined($type)) { + $parseStr = $type; + } else { + $parseStr = ''; + } + } + } + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName($templateName) + { + $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|false + */ + private function parseTemplateFile($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + if (strpos($template, '@')) { + list($module, $template) = explode('@', $template); + } + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + if ($this->config['view_base']) { + $module = isset($module) ? $module : Request::instance()->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; + } + $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.')); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + return $template; + } else { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex($tagName) + { + $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(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!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(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + return '/' . $regex . '/is'; + } +} diff --git a/source/thinkphp/library/think/Url.php b/source/thinkphp/library/think/Url.php new file mode 100644 index 0000000..53a545f --- /dev/null +++ b/source/thinkphp/library/think/Url.php @@ -0,0 +1,333 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Url +{ + // 生成URL地址的root + protected static $root; + protected static $bindCheck; + + /** + * URL生成 支持路由反射 + * @param string $url 路由地址 + * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] + * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean|string $domain 是否显示域名 或者直接传入域名 + * @return string + */ + public static function build($url = '', $vars = '', $suffix = true, $domain = false) + { + if (false === $domain && Route::rules('domain')) { + $domain = true; + } + // 解析URL + 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, '?')) { + // 解析参数 + list($anchor, $info['query']) = explode('?', $anchor, 2); + } + if (false !== strpos($anchor, '@')) { + // 解析域名 + list($anchor, $domain) = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + list($url, $domain) = explode('@', $url, 2); + } + } + + // 解析参数 + if (is_string($vars)) { + // aaa=1&bbb=2 转换成数组 + parse_str($vars, $vars); + } + + if ($url) { + $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + if (is_null($rule) && isset($info['query'])) { + $rule = Route::name($url); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { + // 匹配路由命名标识 + $url = $match[0]; + // 替换可选分隔符 + $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); + if (!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 { + // 检查别名路由 + $alias = Route::rules('alias'); + $matchAlias = false; + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $val) { + if (is_array($val)) { + $val = $val[0]; + } + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = self::parseUrl($url, $domain); + } + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 检测URL绑定 + if (!self::$bindCheck) { + $type = Route::getBind('type'); + if ($type) { + $bind = Route::getBind($type); + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } + } + } + // 还原URL分隔符 + $depr = Config::get('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + // URL后缀 + $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix); + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if (Config::get('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + $paramType = Config::get('url_param_type'); + foreach ($vars as $var => $val) { + if ('' !== trim($val)) { + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } + } + } + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + // 检测域名 + $domain = self::parseDomain($url, $domain); + // URL组装 + $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); + + self::$bindCheck = false; + return $url; + } + + // 直接解析URL地址 + protected static function parseUrl($url, &$domain) + { + $request = Request::instance(); + 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); + } else { + // 解析到 模块/控制器/操作 + $module = $request->module(); + $domains = Route::rules('domain'); + if (true === $domain && 2 == substr_count($url, '/')) { + $current = $request->host(); + $match = []; + $pos = []; + foreach ($domains as $key => $item) { + if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) { + $pos[$key] = strlen($item['[bind]'][0]) + 1; + $match[] = $key; + $module = ''; + } + } + if ($match) { + $domain = current($match); + foreach ($match as $item) { + if (0 === strpos($current, $item)) { + $domain = $item; + } + } + self::$bindCheck = true; + $url = substr($url, $pos[$domain]); + } + } elseif ($domain) { + if (isset($domains[$domain]['[bind]'][0])) { + $bindModule = $domains[$domain]['[bind]'][0]; + if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { + $module = ''; + } + } + } + $module = $module ? $module . '/' : ''; + + $controller = $request->controller(); + if ('' == $url) { + // 空字符串输出当前的 模块/控制器/操作 + $action = $request->action(); + } else { + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + $module = empty($path) ? $module : array_pop($path) . '/'; + } + if (Config::get('url_convert')) { + $action = strtolower($action); + $controller = Loader::parseName($controller); + } + $url = $module . $controller . '/' . $action; + } + return $url; + } + + // 检测域名 + protected static function parseDomain(&$url, $domain) + { + if (!$domain) { + return ''; + } + $request = Request::instance(); + $rootDomain = Config::get('url_domain_root'); + if (true === $domain) { + // 自动判断域名 + $domain = Config::get('app_host') ?: $request->host(); + + $domains = Route::rules('domain'); + if ($domains) { + $route_domain = array_keys($domains); + foreach ($route_domain as $domain_prefix) { + if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== 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; + } + } + } + } + } + + } else { + if (empty($rootDomain)) { + $host = Config::get('app_host') ?: $request->host(); + $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; + } + if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } + } + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://'; + } + return $scheme . $domain; + } + + // 解析URL后缀 + protected static function parseSuffix($suffix) + { + if ($suffix) { + $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix; + if ($pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; + } + + // 匹配路由地址 + public static function getRuleUrl($rule, &$vars = []) + { + foreach ($rule as $item) { + list($url, $pattern, $domain, $suffix) = $item; + if (empty($pattern)) { + return [rtrim($url, '$'), $domain, $suffix]; + } + $type = Config::get('url_common_param'); + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + unset($vars[$key]); + $result = [$url, $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $result = [$url, $domain, $suffix]; + } else { + break; + } + } + if (isset($result)) { + return $result; + } + } + return false; + } + + // 指定当前生成URL地址的root + public static function root($root) + { + self::$root = $root; + Request::instance()->root($root); + } +} diff --git a/source/thinkphp/library/think/Validate.php b/source/thinkphp/library/think/Validate.php new file mode 100644 index 0000000..608e1e4 --- /dev/null +++ b/source/thinkphp/library/think/Validate.php @@ -0,0 +1,1371 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Validate +{ + // 实例 + protected static $instance; + + // 自定义的验证类型 + protected static $type = []; + + // 验证类型别名 + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + // 当前验证的规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + // 验证字段描述 + protected $field = []; + + // 验证规则默认提示信息 + protected static $typeMsg = [ + 'require' => ':attribute require', + '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', + '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', + 'afterWith' => ':attribute cannot be less than :rule', + 'beforeWith' => ':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', + ]; + + // 当前验证场景 + protected $currentScene = null; + + // 正则表达式 regex = ['zip'=>'\d{6}',...] + protected $regex = []; + + // 验证场景 scene = ['edit'=>'name1,name2,...'] + protected $scene = []; + + // 验证失败错误信息 + protected $error = []; + + // 批量验证 + protected $batch = false; + + /** + * 构造函数 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + */ + public function __construct(array $rules = [], $message = [], $field = []) + { + $this->rule = array_merge($this->rule, $rules); + $this->message = array_merge($this->message, $message); + $this->field = array_merge($this->field, $field); + } + + /** + * 实例化验证 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + * @return Validate + */ + public static function make($rules = [], $message = [], $field = []) + { + if (is_null(self::$instance)) { + self::$instance = new self($rules, $message, $field); + } + return self::$instance; + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则 + * @return Validate + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = array_merge($this->rule, $name); + } else { + $this->rule[$name] = $rule; + } + return $this; + } + + /** + * 注册验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param mixed $callback callback方法(或闭包) + * @return void + */ + public static function extend($type, $callback = null) + { + if (is_array($type)) { + self::$type = array_merge(self::$type, $type); + } else { + self::$type[$type] = $callback; + } + } + + /** + * 设置验证规则的默认提示信息 + * @access protected + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public static function setTypeMsg($type, $msg = null) + { + if (is_array($type)) { + self::$typeMsg = array_merge(self::$typeMsg, $type); + } else { + self::$typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param string|array $name 字段名称 + * @param string $message 提示信息 + * @return Validate + */ + public function message($name, $message = '') + { + if (is_array($name)) { + $this->message = array_merge($this->message, $name); + } else { + $this->message[$name] = $message; + } + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string|array $name 场景名或者场景设置数组 + * @param mixed $fields 要验证的字段 + * @return Validate + */ + public function scene($name, $fields = null) + { + if (is_array($name)) { + $this->scene = array_merge($this->scene, $name); + }if (is_null($fields)) { + // 设置当前场景 + $this->currentScene = $name; + } else { + // 设置验证场景 + $this->scene[$name] = $fields; + } + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene($name) + { + return isset($this->scene[$name]); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return Validate + */ + public function batch($batch = true) + { + $this->batch = $batch; + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param mixed $rules 验证规则 + * @param string $scene 验证场景 + * @return bool + */ + public function check($data, $rules = [], $scene = '') + { + $this->error = []; + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + // 分析验证规则 + $scene = $this->getScene($scene); + if (is_array($scene)) { + // 处理场景验证字段 + $change = []; + $array = []; + foreach ($scene as $k => $val) { + if (is_numeric($k)) { + $array[] = $val; + } else { + $array[] = $k; + $change[$k] = $val; + } + } + } + + foreach ($rules as $key => $item) { + // field => rule1|rule2... field=>['rule1','rule2',...] + if (is_numeric($key)) { + // [field,rule1|rule2,msg1|msg2] + $key = $item[0]; + $rule = $item[1]; + if (isset($item[2])) { + $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2]; + } else { + $msg = []; + } + } else { + $rule = $item; + $msg = []; + } + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + list($key, $title) = explode('|', $key); + } else { + $title = isset($this->field[$key]) ? $this->field[$key] : $key; + } + + // 场景检测 + if (!empty($scene)) { + if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) { + continue; + } elseif (is_array($scene)) { + if (!in_array($key, $array)) { + continue; + } elseif (isset($change[$key])) { + // 重载某个验证规则 + $rule = $change[$key]; + } + } + } + + // 获取数据 支持二维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof \Closure) { + // 匿名函数验证 支持传入当前字段和所有字段两个数据 + $result = call_user_func_array($rule, [$value, $data]); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + if (is_array($result)) { + $this->error = array_merge($this->error, $result); + } else { + $this->error[$key] = $result; + } + } else { + $this->error = $result; + return false; + } + } + } + return !empty($this->error) ? false : true; + } + + /** + * 根据验证规则验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + protected function checkRule($value, $rules) + { + if ($rules instanceof \Closure) { + return call_user_func_array($rules, [$value]); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + list($type, $rule) = $this->getValidateType($key, $rule); + + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $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($field, $value, $rules, $data, $title = '', $msg = []) + { + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + $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 { + // 判断验证类型 + list($type, $rule, $info) = $this->getValidateType($key, $rule); + + // 如果不是require 有数据才会行验证 + if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + // 验证类型 + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + // 验证数据 + $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (isset($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = 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', ':rule'], [$title, (string) $rule], $result); + } + return $result; + } + $i++; + } + return $result; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule) + { + // 判断验证类型 + if (!is_numeric($key)) { + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + list($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 protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + protected function confirm($value, $rule, $data, $field = '') + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function different($value, $rule, $data) + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function egt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value >= $val; + } + + /** + * 验证是否大于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function gt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value > $val; + } + + /** + * 验证是否小于等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function elt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value <= $val; + } + + /** + * 验证是否小于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function lt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value < $val; + } + + /** + * 验证是否等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function eq($value, $rule) + { + return $value == $rule; + } + + /** + * 验证字段值是否为有效格式 + * @access protected + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 验证数据 + * @return bool + */ + protected function is($value, $rule, $data = []) + { + switch ($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 'alpha': + // 只允许字母 + $result = $this->regex($value, '/^[A-Za-z]+$/'); + break; + case 'alphaNum': + // 只允许字母和数字 + $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); + break; + case 'alphaDash': + // 只允许字母、数字和下划线 破折号 + $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); + break; + case 'chs': + // 只允许汉字 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); + break; + case 'chsAlpha': + // 只允许汉字、字母 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); + break; + case 'chsAlphaNum': + // 只允许汉字、字母和数字 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); + break; + case 'chsDash': + // 只允许汉字、字母、数字和下划线_及破折号- + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); + break; + case 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'ip': + // 是否为IP地址 + $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); + break; + case 'url': + // 是否为一个URL地址 + $result = $this->filter($value, FILTER_VALIDATE_URL); + break; + case 'float': + // 是否为float + $result = $this->filter($value, FILTER_VALIDATE_FLOAT); + break; + case 'number': + $result = is_numeric($value); + break; + case 'integer': + // 是否为整型 + $result = $this->filter($value, FILTER_VALIDATE_INT); + break; + case 'email': + // 是否为邮箱地址 + $result = $this->filter($value, FILTER_VALIDATE_EMAIL); + break; + case 'boolean': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + 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(self::$type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array(self::$type[$rule], [$value]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + return $result; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } else { + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function activeUrl($value, $rule) + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + protected function ip($value, $rule) + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 验证上传文件后缀 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileExt($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkExt($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkExt($rule); + } else { + return false; + } + } + + /** + * 验证上传文件类型 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileMime($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkMime($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkMime($rule); + } else { + return false; + } + } + + /** + * 验证上传文件大小 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileSize($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkSize($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkSize($rule); + } else { + return false; + } + } + + /** + * 验证图片的宽高及类型 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function image($file, $rule) + { + if (!($file instanceof File)) { + return false; + } + if ($rule) { + $rule = explode(',', $rule); + list($width, $height, $type) = getimagesize($file->getRealPath()); + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + if ('jpeg' == $imageType) { + $imageType = 'jpg'; + } + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + list($w, $h) = $rule; + return $w == $width && $h == $height; + } else { + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + } + + /** + * 验证请求类型 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function method($value, $rule) + { + $method = Request::instance()->method(); + return strtoupper($rule) == $method; + } + + /** + * 验证时间和日期是否符合指定格式 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function dateFormat($value, $rule) + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + protected function unique($value, $rule, $data, $field) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + try { + $db = Loader::model($rule[0]); + } catch (ClassNotFoundException $e) { + $db = Db::name($rule[0]); + } + } + $key = isset($rule[1]) ? $rule[1] : $field; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + if (isset($data[$key])) { + $map[$key] = $data[$key]; + } + } + } elseif (strpos($key, '=')) { + parse_str($key, $map); + } elseif (isset($data[$field])) { + $map[$key] = $data[$field]; + } else { + $map = []; + } + + $pk = isset($rule[3]) ? $rule[3] : $db->getPk(); + if (is_string($pk)) { + if (isset($rule[2])) { + $map[$pk] = ['neq', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[$pk] = ['neq', $data[$pk]]; + } + } + if ($db->where($map)->field($pk)->find()) { + return false; + } + return true; + } + + /** + * 使用行为类验证 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return mixed + */ + protected function behavior($value, $rule, $data) + { + return Hook::exec($rule, '', $data); + } + + /** + * 使用filter_var方式验证 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function filter($value, $rule) + { + if (is_string($rule) && strpos($rule, ',')) { + list($rule, $param) = explode(',', $rule); + } elseif (is_array($rule)) { + $param = isset($rule[1]) ? $rule[1] : null; + $rule = $rule[0]; + } else { + $param = null; + } + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireIf($value, $rule, $data) + { + list($field, $val) = explode(',', $rule); + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireCallback($value, $rule, $data) + { + $result = call_user_func_array($rule, [$value, $data]); + if ($result) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 验证某个字段有值的情况下必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireWith($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + if (!empty($val)) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 验证是否在范围内 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function in($value, $rule) + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function notIn($value, $rule) + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function between($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function notBetween($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function length($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (strpos($rule, ',')) { + // 长度区间 + list($min, $max) = explode(',', $rule); + return $length >= $min && $length <= $max; + } else { + // 指定长度 + return $length == $rule; + } + } + + /** + * 验证数据最大长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function max($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function min($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + return $length >= $rule; + } + + /** + * 验证日期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function after($value, $rule, $data) + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function before($value, $rule, $data) + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function afterWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function beforeWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function expire($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($start, $end) = $rule; + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; + } + + /** + * 验证IP许可 + * @access protected + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + protected function allowIp($value, $rule) + { + return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access protected + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + protected function denyIp($value, $rule) + { + return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return mixed + */ + protected function regex($value, $rule) + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } + if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 验证表单令牌 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function token($value, $rule, $data) + { + $rule = !empty($rule) ? $rule : '__token__'; + if (!isset($data[$rule]) || !Session::has($rule)) { + // 令牌数据无效 + return false; + } + + // 令牌验证 + if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) { + // 防止重复提交 + Session::delete($rule); // 验证完成销毁session + return true; + } + // 开启TOKEN重置 + Session::delete($rule); + return false; + } + + // 获取错误信息 + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持二维 + * @return mixed + */ + protected function getDataValue($data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (strpos($key, '.')) { + // 支持二维数组验证 + list($name1, $name2) = explode('.', $key); + $value = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null; + } else { + $value = isset($data[$key]) ? $data[$key] : null; + } + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string + */ + protected function getRuleMsg($attribute, $title, $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(self::$typeMsg[$type])) { + $msg = self::$typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = self::$typeMsg['require']; + } else { + $msg = $title . Lang::get('not conform to the rules'); + } + + if (is_string($msg) && 0 === strpos($msg, '{%')) { + $msg = Lang::get(substr($msg, 2, -1)); + } elseif (Lang::has($msg)) { + $msg = Lang::get($msg); + } + + if (is_string($msg) && 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', ':rule', ':1', ':2', ':3'], + [$title, (string) $rule, $array[0], $array[1], $array[2]], + $msg); + } + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return array + */ + protected function getScene($scene = '') + { + if (empty($scene)) { + // 读取指定场景 + $scene = $this->currentScene; + } + + if (!empty($scene) && isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $scene = $this->scene[$scene]; + if (is_string($scene)) { + $scene = explode(',', $scene); + } + } else { + $scene = []; + } + return $scene; + } + + public static function __callStatic($method, $params) + { + $class = self::make(); + if (method_exists($class, $method)) { + return call_user_func_array([$class, $method], $params); + } else { + throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/source/thinkphp/library/think/View.php b/source/thinkphp/library/think/View.php new file mode 100644 index 0000000..ca2dadb --- /dev/null +++ b/source/thinkphp/library/think/View.php @@ -0,0 +1,239 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class View +{ + // 视图实例 + protected static $instance; + // 模板引擎实例 + public $engine; + // 模板变量 + protected $data = []; + // 用于静态赋值的模板变量 + protected static $var = []; + // 视图输出替换 + protected $replace = []; + + /** + * 构造函数 + * @access public + * @param array $engine 模板引擎参数 + * @param array $replace 字符串替换参数 + */ + public function __construct($engine = [], $replace = []) + { + // 初始化模板引擎 + $this->engine($engine); + // 基础替换字符串 + $request = Request::instance(); + $base = $request->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DS) : $base; + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + $baseReplace = [ + '__ROOT__' => $root, + '__URL__' => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()), + '__STATIC__' => $root . '/static', + '__CSS__' => $root . '/static/css', + '__JS__' => $root . '/static/js', + ]; + $this->replace = array_merge($baseReplace, (array) $replace); + } + + /** + * 初始化视图 + * @access public + * @param array $engine 模板引擎参数 + * @param array $replace 字符串替换参数 + * @return object + */ + public static function instance($engine = [], $replace = []) + { + if (is_null(self::$instance)) { + self::$instance = new self($engine, $replace); + } + return self::$instance; + } + + /** + * 模板变量静态赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return void + */ + public static function share($name, $value = '') + { + if (is_array($name)) { + self::$var = array_merge(self::$var, $name); + } else { + self::$var[$name] = $value; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + return $this; + } + + /** + * 设置当前模板解析的引擎 + * @access public + * @param array|string $options 引擎参数 + * @return $this + */ + public function engine($options = []) + { + if (is_string($options)) { + $type = $options; + $options = []; + } else { + $type = !empty($options['type']) ? $options['type'] : 'Think'; + } + + $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type); + if (isset($options['type'])) { + unset($options['type']); + } + $this->engine = new $class($options); + return $this; + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function config($name, $value = null) + { + $this->engine->config($name, $value); + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @param bool $renderContent 是否渲染内容 + * @return string + * @throws Exception + */ + public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) + { + // 模板变量 + $vars = array_merge(self::$var, $this->data, $vars); + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 渲染输出 + try { + $method = $renderContent ? 'display' : 'fetch'; + // 允许用户自定义模板的字符串替换 + $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string')); + $this->engine->config('tpl_replace_string', $replace); + $this->engine->$method($template, $vars, $config); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + // 内容过滤标签 + Hook::listen('view_filter', $content); + return $content; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + public function display($content, $vars = [], $replace = [], $config = []) + { + return $this->fetch($content, $vars, $replace, $config, true); + } + + /** + * 模板变量赋值 + * @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]); + } +} diff --git a/source/thinkphp/library/think/cache/Driver.php b/source/thinkphp/library/think/cache/Driver.php new file mode 100644 index 0000000..07805e4 --- /dev/null +++ b/source/thinkphp/library/think/cache/Driver.php @@ -0,0 +1,231 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache; + +/** + * 缓存基础类 + */ +abstract class Driver +{ + protected $handler = null; + protected $options = []; + protected $tag; + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + abstract public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + abstract public function get($name, $default = false); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolean + */ + abstract public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function inc($name, $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function dec($name, $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + abstract public function rm($name); + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + abstract public function clear($tag = null); + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull($name) + { + $result = $this->get($name, false); + if ($result) { + $this->rm($name); + return $result; + } else { + return; + } + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (!$this->has($name)) { + $time = time(); + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + if ($value instanceof \Closure) { + $value = call_user_func($value); + } + $this->set($name, $value, $expire); + // 解锁 + $this->rm($name . '_lock'); + } catch (\Exception $e) { + // 解锁 + $this->rm($name . '_lock'); + throw $e; + } catch (\throwable $e) { + $this->rm($name . '_lock'); + throw $e; + } + } else { + $value = $this->get($name); + } + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($name)) { + + } elseif (is_null($keys)) { + $this->tag = $name; + } else { + $key = 'tag_' . md5($name); + if (is_string($keys)) { + $keys = explode(',', $keys); + } + $keys = array_map([$this, 'getCacheKey'], $keys); + if ($overlay) { + $value = $keys; + } else { + $value = array_unique(array_merge($this->getTagItem($name), $keys)); + } + $this->set($key, implode(',', $value), 0); + } + return $this; + } + + /** + * 更新标签 + * @access public + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $key = 'tag_' . md5($this->tag); + $this->tag = null; + if ($this->has($key)) { + $value = explode(',', $this->get($key)); + $value[] = $name; + $value = implode(',', array_unique($value)); + } else { + $value = $name; + } + $this->set($key, $value, 0); + } + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $key = 'tag_' . md5($tag); + $value = $this->get($key); + if ($value) { + return array_filter(explode(',', $value)); + } else { + return []; + } + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } +} diff --git a/source/thinkphp/library/think/cache/driver/File.php b/source/thinkphp/library/think/cache/driver/File.php new file mode 100644 index 0000000..fee6489 --- /dev/null +++ b/source/thinkphp/library/think/cache/driver/File.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class File extends Driver +{ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => CACHE_PATH, + 'data_compress' => false, + ]; + + protected $expire; + + /** + * 构造函数 + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + if (substr($this->options['path'], -1) != DS) { + $this->options['path'] .= DS; + } + $this->init(); + } + + /** + * 初始化检查 + * @access private + * @return boolean + */ + private function init() + { + // 创建项目缓存目录 + if (!is_dir($this->options['path'])) { + if (mkdir($this->options['path'], 0755, true)) { + return true; + } + } + return false; + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getCacheKey($name, $auto = false) + { + $name = md5($name); + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DS . substr($name, 2); + } + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DS . $name; + } + $filename = $this->options['path'] . $name . '.php'; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + mkdir($dir, 0755, true); + } + return $filename; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $filename = $this->getCacheKey($name); + if (!is_file($filename)) { + return $default; + } + $content = file_get_contents($filename); + $this->expire = null; + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() > filemtime($filename) + $expire) { + return $default; + } + $this->expire = $expire; + $content = substr($content, 32); + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + $content = unserialize($content); + return $content; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $filename = $this->getCacheKey($name, true); + if ($this->tag && !is_file($filename)) { + $first = true; + } + $data = 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) { + isset($first) && $this->setTagItem($filename); + clearstatcache(); + return true; + } else { + return false; + } + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + $expire = $this->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($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + $expire = $this->expire; + } else { + $value = -$step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $filename = $this->getCacheKey($name); + try { + return $this->unlink($filename); + } catch (\Exception $e) { + } + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); + foreach ($files as $path) { + if (is_dir($path)) { + $matches = glob($path . '/*.php'); + if (is_array($matches)) { + array_map('unlink', $matches); + } + rmdir($path); + } else { + unlink($path); + } + } + return true; + } + + /** + * 判断文件是否存在后,删除 + * @param $path + * @return bool + * @author byron sampson + * @return boolean + */ + private function unlink($path) + { + return is_file($path) && unlink($path); + } + +} diff --git a/source/thinkphp/library/think/cache/driver/Lite.php b/source/thinkphp/library/think/cache/driver/Lite.php new file mode 100644 index 0000000..8cbf08f --- /dev/null +++ b/source/thinkphp/library/think/cache/driver/Lite.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class Lite extends Driver +{ + protected $options = [ + 'prefix' => '', + 'path' => '', + 'expire' => 0, // 等于 10*365*24*3600(10年) + ]; + + /** + * 构造函数 + * @access public + * + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + if (substr($this->options['path'], -1) != DS) { + $this->options['path'] .= DS; + } + + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php'; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $filename = $this->getCacheKey($name); + if (is_file($filename)) { + // 判断是否过期 + $mtime = filemtime($filename); + if ($mtime < time()) { + // 清除已经过期的文件 + unlink($filename); + return $default; + } + return include $filename; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; + $expire = time() + $expire; + } + $filename = $this->getCacheKey($name); + if ($this->tag && !is_file($filename)) { + $first = true; + } + $ret = file_put_contents($filename, ("setTagItem($filename); + touch($filename, $expire); + } + return $ret; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); + } +} diff --git a/source/thinkphp/library/think/cache/driver/Memcache.php b/source/thinkphp/library/think/cache/driver/Memcache.php new file mode 100644 index 0000000..58703ea --- /dev/null +++ b/source/thinkphp/library/think/cache/driver/Memcache.php @@ -0,0 +1,177 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcache extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + * @throws \BadFunctionCallException + */ + public function __construct($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 = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) : + $this->handler->addServer($host, $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $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 = false) + { + $result = $this->handler->get($this->getCacheKey($name)); + return false !== $result ? $result : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + if ($this->handler->set($key, $value, 0, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $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($name, $step = 1) + { + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + if (!$res) { + return false; + } else { + return $value; + } + } + + /** + * 删除缓存 + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $key = $this->getCacheKey($name); + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flush(); + } +} diff --git a/source/thinkphp/library/think/cache/driver/Memcached.php b/source/thinkphp/library/think/cache/driver/Memcached.php new file mode 100644 index 0000000..5aab5a3 --- /dev/null +++ b/source/thinkphp/library/think/cache/driver/Memcached.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcached extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($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 = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $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) + { + $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 = false) + { + $result = $this->handler->get($this->getCacheKey($name)); + return false !== $result ? $result : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire; + if ($this->handler->set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $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($name, $step = 1) + { + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + if (!$res) { + return false; + } else { + return $value; + } + } + + /** + * 删除缓存 + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $key = $this->getCacheKey($name); + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + $this->handler->deleteMulti($keys); + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flush(); + } +} diff --git a/source/thinkphp/library/think/cache/driver/Redis.php b/source/thinkphp/library/think/cache/driver/Redis.php new file mode 100644 index 0000000..027b3ea --- /dev/null +++ b/source/thinkphp/library/think/cache/driver/Redis.php @@ -0,0 +1,188 @@ + +// +---------------------------------------------------------------------- + +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 +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('redis')) { + throw new \BadFunctionCallException('not support: redis'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->handler = new \Redis; + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + + if (0 != $this->options['select']) { + $this->handler->select($this->options['select']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->handler->exists($this->getCacheKey($name)); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $value = $this->handler->get($this->getCacheKey($name)); + if (is_null($value) || false === $value) { + return $default; + } + + try { + $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value; + } catch (\Exception $e) { + $result = $default; + } + + return $result; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value); + if ($expire) { + $result = $this->handler->setex($key, $expire, $value); + } else { + $result = $this->handler->set($key, $value); + } + isset($first) && $this->setTagItem($key); + return $result; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return $this->handler->delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flushDB(); + } + +} diff --git a/source/thinkphp/library/think/cache/driver/Sqlite.php b/source/thinkphp/library/think/cache/driver/Sqlite.php new file mode 100644 index 0000000..dc2ee05 --- /dev/null +++ b/source/thinkphp/library/think/cache/driver/Sqlite.php @@ -0,0 +1,199 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Sqlite缓存驱动 + * @author liu21st + */ +class Sqlite extends Driver +{ + protected $options = [ + 'db' => ':memory:', + 'table' => 'sharedmemory', + 'prefix' => '', + 'expire' => 0, + 'persistent' => false, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('sqlite')) { + throw new \BadFunctionCallException('not support: sqlite'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + $this->handler = $func($this->options['db']); + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . sqlite_escape_string($name); + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $name = $this->getCacheKey($name); + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + return sqlite_num_rows($result); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $name = $this->getCacheKey($name); + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + if (sqlite_num_rows($result)) { + $content = sqlite_fetch_single($result); + if (function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return unserialize($content); + } + return $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $name = $this->getCacheKey($name); + $value = sqlite_escape_string(serialize($value)); + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 + } + if (function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value, 3); + } + if ($this->tag) { + $tag = $this->tag; + $this->tag = null; + } else { + $tag = ''; + } + $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; + if (sqlite_query($this->handler, $sql)) { + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $name = $this->getCacheKey($name); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $name = sqlite_escape_string($tag); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + $sql = 'DELETE FROM ' . $this->options['table']; + sqlite_query($this->handler, $sql); + return true; + } +} diff --git a/source/thinkphp/library/think/cache/driver/Wincache.php b/source/thinkphp/library/think/cache/driver/Wincache.php new file mode 100644 index 0000000..03f8d35 --- /dev/null +++ b/source/thinkphp/library/think/cache/driver/Wincache.php @@ -0,0 +1,152 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + * @author liu21st + */ +class Wincache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + * @access public + */ + public function __construct($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) + { + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $key = $this->getCacheKey($name); + if ($this->tag && !$this->has($name)) { + $first = true; + } + if (wincache_ucache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + wincache_ucache_delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } else { + return wincache_ucache_clear(); + } + } + +} diff --git a/source/thinkphp/library/think/cache/driver/Xcache.php b/source/thinkphp/library/think/cache/driver/Xcache.php new file mode 100644 index 0000000..4d94c03 --- /dev/null +++ b/source/thinkphp/library/think/cache/driver/Xcache.php @@ -0,0 +1,155 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Xcache缓存驱动 + * @author liu21st + */ +class Xcache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!function_exists('xcache_info')) { + throw new \BadFunctionCallException('not support: Xcache'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return xcache_isset($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $key = $this->getCacheKey($name); + return xcache_isset($key) ? xcache_get($key) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + if (xcache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + return xcache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + return xcache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return xcache_unset($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + xcache_unset($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + if (function_exists('xcache_unset_by_prefix')) { + return xcache_unset_by_prefix($this->options['prefix']); + } else { + return false; + } + } +} diff --git a/source/thinkphp/library/think/config/driver/Ini.php b/source/thinkphp/library/think/config/driver/Ini.php new file mode 100644 index 0000000..bcd12b6 --- /dev/null +++ b/source/thinkphp/library/think/config/driver/Ini.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Ini +{ + public function parse($config) + { + if (is_file($config)) { + return parse_ini_file($config, true); + } else { + return parse_ini_string($config, true); + } + } +} diff --git a/source/thinkphp/library/think/config/driver/Json.php b/source/thinkphp/library/think/config/driver/Json.php new file mode 100644 index 0000000..479dcc8 --- /dev/null +++ b/source/thinkphp/library/think/config/driver/Json.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Json +{ + public function parse($config) + { + if (is_file($config)) { + $config = file_get_contents($config); + } + $result = json_decode($config, true); + return $result; + } +} diff --git a/source/thinkphp/library/think/config/driver/Xml.php b/source/thinkphp/library/think/config/driver/Xml.php new file mode 100644 index 0000000..1158519 --- /dev/null +++ b/source/thinkphp/library/think/config/driver/Xml.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Xml +{ + public function parse($config) + { + if (is_file($config)) { + $content = simplexml_load_file($config); + } else { + $content = simplexml_load_string($config); + } + $result = (array) $content; + foreach ($result as $key => $val) { + if (is_object($val)) { + $result[$key] = (array) $val; + } + } + return $result; + } +} diff --git a/source/thinkphp/library/think/console/Command.php b/source/thinkphp/library/think/console/Command.php new file mode 100644 index 0000000..d0caad2 --- /dev/null +++ b/source/thinkphp/library/think/console/Command.php @@ -0,0 +1,470 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Command +{ + + /** @var Console */ + private $console; + private $name; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $code; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** + * 构造方法 + * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置 + * @throws \LogicException + * @api + */ + public function __construct($name = null) + { + $this->definition = new Definition(); + + if (null !== $name) { + $this->setName($name); + } + + $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() + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null) + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole() + { + return $this->console; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled() + { + 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) + { + throw new \LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * 用户验证 + * @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) + { + $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 ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 设置执行代码 + * @param callable $code callable(InputInterface $input, OutputInterface $output) + * @return Command + * @throws \InvalidArgumentException + * @see execute() + */ + public function setCode(callable $code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + $code = \Closure::bind($code, $this); + } + } + + $this->code = $code; + + return $this; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition($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() + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument($name, $mode = null, $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($name, $shortcut = null, $mode = null, $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($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp() + { + return $this->help; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp() + { + $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($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis($short = false) + { + $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($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws \InvalidArgumentException + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/source/thinkphp/library/think/console/Input.php b/source/thinkphp/library/think/console/Input.php new file mode 100644 index 0000000..2482dfd --- /dev/null +++ b/source/thinkphp/library/think/console/Input.php @@ -0,0 +1,464 @@ + +// +---------------------------------------------------------------------- + +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) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse() + { + $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($token) + { + $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($name) + { + $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($token) + { + $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($token) + { + $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($shortcut, $value) + { + 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($name, $value) + { + 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) + { + $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() + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument($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) + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption($name, $value) + { + 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($name) + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken($token) + { + 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/source/thinkphp/library/think/console/LICENSE b/source/thinkphp/library/think/console/LICENSE new file mode 100644 index 0000000..0abe056 --- /dev/null +++ b/source/thinkphp/library/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/source/thinkphp/library/think/console/Output.php b/source/thinkphp/library/think/console/Output.php new file mode 100644 index 0000000..65dc9fb --- /dev/null +++ b/source/thinkphp/library/think/console/Output.php @@ -0,0 +1,222 @@ + +// +---------------------------------------------------------------------- + +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; + +/** + * 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($style, $message) + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine($count = 1) + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(\Exception $e) + { + $this->handle->renderException($e); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []) + { + $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/source/thinkphp/library/think/console/bin/README.md b/source/thinkphp/library/think/console/bin/README.md new file mode 100644 index 0000000..9acc52f --- /dev/null +++ b/source/thinkphp/library/think/console/bin/README.md @@ -0,0 +1 @@ +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/source/thinkphp/library/think/console/bin/hiddeninput.exe b/source/thinkphp/library/think/console/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/source/thinkphp/library/think/console/bin/hiddeninput.exe differ diff --git a/source/thinkphp/library/think/console/command/Build.php b/source/thinkphp/library/think/console/command/Build.php new file mode 100644 index 0000000..39806c3 --- /dev/null +++ b/source/thinkphp/library/think/console/command/Build.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class Build extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('build') + ->setDefinition([ + new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"), + new Option('module', null, Option::VALUE_OPTIONAL, "module name"), + ]) + ->setDescription('Build Application Dirs'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->hasOption('module')) { + \think\Build::module($input->getOption('module')); + $output->writeln("Successed"); + return; + } + + if ($input->hasOption('config')) { + $build = include $input->getOption('config'); + } else { + $build = include APP_PATH . 'build.php'; + } + if (empty($build)) { + $output->writeln("Build Config Is Empty"); + return; + } + \think\Build::run($build); + $output->writeln("Successed"); + + } +} diff --git a/source/thinkphp/library/think/console/command/Clear.php b/source/thinkphp/library/think/console/command/Clear.php new file mode 100644 index 0000000..1b5102e --- /dev/null +++ b/source/thinkphp/library/think/console/command/Clear.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\Cache; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; + +class Clear extends Command +{ + protected function configure() + { + // 指令配置 + $this + ->setName('clear') + ->addArgument('type', Argument::OPTIONAL, 'type to clear', null) + ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + $path = $input->getOption('path') ?: RUNTIME_PATH; + + $type = $input->getArgument('type'); + + if ($type == 'route') { + Cache::clear('route_check'); + } else { + if (is_dir($path)) { + $this->clearPath($path); + } + } + + $output->writeln("Clear Successed"); + } + + protected function clearPath($path) + { + $path = realpath($path) . DS; + $files = scandir($path); + if ($files) { + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + $this->clearPath($path . $file); + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); + } + } + } + } +} diff --git a/source/thinkphp/library/think/console/command/Help.php b/source/thinkphp/library/think/console/command/Help.php new file mode 100644 index 0000000..bae2c65 --- /dev/null +++ b/source/thinkphp/library/think/console/command/Help.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- + +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) + { + $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/source/thinkphp/library/think/console/command/Lists.php b/source/thinkphp/library/think/console/command/Lists.php new file mode 100644 index 0000000..084ddaa --- /dev/null +++ b/source/thinkphp/library/think/console/command/Lists.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\input\Definition as InputDefinition; + +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() + { + 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() + { + 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/source/thinkphp/library/think/console/command/Make.php b/source/thinkphp/library/think/console/command/Make.php new file mode 100644 index 0000000..d1daf34 --- /dev/null +++ b/source/thinkphp/library/think/console/command/Make.php @@ -0,0 +1,110 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\App; +use think\Config; +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 . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(strtolower(dirname($pathname)), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ' created successfully.'); + + } + + protected function buildClass($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%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + $namespace, + App::$namespace, + ], $stub); + + } + + protected function getPathName($name) + { + $name = str_replace(App::$namespace . '\\', '', $name); + + return APP_PATH . str_replace('\\', '/', $name) . '.php'; + } + + protected function getClassName($name) + { + $appNamespace = App::$namespace; + + if (strpos($name, $appNamespace . '\\') === 0) { + return $name; + } + + if (Config::get('app_multi_module')) { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = 'common'; + } + } else { + $module = null; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($appNamespace, $module) . '\\' . $name; + } + + protected function getNamespace($appNamespace, $module) + { + return $module ? ($appNamespace . '\\' . $module) : $appNamespace; + } + +} diff --git a/source/thinkphp/library/think/console/command/make/Controller.php b/source/thinkphp/library/think/console/command/make/Controller.php new file mode 100644 index 0000000..afa7be9 --- /dev/null +++ b/source/thinkphp/library/think/console/command/make/Controller.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\Config; +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('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub() + { + if ($this->input->getOption('plain')) { + return __DIR__ . '/stubs/controller.plain.stub'; + } + + return __DIR__ . '/stubs/controller.stub'; + } + + protected function getClassName($name) + { + return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''); + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\controller'; + } + +} diff --git a/source/thinkphp/library/think/console/command/make/Model.php b/source/thinkphp/library/think/console/command/make/Model.php new file mode 100644 index 0000000..d4e9b5d --- /dev/null +++ b/source/thinkphp/library/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() + { + return __DIR__ . '/stubs/model.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\model'; + } +} diff --git a/source/thinkphp/library/think/console/command/make/stubs/controller.plain.stub b/source/thinkphp/library/think/console/command/make/stubs/controller.plain.stub new file mode 100644 index 0000000..b7539dc --- /dev/null +++ b/source/thinkphp/library/think/console/command/make/stubs/controller.plain.stub @@ -0,0 +1,10 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\App; +use think\Config; +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Autoload extends Command +{ + + protected function configure() + { + $this->setName('optimize:autoload') + ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'); + } + + protected function execute(Input $input, Output $output) + { + + $classmapFile = << realpath(rtrim(APP_PATH)), + 'think\\' => LIB_PATH . 'think', + 'behavior\\' => LIB_PATH . 'behavior', + 'traits\\' => LIB_PATH . 'traits', + '' => realpath(rtrim(EXTEND_PATH)), + ]; + + $root_namespace = Config::get('root_namespace'); + foreach ($root_namespace as $namespace => $dir) { + $namespacesToScan[$namespace . '\\'] = realpath($dir); + } + + krsort($namespacesToScan); + $classMap = []; + foreach ($namespacesToScan as $namespace => $dir) { + + if (!is_dir($dir)) { + continue; + } + + $namespaceFilter = $namespace === '' ? null : $namespace; + $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); + } + + ksort($classMap); + foreach ($classMap as $class => $code) { + $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; + } + $classmapFile .= "];\n"; + + if (!is_dir(RUNTIME_PATH)) { + @mkdir(RUNTIME_PATH, 0755, true); + } + + file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile); + + $output->writeln('Succeed!'); + } + + protected function addClassMapCode($dir, $namespace, $classMap) + { + foreach ($this->createMap($dir, $namespace) as $class => $path) { + + $pathCode = $this->getPathCode($path) . ",\n"; + + if (!isset($classMap[$class])) { + $classMap[$class] = $pathCode; + } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . str_replace(["',\n"], [ + '', + ], $classMap[$class]) . '" and "' . $path . '", the first will be used.' + ); + } + } + return $classMap; + } + + protected function getPathCode($path) + { + + $baseDir = ''; + $libPath = $this->normalizePath(realpath(LIB_PATH)); + $appPath = $this->normalizePath(realpath(APP_PATH)); + $extendPath = $this->normalizePath(realpath(EXTEND_PATH)); + $rootPath = $this->normalizePath(realpath(ROOT_PATH)); + $path = $this->normalizePath($path); + + if ($libPath !== null && strpos($path, $libPath . '/') === 0) { + $path = substr($path, strlen(LIB_PATH)); + $baseDir = 'LIB_PATH'; + } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) { + $path = substr($path, strlen($appPath) + 1); + $baseDir = 'APP_PATH'; + } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) { + $path = substr($path, strlen($extendPath) + 1); + $baseDir = 'EXTEND_PATH'; + } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) { + $path = substr($path, strlen($rootPath) + 1); + $baseDir = 'ROOT_PATH'; + } + + if ($path !== false) { + $baseDir .= " . "; + } + + return $baseDir . (($path !== false) ? var_export($path, true) : ""); + } + + protected function normalizePath($path) + { + if ($path === false) { + return; + } + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = false; + + if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, strlen($prefix)); + } + + if (substr($path, 0, 1) === '/') { + $absolute = true; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && ($absolute || $up)) { + array_pop($parts); + $up = !(empty($parts) || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + return $prefix . ($absolute ? '/' : '') . implode('/', $parts); + } + + protected function createMap($path, $namespace = null) + { + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path)) { + + $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST); + + $path = []; + + /** @var \SplFileInfo $object */ + foreach ($objects as $object) { + if ($object->isFile() && $object->getExtension() == 'php') { + $path[] = $object; + } + } + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "' . $path . + '" which does not appear to be a file nor a folder' + ); + } + } + + $map = []; + + /** @var \SplFileInfo $file */ + foreach ($path as $file) { + $filePath = $file->getRealPath(); + + if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') { + continue; + } + + $classes = $this->findClasses($filePath); + + foreach ($classes as $class) { + if (null !== $namespace && 0 !== strpos($class, $namespace)) { + continue; + } + + if (!isset($map[$class])) { + $map[$class] = $filePath; + } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' + ); + } + } + } + + return $map; + } + + protected function findClasses($path) + { + $extraTypes = '|trait'; + + $contents = @php_strip_whitespace($path); + if (!$contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!is_readable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim(file_get_contents($path))) { + return []; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); + } + + if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) { + return []; + } + + // strip heredocs/nowdocs + $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); + // strip strings + $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + // strip leading non-php code if needed + if (substr($contents, 0, 2) !== '.+<\?}s', '?>'); + if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (!empty($matches['ns'][$i])) { + $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + if ($name[0] === ':') { + $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); + } elseif ($matches['type'][$i] === 'enum') { + $name = rtrim($name, ':'); + } + $classes[] = ltrim($namespace . $name, '\\'); + } + } + + return $classes; + } + +} diff --git a/source/thinkphp/library/think/console/command/optimize/Config.php b/source/thinkphp/library/think/console/command/optimize/Config.php new file mode 100644 index 0000000..59c69a8 --- /dev/null +++ b/source/thinkphp/library/think/console/command/optimize/Config.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\Config as ThinkConfig; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +class Config extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:config') + ->addArgument('module', Argument::OPTIONAL, 'Build module config cache .') + ->setDescription('Build config and common file cache.'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->getArgument('module')) { + $module = $input->getArgument('module') . DS; + } else { + $module = ''; + } + + $content = 'buildCacheContent($module); + + if (!is_dir(RUNTIME_PATH . $module)) { + @mkdir(RUNTIME_PATH . $module, 0755, true); + } + + file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content); + + $output->writeln('Succeed!'); + } + + protected function buildCacheContent($module) + { + $content = ''; + $path = realpath(APP_PATH . $module) . DS; + + if ($module) { + // 加载模块配置 + $config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT); + + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + ThinkConfig::load($filename, 'database'); + + // 加载应用状态配置 + if ($config['app_status']) { + $config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if (strpos($file, CONF_EXT)) { + $filename = $dir . DS . $file; + ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + $content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL; + } + + // 加载公共文件 + if (is_file($path . 'common' . EXT)) { + $content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL; + } + + $content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');'; + return $content; + } +} diff --git a/source/thinkphp/library/think/console/command/optimize/Route.php b/source/thinkphp/library/think/console/command/optimize/Route.php new file mode 100644 index 0000000..6da1d9a --- /dev/null +++ b/source/thinkphp/library/think/console/command/optimize/Route.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Route extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:route') + ->setDescription('Build route cache.'); + } + + protected function execute(Input $input, Output $output) + { + + if (!is_dir(RUNTIME_PATH)) { + @mkdir(RUNTIME_PATH, 0755, true); + } + + file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache()); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache() + { + $files = \think\Config::get('route_config_file'); + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + $config = include CONF_PATH . $file . CONF_EXT; + if (is_array($config)) { + \think\Route::import($config); + } + } + } + $rules = \think\Route::rules(true); + array_walk_recursive($rules, [$this, 'buildClosure']); + $content = 'getStartLine(); + $endLine = $reflection->getEndLine(); + $file = $reflection->getFileName(); + $item = file($file); + $content = ''; + for ($i = $startLine - 1; $i <= $endLine - 1; $i++) { + $content .= $item[$i]; + } + $start = strpos($content, 'function'); + $end = strrpos($content, '}'); + $value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]'; + } + } +} diff --git a/source/thinkphp/library/think/console/command/optimize/Schema.php b/source/thinkphp/library/think/console/command/optimize/Schema.php new file mode 100644 index 0000000..3353424 --- /dev/null +++ b/source/thinkphp/library/think/console/command/optimize/Schema.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\App; +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\Db; + +class Schema extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:schema') + ->addOption('config', null, Option::VALUE_REQUIRED, 'db config .') + ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + if (!is_dir(RUNTIME_PATH . 'schema')) { + @mkdir(RUNTIME_PATH . 'schema', 0755, true); + } + $config = []; + if ($input->hasOption('config')) { + $config = $input->getOption('config'); + } + if ($input->hasOption('module')) { + $module = $input->getOption('module'); + // 读取模型 + $path = APP_PATH . $module . DS . 'model'; + $list = is_dir($path) ? scandir($path) : []; + $app = App::$namespace; + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + $output->writeln('Succeed!'); + return; + } elseif ($input->hasOption('table')) { + $table = $input->getOption('table'); + if (!strpos($table, '.')) { + $dbName = Db::connect($config)->getConfig('database'); + } + $tables[] = $table; + } elseif ($input->hasOption('db')) { + $dbName = $input->getOption('db'); + $tables = Db::connect($config)->getTables($dbName); + } elseif (!\think\Config::get('app_multi_module')) { + $app = App::$namespace; + $path = APP_PATH . 'model'; + $list = is_dir($path) ? scandir($path) : []; + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + $output->writeln('Succeed!'); + return; + } else { + $tables = Db::connect($config)->getTables(); + } + + $db = isset($dbName) ? $dbName . '.' : ''; + $this->buildDataBaseSchema($tables, $db, $config); + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema($class) + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + $table = $class::getTable(); + $dbName = $class::getConfig('database'); + $content = 'getFields($table); + $content .= var_export($info, true) . ';'; + file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content); + } + } + + protected function buildDataBaseSchema($tables, $db, $config) + { + if ('' == $db) { + $dbName = Db::connect($config)->getConfig('database') . '.'; + } else { + $dbName = $db; + } + foreach ($tables as $table) { + $content = 'getFields($db . $table); + $content .= var_export($info, true) . ';'; + file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content); + } + } +} diff --git a/source/thinkphp/library/think/console/input/Argument.php b/source/thinkphp/library/think/console/input/Argument.php new file mode 100644 index 0000000..16223bb --- /dev/null +++ b/source/thinkphp/library/think/console/input/Argument.php @@ -0,0 +1,115 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Argument +{ + + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + 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($name, $mode = null, $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() + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + 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() + { + return $this->description; + } +} diff --git a/source/thinkphp/library/think/console/input/Definition.php b/source/thinkphp/library/think/console/input/Definition.php new file mode 100644 index 0000000..c71977e --- /dev/null +++ b/source/thinkphp/library/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) + { + $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($arguments = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments($arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument) + { + 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) + { + 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) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults() + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions($options = []) + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions($options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option) + { + 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($name) + { + 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($name) + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions() + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults() + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName($shortcut) + { + 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($short = false) + { + $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/source/thinkphp/library/think/console/input/Option.php b/source/thinkphp/library/think/console/input/Option.php new file mode 100644 index 0000000..e5707c9 --- /dev/null +++ b/source/thinkphp/library/think/console/input/Option.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Option +{ + + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + 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/source/thinkphp/library/think/console/output/Ask.php b/source/thinkphp/library/think/console/output/Ask.php new file mode 100644 index 0000000..3933eb2 --- /dev/null +++ b/source/thinkphp/library/think/console/output/Ask.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +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(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + 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/source/thinkphp/library/think/console/output/Descriptor.php b/source/thinkphp/library/think/console/output/Descriptor.php new file mode 100644 index 0000000..23dc648 --- /dev/null +++ b/source/thinkphp/library/think/console/output/Descriptor.php @@ -0,0 +1,319 @@ + +// +---------------------------------------------------------------------- + +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 void + */ + 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 = isset($options['total_width']) ? $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 void + */ + 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 = isset($options['total_width']) ? $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 void + */ + 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 void + */ + 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 void + */ + 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->getCommands()); + + 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->getCommands()); + + 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 Command[] $commands + * @return int + */ + private function getColumnWidth(array $commands) + { + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + + 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/source/thinkphp/library/think/console/output/Formatter.php b/source/thinkphp/library/think/console/output/Formatter.php new file mode 100644 index 0000000..f8bee55 --- /dev/null +++ b/source/thinkphp/library/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 = isset($matches[3][$i][0]) ? $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/source/thinkphp/library/think/console/output/Question.php b/source/thinkphp/library/think/console/output/Question.php new file mode 100644 index 0000000..03975f2 --- /dev/null +++ b/source/thinkphp/library/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/source/thinkphp/library/think/console/output/descriptor/Console.php b/source/thinkphp/library/think/console/output/descriptor/Console.php new file mode 100644 index 0000000..4648b68 --- /dev/null +++ b/source/thinkphp/library/think/console/output/descriptor/Console.php @@ -0,0 +1,149 @@ + +// +---------------------------------------------------------------------- + +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() + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectConsole() + { + $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 (!$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) + { + $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/source/thinkphp/library/think/console/output/driver/Buffer.php b/source/thinkphp/library/think/console/output/driver/Buffer.php new file mode 100644 index 0000000..c77a2ec --- /dev/null +++ b/source/thinkphp/library/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, $newline = false, $options = Output::OUTPUT_NORMAL) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Exception $e) + { + // do nothing + } + +} diff --git a/source/thinkphp/library/think/console/output/driver/Console.php b/source/thinkphp/library/think/console/output/driver/Console.php new file mode 100644 index 0000000..8f29fd0 --- /dev/null +++ b/source/thinkphp/library/think/console/output/driver/Console.php @@ -0,0 +1,373 @@ + +// +---------------------------------------------------------------------- + +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 getFormatter() + { + return $this->formatter; + } + + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $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(\Exception $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() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DS) { + 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) + { + 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, $width) + { + 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() + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport() + { + 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) + { + 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/source/thinkphp/library/think/console/output/driver/Nothing.php b/source/thinkphp/library/think/console/output/driver/Nothing.php new file mode 100644 index 0000000..9a55f77 --- /dev/null +++ b/source/thinkphp/library/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, $newline = false, $options = Output::OUTPUT_NORMAL) + { + // do nothing + } + + public function renderException(\Exception $e) + { + // do nothing + } +} diff --git a/source/thinkphp/library/think/console/output/formatter/Stack.php b/source/thinkphp/library/think/console/output/formatter/Stack.php new file mode 100644 index 0000000..4864a3f --- /dev/null +++ b/source/thinkphp/library/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() + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style) + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null) + { + 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() + { + 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() + { + return $this->emptyStyle; + } +} diff --git a/source/thinkphp/library/think/console/output/formatter/Style.php b/source/thinkphp/library/think/console/output/formatter/Style.php new file mode 100644 index 0000000..d9b0999 --- /dev/null +++ b/source/thinkphp/library/think/console/output/formatter/Style.php @@ -0,0 +1,189 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + + private 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], + ]; + private 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], + ]; + private 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($option) + { + 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($option) + { + 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($text) + { + $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/source/thinkphp/library/think/console/output/question/Choice.php b/source/thinkphp/library/think/console/output/question/Choice.php new file mode 100644 index 0000000..f6760e5 --- /dev/null +++ b/source/thinkphp/library/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() + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage($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 (empty($result)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/source/thinkphp/library/think/console/output/question/Confirmation.php b/source/thinkphp/library/think/console/output/question/Confirmation.php new file mode 100644 index 0000000..6598f9b --- /dev/null +++ b/source/thinkphp/library/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($question, $default = true, $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/source/thinkphp/library/think/controller/Rest.php b/source/thinkphp/library/think/controller/Rest.php new file mode 100644 index 0000000..43ab2f6 --- /dev/null +++ b/source/thinkphp/library/think/controller/Rest.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- + +namespace think\controller; + +use think\App; +use think\Request; +use think\Response; + +abstract class Rest +{ + + protected $method; // 当前请求类型 + protected $type; // 当前资源类型 + // 输出类型 + protected $restMethodList = 'get|post|put|delete'; + protected $restDefaultMethod = 'get'; + protected $restTypeList = 'html|xml|json|rss'; + protected $restDefaultType = 'html'; + protected $restOutputType = [ // REST允许输出的资源类型列表 + 'xml' => 'application/xml', + 'json' => 'application/json', + 'html' => 'text/html', + ]; + + /** + * 构造函数 取得模板对象实例 + * @access public + */ + public function __construct() + { + // 资源类型检测 + $request = Request::instance(); + $ext = $request->ext(); + if ('' == $ext) { + // 自动检测资源类型 + $this->type = $request->type(); + } elseif (!preg_match('/(' . $this->restTypeList . ')$/i', $ext)) { + // 资源类型非法 则用默认资源类型访问 + $this->type = $this->restDefaultType; + } else { + $this->type = $ext; + } + // 请求方式检测 + $method = strtolower($request->method()); + if (!preg_match('/(' . $this->restMethodList . ')$/i', $method)) { + // 请求方式非法 则用默认请求方法 + $method = $this->restDefaultMethod; + } + $this->method = $method; + } + + /** + * REST 调用 + * @access public + * @param string $method 方法名 + * @return mixed + * @throws \Exception + */ + public function _empty($method) + { + if (method_exists($this, $method . '_' . $this->method . '_' . $this->type)) { + // RESTFul方法支持 + $fun = $method . '_' . $this->method . '_' . $this->type; + } elseif ($this->method == $this->restDefaultMethod && method_exists($this, $method . '_' . $this->type)) { + $fun = $method . '_' . $this->type; + } elseif ($this->type == $this->restDefaultType && method_exists($this, $method . '_' . $this->method)) { + $fun = $method . '_' . $this->method; + } + if (isset($fun)) { + return App::invokeMethod([$this, $fun]); + } else { + // 抛出异常 + throw new \Exception('error action :' . $method); + } + } + + /** + * 输出返回数据 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type 返回类型 JSON XML + * @param integer $code HTTP状态码 + * @return Response + */ + protected function response($data, $type = 'json', $code = 200) + { + return Response::create($data, $type)->code($code); + } + +} diff --git a/source/thinkphp/library/think/controller/Yar.php b/source/thinkphp/library/think/controller/Yar.php new file mode 100644 index 0000000..af4e9a1 --- /dev/null +++ b/source/thinkphp/library/think/controller/Yar.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\controller; + +/** + * ThinkPHP Yar控制器类 + */ +abstract class Yar +{ + + /** + * 构造函数 + * @access public + */ + public function __construct() + { + //控制器初始化 + if (method_exists($this, '_initialize')) { + $this->_initialize(); + } + + //判断扩展是否存在 + if (!extension_loaded('yar')) { + throw new \Exception('not support yar'); + } + + //实例化Yar_Server + $server = new \Yar_Server($this); + // 启动server + $server->handle(); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method, $args) + {} +} diff --git a/source/thinkphp/library/think/db/Builder.php b/source/thinkphp/library/think/db/Builder.php new file mode 100644 index 0000000..58b45aa --- /dev/null +++ b/source/thinkphp/library/think/db/Builder.php @@ -0,0 +1,899 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\Exception; + +abstract class Builder +{ + // connection对象实例 + protected $connection; + // 查询对象实例 + protected $query; + + // 数据库表达式 + protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME']; + + // SQL表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT%%LOCK%%COMMENT%'; + protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 构造函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + * @param Query $query 数据库查询对象实例 + */ + public function __construct(Connection $connection, Query $query) + { + $this->connection = $connection; + $this->query = $query; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 获取当前的Query对象实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access protected + * @param string $sql sql语句 + * @return string + */ + protected function parseSqlTable($sql) + { + return $this->query->parseSqlTable($sql); + } + + /** + * 数据分析 + * @access protected + * @param array $data 数据 + * @param array $options 查询参数 + * @return array + * @throws Exception + */ + protected function parseData($data, $options) + { + if (empty($data)) { + return []; + } + + // 获取绑定信息 + $bind = $this->query->getFieldsBind($options['table']); + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + + $result = []; + foreach ($data as $key => $val) { + if ('*' != $options['field'] && !in_array($key, $fields, true)) { + continue; + } + + $item = $this->parseKey($key, $options, true); + if ($val instanceof Expression) { + $result[$item] = $val->getValue(); + continue; + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $val = $val->__toString(); + } + if (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)) { + switch (strtolower($val[0])) { + case 'inc': + $result[$item] = $item . '+' . floatval($val[1]); + break; + case 'dec': + $result[$item] = $item . '-' . floatval($val[1]); + break; + case 'exp': + throw new Exception('not support data:[' . $val[0] . ']'); + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { + $result[$item] = $val; + } else { + $key = str_replace('.', '_', $key); + $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + $result[$item] = ':data__' . $key; + } + } + } + return $result; + } + + /** + * 字段名分析 + * @access protected + * @param string $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + return $key; + } + + /** + * value分析 + * @access protected + * @param mixed $value + * @param string $field + * @return string|array + */ + protected function parseValue($value, $field = '') + { + if (is_string($value)) { + $value = strpos($value, ':') === 0 && $this->query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value); + } elseif (is_array($value)) { + $value = array_map([$this, 'parseValue'], $value); + } elseif (is_bool($value)) { + $value = $value ? '1' : '0'; + } elseif (is_null($value)) { + $value = 'null'; + } + return $value; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @param array $options + * @return string + */ + protected function parseField($fields, $options = []) + { + if ('*' == $fields || empty($fields)) { + $fieldsStr = '*'; + } elseif (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + foreach ($fields as $key => $field) { + if ($field instanceof Expression) { + $array[] = $field->getValue(); + } elseif (!is_numeric($key)) { + $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options, true); + } else { + $array[] = $this->parseKey($field, $options); + } + } + $fieldsStr = implode(',', $array); + } + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param mixed $tables + * @param array $options + * @return string + */ + protected function parseTable($tables, $options = []) + { + $item = []; + foreach ((array) $tables as $key => $table) { + if (!is_numeric($key)) { + $key = $this->parseSqlTable($key); + $item[] = $this->parseKey($key) . ' ' . (isset($options['alias'][$table]) ? $this->parseKey($options['alias'][$table]) : $this->parseKey($table)); + } else { + $table = $this->parseSqlTable($table); + if (isset($options['alias'][$table])) { + $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); + } else { + $item[] = $this->parseKey($table); + } + } + } + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param mixed $where 查询条件 + * @param array $options 查询参数 + * @return string + */ + protected function parseWhere($where, $options) + { + $whereStr = $this->buildWhere($where, $options); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + list($field, $condition) = $options['soft_delete']; + + $binds = $this->query->getFieldsBind($options['table']); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); + } + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param mixed $where + * @param array $options + * @return string + */ + public function buildWhere($where, $options) + { + if (empty($where)) { + $where = []; + } + + if ($where instanceof Query) { + return $this->buildWhere($where->getOptions('where'), $options); + } + + $whereStr = ''; + $binds = $this->query->getFieldsBind($options['table']); + foreach ($where as $key => $val) { + $str = []; + foreach ($val as $field => $value) { + if ($value instanceof Expression) { + $str[] = ' ' . $key . ' ( ' . $value->getValue() . ' )'; + } elseif ($value instanceof \Closure) { + // 使用闭包查询 + $query = new Query($this->connection); + call_user_func_array($value, [ & $query]); + $whereClause = $this->buildWhere($query->getOptions('where'), $options); + if (!empty($whereClause)) { + $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + } + } elseif (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + $item = []; + foreach ($array as $k) { + $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + } + $str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + $item = []; + foreach ($array as $k) { + $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + } + $str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key, $options, $binds); + } + } + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + // where子单元分析 + protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) + { + // 字段分析 + $key = $field ? $this->parseKey($field, $options, true) : ''; + + // 查询规则和条件 + if (!is_array($val)) { + $val = is_null($val) ? ['null', ''] : ['=', $val]; + } + list($exp, $value) = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $item = array_pop($val); + // 传入 or 或者 and + if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { + $rule = $item; + } else { + array_push($val, $item); + } + foreach ($val as $k => $item) { + $bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k; + $str[] = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName); + } + return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; + } + + // 检测操作符 + if (!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); + } + } + $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field); + if (preg_match('/\W/', $bindName)) { + // 处理带非单词字符的字段名 + $bindName = md5($bindName); + } + + if ($value instanceof Expression) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; + if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { + if ($this->query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + $this->query->bind($bindName, $value, $bindType); + $value = ':' . $bindName; + } + } + + $whereStr = ''; + if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { + // 比较运算 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } + } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $item) { + $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); + } + $logic = isset($val[2]) ? $val[2] : 'AND'; + $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } + } elseif ('EXP' == $exp) { + // 表达式查询 + if ($value instanceof Expression) { + $whereStr .= '( ' . $key . ' ' . $value->getValue() . ' )'; + } else { + throw new Exception('where express error:' . $exp); + } + } elseif (in_array($exp, ['NOT NULL', 'NULL'])) { + // NULL 查询 + $whereStr .= $key . ' IS ' . $exp; + } elseif (in_array($exp, ['NOT IN', 'IN'])) { + // IN 查询 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + if (array_key_exists($field, $binds)) { + $bind = []; + $array = []; + $i = 0; + foreach ($value as $v) { + $i++; + if ($this->query->isBind($bindName . '_in_' . $i)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; + } else { + $bindKey = $bindName . '_in_' . $i; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; + } + $this->query->bind($bind); + $zone = implode(',', $array); + } else { + $zone = implode(',', $this->parseValue($value, $field)); + } + $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; + } + } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + if (array_key_exists($field, $binds)) { + if ($this->query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } + $bind = [ + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], + ]; + $this->query->bind($bind); + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; + } else { + $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); + } + $whereStr .= $key . ' ' . $exp . ' ' . $between; + } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { + // EXISTS 查询 + if ($value instanceof \Closure) { + $whereStr .= $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $exp . ' (' . $value . ')'; + } + } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) { + $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType); + } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) { + if (is_string($value)) { + $value = explode(',', $value); + } + + $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType); + } + return $whereStr; + } + + // 执行闭包子查询 + protected function parseClosure($call, $show = true) + { + $query = new Query($this->connection); + call_user_func_array($call, [ & $query]); + return $query->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param string $value + * @param string $key + * @param array $options + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) + { + // 获取时间字段类型 + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key); + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + $type = $this->query->getTableInfo($table, 'type'); + if (isset($type[$key])) { + $info = $type[$key]; + } + if (isset($info)) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (preg_match('/(datetime|timestamp)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + $bindName = $bindName ?: $key; + + if ($this->query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + + $this->query->bind($bindName, $value, $bindType); + return ':' . $bindName; + } + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return string + */ + protected function parseLimit($limit) + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param array $join + * @param array $options 查询条件 + * @return string + */ + protected function parseJoin($join, $options = []) + { + $joinStr = ''; + if (!empty($join)) { + foreach ($join as $item) { + list($table, $type, $on) = $item; + $condition = []; + foreach ((array) $on as $val) { + if ($val instanceof Expression) { + $condition[] = $val->getValue(); + } elseif (strpos($val, '=')) { + list($val1, $val2) = explode('=', $val, 2); + $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); + } else { + $condition[] = $val; + } + } + + $table = $this->parseTable($table, $options); + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); + } + } + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @param array $options 查询条件 + * @return string + */ + protected function parseOrder($order, $options = []) + { + if (empty($order)) { + return ''; + } + + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand(); + } else { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($key, $options, true) . $sort; + } + } + $order = implode(',', $array); + + return !empty($order) ? ' ORDER BY ' . $order : ''; + } + + /** + * group分析 + * @access protected + * @param mixed $group + * @return string + */ + protected function parseGroup($group) + { + return !empty($group) ? ' GROUP BY ' . $this->parseKey($group) : ''; + } + + /** + * having分析 + * @access protected + * @param string $having + * @return string + */ + protected function parseHaving($having) + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param string $comment + * @return string + */ + protected function parseComment($comment) + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param mixed $distinct + * @return string + */ + protected function parseDistinct($distinct) + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param mixed $union + * @return string + */ + protected function parseUnion($union) + { + if (empty($union)) { + return ''; + } + $type = $union['type']; + unset($union['type']); + foreach ($union as $u) { + if ($u instanceof \Closure) { + $sql[] = $type . ' ' . $this->parseClosure($u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $this->parseSqlTable($u) . ' )'; + } + } + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param mixed $index + * @return string + */ + protected function parseForce($index) + { + if (empty($index)) { + return ''; + } + + return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index); + } + + /** + * 设置锁机制 + * @access protected + * @param bool|string $lock + * @return string + */ + protected function parseLock($lock = false) + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } elseif (is_string($lock)) { + return ' ' . trim($lock) . ' '; + } + } + + /** + * 生成查询SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function select($options = []) + { + $sql = str_replace( + ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($options['table'], $options), + $this->parseDistinct($options['distinct']), + $this->parseField($options['field'], $options), + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseGroup($options['group']), + $this->parseHaving($options['having']), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseUnion($options['union']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + $this->parseForce($options['force']), + ], $this->selectSql); + return $sql; + } + + /** + * 生成insert SQL + * @access public + * @param array $data 数据 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + */ + public function insert(array $data, $options = [], $replace = false) + { + // 分析并处理数据 + $data = $this->parseData($data, $options); + if (empty($data)) { + return 0; + } + $fields = array_keys($data); + $values = array_values($data); + + $sql = str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($options['comment']), + ], $this->insertSql); + + return $sql; + } + + /** + * 生成insertall SQL + * @access public + * @param array $dataSet 数据集 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + * @throws Exception + */ + public function insertAll($dataSet, $options = [], $replace = false) + { + // 获取合法的字段 + if ('*' == $options['field']) { + $fields = array_keys($this->query->getFieldsType($options['table'])); + } else { + $fields = $options['field']; + } + + foreach ($dataSet as $data) { + foreach ($data as $key => $val) { + if (!in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + unset($data[$key]); + } elseif (is_null($val)) { + $data[$key] = 'NULL'; + } elseif (is_scalar($val)) { + $data[$key] = $this->parseValue($val, $key); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $data[$key] = $val->__toString(); + } else { + // 过滤掉非标量数据 + unset($data[$key]); + } + } + $value = array_values($data); + $values[] = 'SELECT ' . implode(',', $value); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($field, $options, true); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $insertFields), + implode(' UNION ALL ', $values), + $this->parseComment($options['comment']), + ], $this->insertAllSql); + } + + /** + * 生成select insert SQL + * @access public + * @param array $fields 数据 + * @param string $table 数据表 + * @param array $options 表达式 + * @return string + */ + public function selectInsert($fields, $table, $options) + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + $fields = array_map([$this, 'parseKey'], $fields); + $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); + return $sql; + } + + /** + * 生成update SQL + * @access public + * @param array $data 数据 + * @param array $options 表达式 + * @return string + */ + public function update($data, $options) + { + $table = $this->parseTable($options['table'], $options); + $data = $this->parseData($data, $options); + if (empty($data)) { + return ''; + } + foreach ($data as $key => $val) { + $set[] = $key . '=' . $val; + } + + $sql = str_replace( + ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($options['table'], $options), + implode(',', $set), + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + ], $this->updateSql); + + return $sql; + } + + /** + * 生成delete SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function delete($options) + { + $sql = str_replace( + ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($options['table'], $options), + !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + ], $this->deleteSql); + + return $sql; + } +} diff --git a/source/thinkphp/library/think/db/Connection.php b/source/thinkphp/library/think/db/Connection.php new file mode 100644 index 0000000..578cc8f --- /dev/null +++ b/source/thinkphp/library/think/db/Connection.php @@ -0,0 +1,1059 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use PDOStatement; +use think\Db; +use think\db\exception\BindParamException; +use think\Debug; +use think\Exception; +use think\exception\PDOException; +use think\Log; + +/** + * Class Connection + * @package think + * @method Query table(string $table) 指定数据表(含前缀) + * @method Query name(string $name) 指定数据表(不含前缀) + * + */ +abstract class Connection +{ + + /** @var PDOStatement PDO操作实例 */ + protected $PDOStatement; + + /** @var string 当前SQL指令 */ + protected $queryStr = ''; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + + /** @var PDO[] 数据库连接ID 支持多个连接 */ + protected $links = []; + + /** @var PDO 当前连接ID */ + protected $linkID; + protected $linkRead; + protected $linkWrite; + + // 查询结果类型 + protected $fetchType = PDO::FETCH_ASSOC; + // 字段属性大小写 + protected $attrCase = PDO::CASE_LOWER; + // 监听回调 + protected static $event = []; + // 使用Builder类 + protected $builder; + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据返回类型 + 'result_type' => PDO::FETCH_ASSOC, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, + ]; + + // PDO连接参数 + 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, + ]; + + // 绑定参数 + protected $bind = []; + + /** + * 构造函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 获取新的查询对象 + * @access protected + * @return Query + */ + protected function getQuery() + { + $class = $this->config['query']; + return new $class($this); + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilder() + { + if (!empty($this->builder)) { + return $this->builder; + } else { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + } + + /** + * 调用Query类的查询方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([$this->getQuery(), $method], $args); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn($config); + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + abstract public function getFields($tableName); + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + abstract public function getTables($dbName); + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + abstract protected function getExplain($sql); + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase($info) + { + // 字段大小写转换 + 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 public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig($config = '') + { + return $config ? $this->config[$config] : $this->config; + } + + /** + * 设置数据库的配置参数 + * @access public + * @param string|array $config 配置名称 + * @param mixed $value 配置值 + * @return void + */ + public function setConfig($config, $value = '') + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } else { + $this->config[$config] = $value; + } + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws Exception + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false) + { + if (!isset($this->links[$linkNum])) { + if (!$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 (isset($config['result_type'])) { + $this->fetchType = $config['result_type']; + } + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + if ($config['debug']) { + $startTime = microtime(true); + } + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + if ($config['debug']) { + // 记录数据库连接信息 + Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql'); + } + } catch (\PDOException $e) { + if ($autoConnection) { + Log::record($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + return $this->links[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return \PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } else { + return $this->linkID; + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return mixed + * @throws PDOException + * @throws \Exception + */ + public function query($sql, $bind = [], $master = false, $pdo = false) + { + $this->initConnect($master); + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + Db::$queryTimes++; + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + // 执行查询 + $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false, '', $master); + // 返回结果集 + return $this->getResult($pdo, $procedure); + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; + } + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param Query $query 查询对象 + * @return int + * @throws PDOException + * @throws \Exception + */ + public function execute($sql, $bind = [], Query $query = null) + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + Db::$executeTimes++; + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + // 执行语句 + $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false, '', true); + + if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $query->readMaster(); + } + + $this->numRows = $this->PDOStatement->rowCount(); + return $this->numRows; + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw $e; + } + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql($sql, array $bind = []) + { + if (is_array($sql)) { + $sql = implode(';', $sql); + } + + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + if (PDO::PARAM_STR == $type) { + $value = $this->quote($value); + } elseif (PDO::PARAM_INT == $type) { + $value = (float) $value; + } + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + str_replace( + [':' . $key . ')', ':' . $key . ',', ':' . $key . ' ', ':' . $key . PHP_EOL], + [$value . ')', $value . ',', $value . ' ', $value . PHP_EOL], + $sql . ' '); + } + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []) + { + 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; + } + $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($bind) + { + 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 $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return PDOStatement|array + */ + protected function getResult($pdo = false, $procedure = false) + { + if ($pdo) { + // 返回PDOStatement对象处理 + return $this->PDOStatement; + } + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + $result = $this->PDOStatement->fetchAll($this->fetchType); + $this->numRows = count($result); + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure() + { + $item = []; + do { + $result = $this->getResult(); + if ($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($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 bool|mixed + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + ++$this->transTimes; + try { + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + + } catch (\Exception $e) { + if ($this->isBreak($e)) { + --$this->transTimes; + return $this->close()->startTrans(); + } + throw $e; + } catch (\Error $e) { + if ($this->isBreak($e)) { + --$this->transTimes; + return $this->close()->startTrans(); + } + throw $e; + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->initConnect(true); + + 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() + { + return false; + } + + /** + * 生成定义保存点的SQL + * @param $name + * @return string + */ + protected function parseSavepoint($name) + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @param $name + * @return string + */ + protected function parseSavepointRollBack($name) + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sqlArray SQL批处理指令 + * @return boolean + */ + public function batchQuery($sqlArray = [], $bind = [], Query $query = null) + { + if (!is_array($sqlArray)) { + return false; + } + // 自动启动事务支持 + $this->startTrans(); + try { + foreach ($sqlArray as $sql) { + $this->execute($sql, $bind, $query); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute = false) + { + return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes() + { + return Db::$executeTimes; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + // 释放查询 + $this->free(); + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e) + { + if (!$this->config['break_reconnect']) { + return false; + } + + $info = [ + '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', + ]; + + $error = $e->getMessage(); + + foreach ($info as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->linkID->lastInsertId($sequence); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->numRows; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + return $error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @param bool $master 是否主库查询 + * @return string + */ + public function quote($str, $master = true) + { + $this->initConnect($master); + return $this->linkID ? $this->linkID->quote($str) : $str; + } + + /** + * 数据库调试 记录当前SQL及分析性能 + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param string $sql 执行的SQL语句 留空自动获取 + * @param boolean $master 主从标记 + * @return void + */ + protected function debug($start, $sql = '', $master = false) + { + if (!empty($this->config['debug'])) { + // 开启数据库调试模式 + if ($start) { + Debug::remark('queryStartTime', 'time'); + } else { + // 记录操作结束时间 + Debug::remark('queryEndTime', 'time'); + $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); + $sql = $sql ?: $this->getLastsql(); + $result = []; + // SQL性能分析 + if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { + $result = $this->getExplain($sql); + } + // SQL监听 + $this->trigger($sql, $runtime, $result, $master); + } + } + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + self::$event[] = $callback; + } + + /** + * 触发SQL事件 + * @access protected + * @param string $sql SQL语句 + * @param float $runtime SQL运行时间 + * @param mixed $explain SQL分析 + * @param bool $master 主从标记 + * @return void + */ + protected function trigger($sql, $runtime, $explain = [], $master = false) + { + if (!empty(self::$event)) { + foreach (self::$event as $callback) { + if (is_callable($callback)) { + call_user_func_array($callback, [$sql, $runtime, $explain, $master]); + } + } + } else { + // 未注册监听则记录到日志中 + if ($this->config['deploy']) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + Log::record('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]', 'sql'); + if (!empty($explain)) { + Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql'); + } + } + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect($master = true) + { + 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($master = false) + { + $_config = []; + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $_config[$name] = explode(',', $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] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; + } + } + $dbConfig = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; + } + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 释放查询 + if ($this->PDOStatement) { + $this->free(); + } + // 关闭连接 + $this->close(); + } +} diff --git a/source/thinkphp/library/think/db/Expression.php b/source/thinkphp/library/think/db/Expression.php new file mode 100644 index 0000000..f1b92ab --- /dev/null +++ b/source/thinkphp/library/think/db/Expression.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +class Expression +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @return void + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/source/thinkphp/library/think/db/Query.php b/source/thinkphp/library/think/db/Query.php new file mode 100644 index 0000000..ac4adea --- /dev/null +++ b/source/thinkphp/library/think/db/Query.php @@ -0,0 +1,3045 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\App; +use think\Cache; +use think\Collection; +use think\Config; +use think\Db; +use think\db\exception\BindParamException; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Exception; +use think\exception\DbException; +use think\exception\PDOException; +use think\Loader; +use think\Model; +use think\model\Relation; +use think\model\relation\OneToOne; +use think\Paginator; + +class Query +{ + // 数据库Connection对象实例 + protected $connection; + // 数据库Builder对象实例 + protected $builder; + // 当前模型类名称 + protected $model; + // 当前数据表名称(含前缀) + protected $table = ''; + // 当前数据表名称(不含前缀) + protected $name = ''; + // 当前数据表主键 + protected $pk; + // 当前数据表前缀 + protected $prefix = ''; + // 查询参数 + protected $options = []; + // 参数绑定 + protected $bind = []; + // 数据表信息 + protected static $info = []; + // 回调事件 + private static $event = []; + // 读取主库 + protected static $readMaster = []; + + /** + * 构造函数 + * @access public + * @param Connection $connection 数据库对象实例 + * @param Model $model 模型对象 + */ + public function __construct(Connection $connection = null, $model = null) + { + $this->connection = $connection ?: Db::connect([], true); + $this->prefix = $this->connection->getConfig('prefix'); + $this->model = $model; + // 设置当前连接的Builder对象 + $this->setBuilder(); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws DbException + * @throws Exception + */ + public function __call($method, $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Loader::parseName(substr($method, 5)); + $where[$field] = $args[0]; + return $this->where($where)->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Loader::parseName(substr($method, 10)); + $where[$name] = $args[0]; + return $this->where($where)->value($args[1]); + } 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:' . __CLASS__ . '->' . $method); + } + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 切换当前的数据库连接 + * @access public + * @param mixed $config + * @return $this + */ + public function connect($config) + { + $this->connection = Db::connect($config); + $this->setBuilder(); + $this->prefix = $this->connection->getConfig('prefix'); + return $this; + } + + /** + * 设置当前的数据库Builder对象 + * @access protected + * @return void + */ + protected function setBuilder() + { + $class = $this->connection->getBuilder(); + $this->builder = new $class($this->connection, $this); + } + + /** + * 获取当前的模型对象实例 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model; + } + + /** + * 设置后续从主库读取数据 + * @access public + * @param bool $allTable + * @return void + */ + public function readMaster($allTable = false) + { + if ($allTable) { + $table = '*'; + } else { + $table = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + static::$readMaster[$table] = true; + + return $this; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 指定默认的数据表名(不含前缀) + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + return $this; + } + + /** + * 指定默认数据表名(含前缀) + * @access public + * @param string $table 表名 + * @return $this + */ + public function setTable($table) + { + $this->table = $table; + return $this; + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name + * @return string + */ + public function getTable($name = '') + { + if ($name || empty($this->table)) { + $name = $name ?: $this->name; + $tableName = $this->prefix; + if ($name) { + $tableName .= Loader::parseName($name); + } + } else { + $tableName = $this->table; + } + return $tableName; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access public + * @param string $sql sql语句 + * @return string + */ + public function parseSqlTable($sql) + { + if (false !== strpos($sql, '__')) { + $prefix = $this->prefix; + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) { + return $prefix . strtolower($match[1]); + }, $sql); + } + return $sql; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param boolean $master 是否在主服务器读操作 + * @param bool|string $class 指定返回的数据集对象 + * @return mixed + * @throws BindParamException + * @throws PDOException + */ + public function query($sql, $bind = [], $master = false, $class = false) + { + return $this->connection->query($sql, $bind, $master, $class); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws BindParamException + * @throws PDOException + */ + public function execute($sql, $bind = []) + { + return $this->connection->execute($sql, $bind, $this); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->connection->getLastInsID($sequence); + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->connection->getLastSql(); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction($callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->connection->rollback(); + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return boolean + */ + public function batchQuery($sql = [], $bind = []) + { + return $this->connection->batchQuery($sql, $bind); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return boolean + */ + public function getConfig($name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 得到分表的的数据表名 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return string + */ + public function getPartitionTableName($data, $field, $rule = []) + { + // 对数据表进行分区 + if ($field && isset($data[$field])) { + $value = $data[$field]; + $type = $rule['type']; + switch ($type) { + case 'id': + // 按照id范围分表 + $step = $rule['expr']; + $seq = floor($value / $step) + 1; + break; + case 'year': + // 按照年份分表 + if (!is_numeric($value)) { + $value = strtotime($value); + } + $seq = date('Y', $value) - $rule['expr'] + 1; + break; + case 'mod': + // 按照id的模数分表 + $seq = ($value % $rule['num']) + 1; + break; + case 'md5': + // 按照md5的序列分表 + $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1; + break; + default: + if (function_exists($type)) { + // 支持指定函数哈希 + $seq = (ord(substr($type($value), 0, 1)) % $rule['num']) + 1; + } else { + // 按照字段的首字母的值分表 + $seq = (ord($value{0}) % $rule['num']) + 1; + } + } + return $this->getTable() . '_' . $seq; + } else { + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } + + $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; + return $tableName; + } + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function value($field, $default = null, $force = false) + { + $result = false; + if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { + // 判断查询缓存 + $cache = $this->options['cache']; + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); + $result = Cache::get($key); + } + if (false === $result) { + if (isset($this->options['field'])) { + unset($this->options['field']); + } + $pdo = $this->field($field)->limit(1)->getPdo(); + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + + $result = $pdo->fetchColumn(); + if ($force) { + $result = (float) $result; + } + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } else { + // 清空查询条件 + $this->options = []; + } + return false !== $result ? $result : $default; + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column($field, $key = '') + { + $result = false; + if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { + // 判断查询缓存 + $cache = $this->options['cache']; + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + $guid = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); + $result = Cache::get($guid); + } + if (false === $result) { + if (isset($this->options['field'])) { + unset($this->options['field']); + } + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } + $pdo = $this->field($field)->getPdo(); + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + if ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + foreach ($resultSet as $val) { + if ($count > 2) { + $result[$val[$key]] = $val; + } elseif (2 == $count) { + $result[$val[$key]] = $val[$key2]; + } elseif (1 == $count) { + $result[$val[$key]] = $val[$key1]; + } + } + } else { + $result = []; + } + } + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + } else { + // 清空查询条件 + $this->options = []; + } + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return integer|string + */ + public function count($field = '*') + { + if (isset($this->options['group'])) { + if (!preg_match('/^[\w\.\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); + + $count = $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); + } else { + $count = $this->aggregate('COUNT', $field, true); + } + + return is_string($count) ? $count : (int) $count; + + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + if (0 === stripos($field, 'DISTINCT ')) { + list($distinct, $field) = explode(' ', $field); + } + + if (!preg_match('/^[\w\.\+\-\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + + $result = $this->value($aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $field . ') AS tp_' . strtolower($aggregate), 0, $force); + + return $result; + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return float|int + */ + public function sum($field) + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return float|int + */ + public function avg($field) + { + return $this->aggregate('AVG', $field, true); + } + + /** + * 设置记录的某个字段值 + * 支持使用数据库字段和方法 + * @access public + * @param string|array $field 字段名 + * @param mixed $value 字段值 + * @return integer + */ + public function setField($field, $value = '') + { + if (is_array($field)) { + $data = $field; + } else { + $data[$field] = $value; + } + return $this->update($data); + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + } + return $this->setField($field, ['inc', $step]); + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + return $this->setField($field, ['inc', $step]); + } + return $this->setField($field, ['dec', $step]); + } + + /** + * 延时更新检查 返回false表示需要延时 + * 否则返回实际写入的数值 + * @access protected + * @param string $type 自增或者自减 + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) + * @return false|integer + */ + protected function lazyWrite($type, $guid, $step, $lazyTime) + { + if (!Cache::has($guid . '_time')) { + // 计时开始 + Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0); + Cache::$type($guid, $step); + } elseif ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) { + // 删除缓存 + $value = Cache::$type($guid, $step); + Cache::rm($guid); + Cache::rm($guid . '_time'); + return 0 === $value ? false : $value; + } else { + // 更新缓存 + Cache::$type($guid, $step); + } + return false; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @return $this + */ + public function join($join, $condition = null, $type = 'INNER') + { + if (empty($condition)) { + // 如果为组数,则循环调用join + foreach ($join as $key => $value) { + if (is_array($value) && 2 <= count($value)) { + $this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type); + } + } + } else { + $table = $this->getJoinTable($join); + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + } + return $this; + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * @access public + * @param array|string $join + * @return array|string + */ + protected function getJoinTable($join, &$alias = null) + { + // 传入的表名为数组 + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + } else { + $join = trim($join); + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + $prefix = $this->prefix; + if (strpos($join, ' ')) { + // 使用别名 + list($table, $alias) = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { + $alias = $join; + } + } + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { + $table = $this->getTable($table); + } + } + if (isset($alias) && $table != $alias) { + $table = [$table => $alias]; + } + } + return $table; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return $this + */ + public function union($union, $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; + } + + /** + * 指定查询字段 支持字段排除和指定数据表 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $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->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $field = $fields ?: ['*']; + } elseif ($except) { + // 字段排除 + $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $field = $fields ? array_diff($fields, $field) : $field; + } + if ($tableName) { + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $val = $prefix . '.' . $val . ($alias ? ' AS ' . $alias . $val : ''); + } + $field[$key] = $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 string $field 字段名 + * @param array $bind 参数绑定 + * @return $this + */ + public function fieldRaw($field, array $bind = []) + { + $this->options['field'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 设置数据 + * @access public + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 + * @return $this + */ + public function data($field, $value = null) + { + if (is_array($field)) { + $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; + } else { + $this->options['data'][$field] = $value; + } + return $this; + } + + /** + * 字段值增长 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function inc($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['inc', $step]); + } + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function dec($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['dec', $step]); + } + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp($field, $value) + { + $this->data($field, $this->raw($value)); + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param mixed $value 表达式 + * @return Expression + */ + public function raw($value) + { + return new Expression($value); + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $table 数据表 + * @param string|array $field 查询字段 + * @param mixed $on JOIN条件 + * @param string $type JOIN类型 + * @return $this + */ + public function view($join, $field = true, $on = null, $type = 'INNER') + { + $this->options['view'] = true; + if (is_array($join) && key($join) === 0) { + foreach ($join as $key => $val) { + $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); + } + } else { + $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] = $val; + $this->options['map'][$val] = $name; + } + } + } + $this->field($fields); + if ($on) { + $this->join($table, $on, $type); + } else { + $this->table($table); + } + } + return $this; + } + + /** + * 设置分表规则 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return $this + */ + public function partition($data, $field, $rule = []) + { + $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); + return $this; + } + + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('AND', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定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); + $this->parseWhereExp('OR', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定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); + $this->parseWhereExp('XOR', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw($where, $bind = [], $logic = 'AND') + { + $this->options['where'][$logic][] = $this->raw($where); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw($where, $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'null', null, [], true); + return $this; + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'notnull', null, [], true); + return $this; + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + $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, $logic = 'AND') + { + $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($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'in', $condition, [], true); + return $this; + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not in', $condition, [], true); + return $this; + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'like', $condition, [], true); + return $this; + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not like', $condition, [], true); + return $this; + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'between', $condition, [], true); + return $this; + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not between', $condition, [], true); + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'exp', $this->raw($condition), [], true); + return $this; + } + + /** + * 设置软删除字段及条件 + * @access public + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete($field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; + } + return $this; + } + + /** + * 分析查询表达式 + * @access public + * @param string $logic 查询逻辑 and or xor + * @param string|array|\Closure $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return void + */ + protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false) + { + $logic = strtoupper($logic); + if ($field instanceof \Closure) { + $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; + return; + } + + if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Expression) { + return $this->whereRaw($field, is_array($op) ? $op : []); + } elseif ($strict) { + // 使用严格模式查询 + $where[$field] = [$op, $condition]; + + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { + $where[] = ['exp', $this->raw($field)]; + if (is_array($op)) { + // 参数绑定 + $this->bind($op); + } + } elseif (is_null($op) && is_null($condition)) { + if (is_array($field)) { + // 数组批量查询 + $where = $field; + foreach ($where as $k => $val) { + $this->options['multi'][$logic][$k][] = $val; + } + } elseif ($field && is_string($field)) { + // 字符串查询 + $where[$field] = ['null', '']; + $this->options['multi'][$logic][$field][] = $where[$field]; + } + } elseif (is_array($op)) { + $where[$field] = $param; + } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { + // null查询 + $where[$field] = [$op, '']; + + $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (is_null($condition)) { + // 字段相等查询 + $where[$field] = ['eq', $op]; + + $this->options['multi'][$logic][$field][] = $where[$field]; + } else { + if ('exp' == strtolower($op)) { + $where[$field] = ['exp', $this->raw($condition)]; + // 参数绑定 + if (isset($param[2]) && is_array($param[2])) { + $this->bind($param[2]); + } + } else { + $where[$field] = [$op, $condition]; + } + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; + } + + if (!empty($where)) { + if (!isset($this->options['where'][$logic])) { + $this->options['where'][$logic] = []; + } + if (is_string($field) && $this->checkMultiField($field, $logic)) { + $where[$field] = $this->options['multi'][$logic][$field]; + } elseif (is_array($field)) { + foreach ($field as $key => $val) { + if ($this->checkMultiField($key, $logic)) { + $where[$key] = $this->options['multi'][$logic][$key]; + } + } + } + $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); + } + } + + /** + * 检查是否存在一个字段多次查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return bool + */ + private function checkMultiField($field, $logic) + { + return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + if (isset($this->options['where'][$logic][$field])) { + unset($this->options['where'][$logic][$field]); + unset($this->options['multi'][$logic][$field]); + } + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string|bool $option 参数名 true 表示去除所有参数 + * @return $this + */ + public function removeOption($option = true) + { + if (true === $option) { + $this->options = []; + } elseif (is_string($option) && isset($this->options[$option])) { + unset($this->options[$option]); + } + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return $this + */ + public function limit($offset, $length = null) + { + if (is_null($length) && strpos($offset, ',')) { + list($offset, $length) = explode(',', $offset); + } + $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return $this + */ + public function page($page, $listRows = null) + { + if (is_null($listRows) && strpos($page, ',')) { + list($page, $listRows) = explode(',', $page); + } + $this->options['page'] = [intval($page), intval($listRows)]; + return $this; + } + + /** + * 分页查询 + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return \think\Paginator + * @throws DbException + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + if (is_array($listRows)) { + $config = array_merge(Config::get('paginate'), $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge(Config::get('paginate'), $config); + $listRows = $listRows ?: $config['list_rows']; + } + + /** @var Paginator $class */ + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); + $page = isset($config['page']) ? (int) $config['page'] : call_user_func([ + $class, + 'getCurrentPage', + ], $config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, '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(); + $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + return $class::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (strpos($table, ',')) { + $tables = explode(',', $table); + $table = []; + foreach ($tables as $item) { + list($item, $alias) = explode(' ', trim($item)); + if ($alias) { + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } elseif (strpos($table, ' ')) { + list($table, $alias) = explode(' ', $table); + + $table = [$table => $alias]; + $this->alias($table); + } + } else { + $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; + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, $order = null) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $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; + } + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw($field, array $bind = []) + { + $this->options['order'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) + if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (false !== $key) { + $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; + } + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string $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($having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + $this->options['master'] = true; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param string $distinct 是否唯一 + * @return $this + */ + public function distinct($distinct) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param mixed $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + foreach ($alias as $key => $val) { + if (false !== strpos($key, '__')) { + $table = $this->parseSqlTable($key); + } else { + $table = $key; + } + $this->options['alias'][$table] = $val; + } + } else { + if (isset($this->options['table'])) { + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + if (false !== strpos($table, '__')) { + $table = $this->parseSqlTable($table); + } + } else { + $table = $this->getTable(); + } + + $this->options['alias'][$table] = $alias; + } + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force($force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment($comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 获取执行的SQL语句 + * @access public + * @param boolean $fetch 是否返回sql + * @return $this + */ + public function fetchSql($fetch = true) + { + $this->options['fetch_sql'] = $fetch; + return $this; + } + + /** + * 不主动获取数据集 + * @access public + * @param bool $pdo 是否返回 PDOStatement 对象 + * @return $this + */ + public function fetchPdo($pdo = true) + { + $this->options['fetch_pdo'] = $pdo; + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @return $this + */ + public function master() + { + $this->options['master'] = true; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict($strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException($fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence($sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string|array $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @return $this + */ + public function whereTime($field, $op, $range = null) + { + if (is_null($range)) { + if (is_array($op)) { + $range = $op; + } else { + // 使用日期表达式 + switch (strtolower($op)) { + case 'today': + case 'd': + $range = ['today', 'tomorrow']; + break; + case 'week': + case 'w': + $range = ['this week 00:00:00', 'next week 00:00:00']; + break; + case 'month': + case 'm': + $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; + break; + case 'year': + case 'y': + $range = ['this year 1/1', 'next year 1/1']; + break; + case 'yesterday': + $range = ['yesterday', 'today']; + break; + case 'last week': + $range = ['last week 00:00:00', 'this week 00:00:00']; + break; + case 'last month': + $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; + break; + case 'last year': + $range = ['last year 1/1', 'this year 1/1']; + break; + default: + $range = $op; + } + } + $op = is_array($range) ? 'between' : '>'; + } + $this->where($field, strtolower($op) . ' time', $range); + return $this; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName = '', $fetch = '') + { + if (!$tableName) { + $tableName = $this->getTable(); + } + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',')) { + // 多表不获取字段信息 + return false; + } else { + $tableName = $this->parseSqlTable($tableName); + } + + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + + list($guid) = explode(' ', $tableName); + $db = $this->getConfig('database'); + if (!isset(self::$info[$db . '.' . $guid])) { + if (!strpos($guid, '.')) { + $schema = $db . '.' . $guid; + } else { + $schema = $guid; + } + // 读取缓存 + if (!App::$debug && is_file(RUNTIME_PATH . 'schema/' . $schema . '.php')) { + $info = include RUNTIME_PATH . 'schema/' . $schema . '.php'; + } else { + $info = $this->connection->getFields($guid); + } + $fields = array_keys($info); + $bind = $type = []; + foreach ($info as $key => $val) { + // 记录字段类型 + $type[$key] = $val['type']; + $bind[$key] = $this->getFieldBindType($val['type']); + if (!empty($val['primary'])) { + $pk[] = $key; + } + } + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + } else { + $pk = null; + } + self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + } + return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; + } + + /** + * 获取当前数据表的主键 + * @access public + * @param string|array $options 数据表名或者查询参数 + * @return string|array + */ + public function getPk($options = '') + { + if (!empty($this->pk)) { + $pk = $this->pk; + } else { + $pk = $this->getTableInfo(is_array($options) ? $options['table'] : $options, 'pk'); + } + return $pk; + } + + // 获取当前数据表字段信息 + public function getTableFields($table = '') + { + return $this->getTableInfo($table ?: $this->getOptions('table'), 'fields'); + } + + // 获取当前数据表字段类型 + public function getFieldsType($table = '') + { + return $this->getTableInfo($table ?: $this->getOptions('table'), 'type'); + } + + // 获取当前数据表绑定信息 + public function getFieldsBind($table = '') + { + $types = $this->getFieldsType($table); + $bind = []; + if ($types) { + foreach ($types as $key => $type) { + $bind[$key] = $this->getFieldBindType($type); + } + } + return $bind; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + protected function getFieldBindType($type) + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(int|double|float|decimal|real|numeric|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; + } + + /** + * 参数绑定 + * @access public + * @param mixed $key 参数名 + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @return $this + */ + public function bind($key, $value = false, $type = PDO::PARAM_STR) + { + if (is_array($key)) { + $this->bind = array_merge($this->bind, $key); + } else { + $this->bind[$key] = [$value, $type]; + } + return $this; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 查询参数赋值 + * @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($name = '') + { + if ('' === $name) { + return $this->options; + } else { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + } + + /** + * 设置关联查询JOIN预查询 + * @access public + * @param string|array $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (empty($with)) { + return $this; + } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + $with[$key] = $key; + } elseif (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + $with[$key] = $relation; + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { + $model->eagerly($this, $relation, $subRelation, $closure, $first); + $first = false; + } elseif ($closure) { + $with[$key] = $closure; + } + } + $this->via(); + if (isset($this->options['with'])) { + $this->options['with'] = array_merge($this->options['with'], $with); + } else { + $this->options['with'] = $with; + } + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'] = $relation; + } else { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + if (!isset($this->options['field'])) { + $this->field('*'); + } + foreach ($relations as $key => $relation) { + $closure = $name = null; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (!is_int($key)) { + $name = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + + $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $name) . ')'; + + if (empty($name)) { + $name = Loader::parseName($relation) . '_count'; + } + + $this->field([$count => $name]); + } + } + return $this; + } + + /** + * 关联预加载中 获取关联指定字段值 + * example: + * Model::with(['relation' => function($query){ + * $query->withField("id,name"); + * }]) + * + * @param string | array $field 指定获取的字段 + * @return $this + */ + public function withField($field) + { + $this->options['with_field'] = $field; + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via + * @return $this + */ + public function via($via = '') + { + $this->options['via'] = $via; + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param string|array $relation 关联名称 + * @return $this + */ + public function relation($relation) + { + if (empty($relation)) { + return $this; + } + if (is_string($relation)) { + $relation = explode(',', $relation); + } + if (isset($this->options['relation'])) { + $this->options['relation'] = array_merge($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } + return $this; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @param mixed $options 表达式参数 + * @return void + * @throws Exception + */ + protected function parsePkWhere($data, &$options) + { + $pk = $this->getPk($options); + // 获取当前数据表 + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + if (!empty($options['alias'][$table])) { + $alias = $options['alias'][$table]; + } + if (is_string($pk)) { + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $where[$key] = isset($data[$pk]) ? $data[$pk] : ['in', $data]; + } else { + $where[$key] = strpos($data, ',') ? ['IN', $data] : $data; + } + } elseif (is_array($pk) && is_array($data) && !empty($data)) { + // 根据复合主键查询 + foreach ($pk as $key) { + if (isset($data[$key])) { + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$attr] = $data[$key]; + } else { + throw new Exception('miss complex primary data'); + } + } + } + + if (!empty($where)) { + if (isset($options['where']['AND'])) { + $options['where']['AND'] = array_merge($options['where']['AND'], $where); + } else { + $options['where']['AND'] = $where; + } + } + return; + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); + // 生成SQL语句 + $sql = $this->builder->insert($data, $options, $replace); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + // 执行操作 + $result = 0 === $sql ? 0 : $this->execute($sql, $bind, $this); + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + if ($lastInsId) { + $pk = $this->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + $options['data'] = $data; + $this->trigger('after_insert', $options); + + if ($getLastInsID) { + return $lastInsId; + } + } + return $result; + } + + /** + * 插入记录并获取自增ID + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insertGetId(array $data, $replace = false, $sequence = null) + { + return $this->insert($data, $replace, true, $sequence); + } + + /** + * 批量插入记录 + * @access public + * @param mixed $dataSet 数据集 + * @param boolean $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + */ + public function insertAll(array $dataSet, $replace = false, $limit = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + if (!is_array(reset($dataSet))) { + return false; + } + + // 生成SQL语句 + if (is_null($limit)) { + $sql = $this->builder->insertAll($dataSet, $options, $replace); + } else { + $array = array_chunk($dataSet, $limit, true); + foreach ($array as $item) { + $sql[] = $this->builder->insertAll($item, $options, $replace); + } + } + + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } elseif (is_array($sql)) { + // 执行操作 + return $this->batchQuery($sql, $bind, $this); + } else { + // 执行操作 + return $this->execute($sql, $bind, $this); + } + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert($fields, $table) + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成SQL语句 + $table = $this->parseSqlTable($table); + $sql = $this->builder->selectInsert($fields, $table, $options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } else { + // 执行操作 + return $this->execute($sql, $bind, $this); + } + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(array $data = []) + { + $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = $data[$pk]; + if (!isset($key)) { + $key = 'think:' . $options['table'] . '|' . $data[$pk]; + } + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $where[$field] = $data[$field]; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + if (!isset($where)) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } else { + $options['where']['AND'] = $where; + } + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($data, $options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } else { + // 检测缓存 + if (isset($key) && Cache::get($key)) { + // 删除缓存 + Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); + } + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $this); + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + $options['data'] = $data; + $this->trigger('after_update', $options); + } + return $result; + } + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + + /** + * 查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection|false|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null) + { + if ($data instanceof Query) { + return $data->select(); + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $this]); + $data = null; + } + // 分析查询表达式 + $options = $this->parseExpress(); + + if (false === $data) { + // 用于子查询 不查询只返回SQL + $options['fetch_sql'] = true; + } elseif (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data, $options); + } + + $resultSet = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + unset($options['cache']); + $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); + $resultSet = Cache::get($key); + } + if (false === $resultSet) { + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + $options['data'] = $data; + if ($resultSet = $this->trigger('before_select', $options)) { + } else { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + } + + if (isset($cache) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $cache); + } + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + if (count($resultSet) > 0) { + foreach ($resultSet as $key => $result) { + /** @var Model $model */ + $model = $this->model->newInstance($result); + $model->isUpdate(true); + + // 关联查询 + if (!empty($options['relation'])) { + $model->relationQuery($options['relation']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $model->relationCount($model, $options['with_count']); + } + $resultSet[$key] = $model; + } + if (!empty($options['with'])) { + // 预载入 + $model->eagerlyResultSet($resultSet, $options['with']); + } + // 模型数据集转换 + $resultSet = $model->toCollection($resultSet); + } else { + $resultSet = $this->model->toCollection($resultSet); + } + } elseif ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + // 返回结果处理 + if (!empty($options['fail']) && count($resultSet) == 0) { + $this->throwNotFound($options); + } + return $resultSet; + } + + /** + * 缓存数据 + * @access public + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + if (isset($config['tag'])) { + Cache::tag($config['tag'])->set($key, $data, $config['expire']); + } else { + Cache::set($key, $data, $config['expire']); + } + } + + /** + * 生成缓存标识 + * @access public + * @param mixed $value 缓存数据 + * @param array $options 缓存参数 + * @param array $bind 绑定参数 + * @return string + */ + protected function getCacheKey($value, $options, $bind = []) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { + $data = $value[1]; + } + $prefix = $this->connection->getConfig('database') . '.'; + + if (isset($data)) { + return 'think:' . $prefix . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } + + try { + return md5($prefix . serialize($options) . serialize($bind)); + } catch (\Exception $e) { + throw new Exception('closure not support cache(true)'); + } + } + + /** + * 查找单条记录 + * @access public + * @param array|string|Query|\Closure $data + * @return array|false|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if ($data instanceof Query) { + return $data->find(); + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $this]); + $data = null; + } + // 分析查询表达式 + $options = $this->parseExpress(); + $pk = $this->getPk($options); + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data, $options); + } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + $options['limit'] = 1; + $result = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + if (true === $cache['key'] && !is_null($data) && !is_array($data)) { + $key = 'think:' . $this->connection->getConfig('database') . '.' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } elseif (is_string($cache['key'])) { + $key = $cache['key']; + } elseif (!isset($key)) { + $key = md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); + } + $result = Cache::get($key); + } + if (false === $result) { + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + if (is_string($pk)) { + if (!is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + } + $options['data'] = $data; + // 事件回调 + if ($result = $this->trigger('before_find', $options)) { + } else { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } + + // 数据处理 + if (!empty($result)) { + if (!empty($this->model)) { + // 返回模型对象 + $result = $this->model->newInstance($result); + $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation']); + } + // 预载入查询 + if (!empty($options['with'])) { + $result->eagerlyResult($result, $options['with']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $result->relationCount($result, $options['with_count']); + } + } + } elseif (!empty($options['fail'])) { + $this->throwNotFound($options); + } + return $result; + } + + /** + * 查询失败 抛出异常 + * @access public + * @param array $options 查询参数 + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound($options = []) + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); + } else { + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); + } + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @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|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string $column 分批处理的字段名 + * @param string $order 排序规则 + * @return boolean + * @throws \LogicException + */ + public function chunk($count, $callback, $column = null, $order = 'asc') + { + $options = $this->getOptions(); + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + $column = $column ?: $this->getPk($options); + + if (isset($options['order'])) { + if (App::$debug) { + throw new \LogicException('chunk not support call order'); + } + unset($options['order']); + } + $bind = $this->bind; + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + if (strpos($column, '.')) { + list($alias, $key) = explode('.', $column); + } else { + $key = $column; + } + $query = $this->options($options)->limit($count); + } + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } + + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (is_array($column)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = end($resultSet); + $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; + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @return array + */ + public function getBind() + { + $bind = $this->bind; + $this->bind = []; + return $bind; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub + * @return string + * @throws DbException + */ + public function buildSql($sub = true) + { + return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + if (!is_null($data) && true !== $data) { + if (!isset($key) && !is_array($data)) { + // 缓存标识 + $key = 'think:' . $options['table'] . '|' . $data; + } + // AR模式分析主键条件 + $this->parsePkWhere($data, $options); + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + if (true !== $data && empty($options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + // 生成删除SQL语句 + $sql = $this->builder->delete($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + // 检测缓存 + if (isset($key) && Cache::get($key)) { + // 删除缓存 + Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); + } + // 执行操作 + $result = $this->execute($sql, $bind, $this); + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + $options['data'] = $data; + $this->trigger('after_delete', $options); + } + return $result; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access protected + * @return array + */ + protected function parseExpress() + { + $options = $this->options; + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + if (is_string($options['order'])) { + $options['order'] = explode(',', $options['order']); + } + foreach ($options['order'] as $key => $val) { + if (is_numeric($key)) { + if (strpos($val, ' ')) { + list($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]); + } + } + } + } + + if (!isset($options['field'])) { + $options['field'] = '*'; + } + + if (!isset($options['data'])) { + $options['data'] = []; + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) { + $options['master'] = true; + } + + foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + list($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['limit'] = $offset . ',' . $listRows; + } + + $this->options = []; + return $options; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public static function event($event, $callback) + { + self::$event[$event] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 额外参数 + * @return bool + */ + protected function trigger($event, $params = []) + { + $result = false; + if (isset(self::$event[$event])) { + $callback = self::$event[$event]; + $result = call_user_func_array($callback, [$params, $this]); + } + return $result; + } +} diff --git a/source/thinkphp/library/think/db/builder/Mysql.php b/source/thinkphp/library/think/db/builder/Mysql.php new file mode 100644 index 0000000..be2af71 --- /dev/null +++ b/source/thinkphp/library/think/db/builder/Mysql.php @@ -0,0 +1,137 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\Exception; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成insertall SQL + * @access public + * @param array $dataSet 数据集 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + * @throws Exception + */ + public function insertAll($dataSet, $options = [], $replace = false) + { + // 获取合法的字段 + if ('*' == $options['field']) { + $fields = array_keys($this->query->getFieldsType($options['table'])); + } else { + $fields = $options['field']; + } + + foreach ($dataSet as $data) { + foreach ($data as $key => $val) { + if (!in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + unset($data[$key]); + } elseif (is_null($val)) { + $data[$key] = 'NULL'; + } elseif (is_scalar($val)) { + $data[$key] = $this->parseValue($val, $key); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $data[$key] = $val->__toString(); + } else { + // 过滤掉非标量数据 + unset($data[$key]); + } + } + $value = array_values($data); + $values[] = '( ' . implode(',', $value) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_map([$this, 'parseKey'], array_keys($data)); + } + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $insertFields), + implode(' , ', $values), + $this->parseComment($options['comment']), + ], $this->insertAllSql); + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '$.') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('$.', $key); + return 'json_extract(' . $field . ', \'$.' . $name . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { + $key = '`' . $key . '`'; + } + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + $key = '`' . $table . '`.' . $key; + } + return $key; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'rand()'; + } + +} diff --git a/source/thinkphp/library/think/db/builder/Pgsql.php b/source/thinkphp/library/think/db/builder/Pgsql.php new file mode 100644 index 0000000..acc2289 --- /dev/null +++ b/source/thinkphp/library/think/db/builder/Pgsql.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return string + */ + public function parseLimit($limit) + { + $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 mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '$.') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('$.', $key); + $key = $field . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'RANDOM()'; + } + +} diff --git a/source/thinkphp/library/think/db/builder/Sqlite.php b/source/thinkphp/library/think/db/builder/Sqlite.php new file mode 100644 index 0000000..c727f04 --- /dev/null +++ b/source/thinkphp/library/think/db/builder/Sqlite.php @@ -0,0 +1,82 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + + /** + * limit + * @access public + * @param string $limit + * @return string + */ + public function parseLimit($limit) + { + $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 + * @return string + */ + protected function parseRand() + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } +} diff --git a/source/thinkphp/library/think/db/builder/Sqlsrv.php b/source/thinkphp/library/think/db/builder/Sqlsrv.php new file mode 100644 index 0000000..dc425d9 --- /dev/null +++ b/source/thinkphp/library/think/db/builder/Sqlsrv.php @@ -0,0 +1,137 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Expression; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + 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%'; + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param mixed $order + * @param array $options + * @return string + */ + protected function parseOrder($order, $options = []) + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif (is_numeric($key)) { + if (false === strpos($val, '(')) { + $array[] = $this->parseKey($val, $options); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand(); + } else { + $array[] = $val; + } + } else { + $sort = in_array(strtolower(trim($val)), ['asc', 'desc'], true) ? ' ' . $val : ''; + $array[] = $this->parseKey($key, $options, true) . ' ' . $sort; + } + } + + return ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + $key = trim($key); + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { + $key = '[' . $key . ']'; + } + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + return $key; + } + + /** + * limit + * @access protected + * @param mixed $limit + * @return string + */ + protected function parseLimit($limit) + { + 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($fields, $table, $options) + { + $this->selectSql = $this->selectInsertSql; + return parent::selectInsert($fields, $table, $options); + } + +} diff --git a/source/thinkphp/library/think/db/connector/Mysql.php b/source/thinkphp/library/think/db/connector/Mysql.php new file mode 100644 index 0000000..be1a85c --- /dev/null +++ b/source/thinkphp/library/think/db/connector/Mysql.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; +use think\Log; + +/** + * mysql数据库驱动 + */ +class Mysql extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Mysql'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + 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($tableName) + { + list($tableName) = explode(' ', $tableName); + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + $sql = 'SHOW COLUMNS FROM ' . $tableName; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($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']), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['key']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + ]; + } + } + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + $pdo = $this->linkID->query("EXPLAIN " . $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + $result = array_change_key_case($result); + if (isset($result['extra'])) { + if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { + Log::record('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + } + } + return $result; + } + + protected function supportSavepoint() + { + return true; + } + +} diff --git a/source/thinkphp/library/think/db/connector/Pgsql.php b/source/thinkphp/library/think/db/connector/Pgsql.php new file mode 100644 index 0000000..bbcf576 --- /dev/null +++ b/source/thinkphp/library/think/db/connector/Pgsql.php @@ -0,0 +1,103 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Connection +{ + protected $builder = '\\think\\db\\builder\\Pgsql'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $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($tableName) + { + + list($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->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($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($dbName = '') + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/source/thinkphp/library/think/db/connector/Sqlite.php b/source/thinkphp/library/think/db/connector/Sqlite.php new file mode 100644 index 0000000..c4e3a72 --- /dev/null +++ b/source/thinkphp/library/think/db/connector/Sqlite.php @@ -0,0 +1,104 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Sqlite'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlite:' . $config['database']; + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($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($dbName = '') + { + + $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->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/source/thinkphp/library/think/db/connector/Sqlsrv.php b/source/thinkphp/library/think/db/connector/Sqlsrv.php new file mode 100644 index 0000000..35c6600 --- /dev/null +++ b/source/thinkphp/library/think/db/connector/Sqlsrv.php @@ -0,0 +1,125 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Connection +{ + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + protected $builder = '\\think\\db\\builder\\Sqlsrv'; + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $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($tableName) + { + list($tableName) = explode(' ', $tableName); + $tableNames = explode('.', $tableName); + $tableName = isset($tableNames[1]) ? $tableNames[1] : $tableNames[0]; + + $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->query($sql, [], false, true); + $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' => (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'"; + // 调试开始 + $this->debug(true); + $pdo = $this->linkID->query($sql); + // 调试结束 + $this->debug(false, $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($dbName = '') + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } +} diff --git a/source/thinkphp/library/think/db/connector/pgsql.sql b/source/thinkphp/library/think/db/connector/pgsql.sql new file mode 100644 index 0000000..e1a09a3 --- /dev/null +++ b/source/thinkphp/library/think/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/source/thinkphp/library/think/db/exception/BindParamException.php b/source/thinkphp/library/think/db/exception/BindParamException.php new file mode 100644 index 0000000..4ed1954 --- /dev/null +++ b/source/thinkphp/library/think/db/exception/BindParamException.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct($message, $config, $sql, $bind, $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/source/thinkphp/library/think/db/exception/DataNotFoundException.php b/source/thinkphp/library/think/db/exception/DataNotFoundException.php new file mode 100644 index 0000000..f2542ac --- /dev/null +++ b/source/thinkphp/library/think/db/exception/DataNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct($message, $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/source/thinkphp/library/think/db/exception/ModelNotFoundException.php b/source/thinkphp/library/think/db/exception/ModelNotFoundException.php new file mode 100644 index 0000000..6e5f930 --- /dev/null +++ b/source/thinkphp/library/think/db/exception/ModelNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @param string $message + * @param string $model + */ + public function __construct($message, $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/source/thinkphp/library/think/debug/Console.php b/source/thinkphp/library/think/debug/Console.php new file mode 100644 index 0000000..c17911b --- /dev/null +++ b/source/thinkphp/library/think/debug/Console.php @@ -0,0 +1,160 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_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 $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console($type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['trace_tabs']); + $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, 1)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', json_encode($m)); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', $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); + } + +} diff --git a/source/thinkphp/library/think/debug/Html.php b/source/thinkphp/library/think/debug/Html.php new file mode 100644 index 0000000..b6be7ad --- /dev/null +++ b/source/thinkphp/library/think/debug/Html.php @@ -0,0 +1,111 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'trace_file' => '', + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + // 页面Trace信息 + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_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 $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['trace_file']; + return ob_get_clean(); + } + +} diff --git a/source/thinkphp/library/think/exception/ClassNotFoundException.php b/source/thinkphp/library/think/exception/ClassNotFoundException.php new file mode 100644 index 0000000..eb22e73 --- /dev/null +++ b/source/thinkphp/library/think/exception/ClassNotFoundException.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ClassNotFoundException extends \RuntimeException +{ + protected $class; + public function __construct($message, $class = '') + { + $this->message = $message; + $this->class = $class; + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/source/thinkphp/library/think/exception/DbException.php b/source/thinkphp/library/think/exception/DbException.php new file mode 100644 index 0000000..0ae80ad --- /dev/null +++ b/source/thinkphp/library/think/exception/DbException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct($message, array $config, $sql, $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/source/thinkphp/library/think/exception/ErrorException.php b/source/thinkphp/library/think/exception/ErrorException.php new file mode 100644 index 0000000..b3a9a30 --- /dev/null +++ b/source/thinkphp/library/think/exception/ErrorException.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +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; + + /** + * 错误异常构造函数 + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + * @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 + */ + public function __construct($severity, $message, $file, $line, array $context = []) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + + empty($context) || $this->setData('Error Context', $context); + } + + /** + * 获取错误级别 + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/source/thinkphp/library/think/exception/Handle.php b/source/thinkphp/library/think/exception/Handle.php new file mode 100644 index 0000000..f523db0 --- /dev/null +++ b/source/thinkphp/library/think/exception/Handle.php @@ -0,0 +1,282 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Exception; +use think\App; +use think\Config; +use think\console\Output; +use think\Lang; +use think\Log; +use think\Response; + +class Handle +{ + protected $render; + protected $ignoreReport = [ + '\\think\\exception\\HttpException', + ]; + + public function setRender($render) + { + $this->render = $render; + } + + /** + * Report or log an exception. + * + * @param \Exception $exception + * @return void + */ + public function report(Exception $exception) + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if (App::$debug) { + $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 (Config::get('record_trace')) { + $log .= "\r\n" . $exception->getTraceAsString(); + } + + Log::record($log, 'error'); + } + } + + protected function isIgnoreReport(Exception $exception) + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @param \Exception $e + * @return Response + */ + public function render(Exception $e) + { + if ($this->render && $this->render instanceof \Closure) { + $result = call_user_func_array($this->render, [$e]); + if ($result) { + return $result; + } + } + + if ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Output $output + * @param Exception $e + */ + public function renderForConsole(Output $output, Exception $e) + { + if (App::$debug) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + $output->renderException($e); + } + + /** + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + $template = Config::get('http_exception_template'); + if (!App::$debug && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Exception $exception + * @return Response + */ + protected function convertExceptionToResponse(Exception $exception) + { + // 收集异常数据 + if (App::$debug) { + // 调试模式,获取详细的错误信息 + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'trace' => $exception->getTrace(), + 'code' => $this->getCode($exception), + 'source' => $this->getSourceCode($exception), + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => $this->getConst(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!Config::get('show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = Config::get('error_message'); + } + } + + //保留一层 + while (ob_get_level() > 1) { + ob_end_clean(); + } + + $data['echo'] = ob_get_clean(); + + ob_start(); + extract($data); + include Config::get('exception_tmpl'); + // 获取并清空缓存 + $content = ob_get_clean(); + $response = new Response($content, 'html'); + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + if (!isset($statusCode)) { + $statusCode = 500; + } + $response->code($statusCode); + return $response; + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return integer 错误编码 + */ + protected function getCode(Exception $exception) + { + $code = $exception->getCode(); + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return string 错误信息 + */ + protected function getMessage(Exception $exception) + { + $message = $exception->getMessage(); + if (IS_CLI) { + return $message; + } + + 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行 + * @param \Exception $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Exception $exception) + { + // 读取前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返回类型显示 + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Exception $exception) + { + $data = []; + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + return $data; + } + + /** + * 获取常量列表 + * @return array 常量列表 + */ + private static function getConst() + { + return get_defined_constants(true)['user']; + } +} diff --git a/source/thinkphp/library/think/exception/HttpException.php b/source/thinkphp/library/think/exception/HttpException.php new file mode 100644 index 0000000..01a27fc --- /dev/null +++ b/source/thinkphp/library/think/exception/HttpException.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \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/source/thinkphp/library/think/exception/HttpResponseException.php b/source/thinkphp/library/think/exception/HttpResponseException.php new file mode 100644 index 0000000..5297286 --- /dev/null +++ b/source/thinkphp/library/think/exception/HttpResponseException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Response; + +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/source/thinkphp/library/think/exception/PDOException.php b/source/thinkphp/library/think/exception/PDOException.php new file mode 100644 index 0000000..044f82a --- /dev/null +++ b/source/thinkphp/library/think/exception/PDOException.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) + { + $error = $exception->errorInfo; + + $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($exception->getMessage(), $config, $sql, $code); + } +} diff --git a/source/thinkphp/library/think/exception/RouteNotFoundException.php b/source/thinkphp/library/think/exception/RouteNotFoundException.php new file mode 100644 index 0000000..d22e3a6 --- /dev/null +++ b/source/thinkphp/library/think/exception/RouteNotFoundException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/source/thinkphp/library/think/exception/TemplateNotFoundException.php b/source/thinkphp/library/think/exception/TemplateNotFoundException.php new file mode 100644 index 0000000..4202069 --- /dev/null +++ b/source/thinkphp/library/think/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct($message, $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate() + { + return $this->template; + } +} diff --git a/source/thinkphp/library/think/exception/ThrowableError.php b/source/thinkphp/library/think/exception/ThrowableError.php new file mode 100644 index 0000000..87b6b9d --- /dev/null +++ b/source/thinkphp/library/think/exception/ThrowableError.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ThrowableError extends \ErrorException +{ + public function __construct(\Throwable $e) + { + + if ($e instanceof \ParseError) { + $message = 'Parse error: ' . $e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: ' . $e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = 'Fatal error: ' . $e->getMessage(); + $severity = E_ERROR; + } + + parent::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/source/thinkphp/library/think/exception/ValidateException.php b/source/thinkphp/library/think/exception/ValidateException.php new file mode 100644 index 0000000..b368416 --- /dev/null +++ b/source/thinkphp/library/think/exception/ValidateException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error) + { + $this->error = $error; + $this->message = is_array($error) ? implode("\n\r", $error) : $error; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/source/thinkphp/library/think/log/driver/File.php b/source/thinkphp/library/think/log/driver/File.php new file mode 100644 index 0000000..f2296cf --- /dev/null +++ b/source/thinkphp/library/think/log/driver/File.php @@ -0,0 +1,270 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; +use think\Request; + +/** + * 本地化调试输出到文件 + */ +class File +{ + protected $config = [ + 'time_format' => ' c ', + 'single' => false, + 'file_size' => 2097152, + 'path' => LOG_PATH, + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; + } + + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + + $this->write($info[$type], $filename, true, $append); + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination, false, $append); + } + + return true; + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile() + { + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $destination = $this->config['path'] . $name . '.log'; + } else { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['max_files']) { + $filename = date('Ymd') . $cli . '.log'; + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + } + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + 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 . $cli; + } else { + $name = date('d') . '_' . $type . $cli; + } + + return $path . DIRECTORY_SEPARATOR . $name . '.log'; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg; + } + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); + } + + return error_log($message, 3, $destination); + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + } + } + + /** + * CLI日志解析 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseCliLog($info) + { + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); + + $message = implode("\r\n", $info); + + $message = "[{$now}]" . $message . "\r\n"; + } + + return $message; + } + + /** + * 解析日志 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseLog($info) + { + $request = Request::instance(); + $requestInfo = [ + 'ip' => $request->ip(), + 'method' => $request->method(), + 'host' => $request->host(), + 'uri' => $request->url(), + ]; + + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } + + array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); + + return implode("\r\n", $info) . "\r\n"; + } + + protected function getDebugLog(&$info, $append, $apart) + { + if (App::$debug && $append) { + + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; + + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + array_unshift($info, $time_str . $memory_str . $file_load); + } + } + } +} diff --git a/source/thinkphp/library/think/log/driver/Socket.php b/source/thinkphp/library/think/log/driver/Socket.php new file mode 100644 index 0000000..4f62915 --- /dev/null +++ b/source/thinkphp/library/think/log/driver/Socket.php @@ -0,0 +1,250 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket +{ + public $port = 1116; //SocketLog 服务的http的端口号 + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + ]; + + 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 + + /** + * 构造函数 + * @param array $config 缓存参数 + * @access public + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + if (!$this->check()) { + return false; + } + $trace = []; + if (App::$debug) { + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; + } + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => isset($this->css[$type]) ? $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 (!$client_id = $this->getClientArg('client_id')) { + $client_id = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $force_client_id) { + $client_id = $force_client_id; + $this->sendToClient($tabid, $client_id, $trace, $force_client_id); + } + } else { + $this->sendToClient($tabid, $client_id, $trace, ''); + } + return true; + } + + /** + * 发送给指定客户端 + * @author Zjmainstay + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id + */ + protected function sendToClient($tabid, $client_id, $logs, $force_client_id) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $client_id, + 'logs' => $logs, + 'force_client_id' => $force_client_id, + ]; + $msg = @json_encode($logs); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + $this->send($this->config['host'], $msg, $address); + } + + protected function check() + { + $tabid = $this->getClientArg('tabid'); + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + //用户认证 + $allow_client_ids = $this->config['allow_client_ids']; + if (!empty($allow_client_ids)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $client_id = $this->getClientArg('client_id'); + if (!in_array($client_id, $allow_client_ids)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + return true; + } + + protected function getClientArg($name) + { + static $args = []; + + $key = 'HTTP_USER_AGENT'; + + if (isset($_SERVER['HTTP_SOCKETLOG'])) { + $key = 'HTTP_SOCKETLOG'; + } + + if (!isset($_SERVER[$key])) { + return; + } + if (empty($args)) { + if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { + $args = ['tabid' => null]; + return; + } + parse_str($match[1], $args); + } + if (isset($args[$name])) { + return $args[$name]; + } + return; + } + + /** + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $this->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, 1); + curl_setopt($ch, 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/source/thinkphp/library/think/log/driver/Test.php b/source/thinkphp/library/think/log/driver/Test.php new file mode 100644 index 0000000..7f66338 --- /dev/null +++ b/source/thinkphp/library/think/log/driver/Test.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +/** + * 模拟测试输出 + */ +class Test +{ + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = []) + { + return true; + } + +} diff --git a/source/thinkphp/library/think/model/Collection.php b/source/thinkphp/library/think/model/Collection.php new file mode 100644 index 0000000..0406533 --- /dev/null +++ b/source/thinkphp/library/think/model/Collection.php @@ -0,0 +1,79 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + return $this; + } + + /** + * 设置需要输出的属性 + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model && $model->append($append, $override); + }); + return $this; + } + +} diff --git a/source/thinkphp/library/think/model/Merge.php b/source/thinkphp/library/think/model/Merge.php new file mode 100644 index 0000000..4a9da81 --- /dev/null +++ b/source/thinkphp/library/think/model/Merge.php @@ -0,0 +1,322 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Db; +use think\db\Query; +use think\Model; + +class Merge extends Model +{ + + protected $relationModel = []; // HAS ONE 关联的模型列表 + protected $fk = ''; // 外键名 默认为主表名_id + protected $mapFields = []; // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) + + /** + * 构造函数 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + parent::__construct($data); + + // 设置默认外键名 仅支持单一外键 + if (empty($this->fk)) { + $this->fk = strtolower($this->name) . '_id'; + } + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param string|array $with 关联预查询 + * @param bool $cache 是否缓存 + * @return \think\Model + */ + public static function get($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->find($data); + } + + /** + * 附加查询表达式 + * @access protected + * @param \think\db\Query $query 查询对象 + * @return \think\db\Query + */ + protected static function attachQuery($query) + { + $class = new static(); + $master = $class->name; + $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); + $query->alias($master)->field($fields); + + foreach ($class->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $query->getTable($name) : $model; + $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); + $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); + $query->field($fields); + } + return $query; + } + + /** + * 获取关联模型的字段 并解决混淆 + * @access protected + * @param \think\db\Query $query 查询对象 + * @param string $name 模型名称 + * @param string $table 关联表名称 + * @param array $map 字段映射 + * @param array $fields 查询字段 + * @return array + */ + protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) + { + // 获取模型的字段信息 + $fields = $fields ?: $query->getTableInfo($table, 'fields'); + $array = []; + foreach ($fields as $field) { + if ($key = array_search($name . '.' . $field, $map)) { + // 需要处理映射字段 + $array[] = $name . '.' . $field . ' AS ' . $key; + } else { + $array[] = $field; + } + } + return $array; + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache + * @return array|false|string + */ + public static function all($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->select($data); + } + + /** + * 处理写入的模型数据 + * @access public + * @param string $model 模型名称 + * @param array $data 数据 + * @return array + */ + protected function parseData($model, $data) + { + $item = []; + foreach ($data as $key => $val) { + if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { + list($name, $key) = explode('.', $this->mapFields[$key]); + if ($model == $name) { + $item[$key] = $val; + } + } else { + $item[$key] = $val; + } + } + return $item; + } + + /** + * 保存模型数据 以及关联数据 + * @access public + * @param mixed $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return false|int + * @throws \Exception + */ + public function save($data = [], $where = [], $sequence = null) + { + if (!empty($data)) { + // 数据自动验证 + if (!$this->validateData($data)) { + return false; + } + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + if (!empty($where)) { + $this->isUpdate = true; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 自动写入更新时间 + if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { + $this->setAttr($this->updateTime, null); + } + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + $pk = $this->getPk(); + try { + if ($this->isUpdate) { + // 自动写入 + $this->autoCompleteData($this->update); + + if (false === $this->trigger('before_update', $this)) { + return false; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + // 处理模型数据 + $data = $this->parseData($this->name, $data); + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } + // 写入主表数据 + $result = $db->strict(false)->where($where)->update($data); + + // 写入附表数据 + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $data); + if (Db::table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { + $result = 1; + } + } + + // 新增回调 + $this->trigger('after_update', $this); + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间 + if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { + $this->setAttr($this->createTime, null); + } + + if (false === $this->trigger('before_insert', $this)) { + return false; + } + + // 处理模型数据 + $data = $this->parseData($this->name, $this->data); + // 写入主表数据 + $result = $db->name($this->name)->strict(false)->insert($data); + if ($result) { + $insertId = $db->getLastInsID($sequence); + // 写入外键数据 + if ($insertId) { + if (is_string($pk)) { + $this->data[$pk] = $insertId; + } + $this->data[$this->fk] = $insertId; + } + + // 写入附表数据 + $source = $this->data; + if ($insertId && is_string($pk) && isset($source[$pk]) && $this->fk != $pk) { + unset($source[$pk]); + } + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $source); + Db::table($table)->strict(false)->insert($data); + } + } + // 标记为更新 + $this->isUpdate = true; + // 新增回调 + $this->trigger('after_insert', $this); + } + $db->commit(); + // 写入回调 + $this->trigger('after_write', $this); + + $this->origin = $this->data; + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 删除当前的记录 并删除关联数据 + * @access public + * @return int + * @throws \Exception + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + try { + $result = $db->delete($this->data); + if ($result) { + // 获取主键数据 + $pk = $this->data[$this->getPk()]; + + // 删除关联数据 + foreach ($this->relationModel as $key => $model) { + $table = is_int($key) ? $db->getTable($model) : $model; + $query = new Query; + $query->table($table)->where($this->fk, $pk)->delete(); + } + } + $this->trigger('after_delete', $this); + $db->commit(); + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + +} diff --git a/source/thinkphp/library/think/model/Pivot.php b/source/thinkphp/library/think/model/Pivot.php new file mode 100644 index 0000000..13525cd --- /dev/null +++ b/source/thinkphp/library/think/model/Pivot.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Model; + +class Pivot extends Model +{ + + /** @var Model */ + public $parent; + + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct($data = [], Model $parent = null, $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/source/thinkphp/library/think/model/Relation.php b/source/thinkphp/library/think/model/Relation.php new file mode 100644 index 0000000..25fe88d --- /dev/null +++ b/source/thinkphp/library/think/model/Relation.php @@ -0,0 +1,155 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\db\Query; +use think\Exception; +use think\Model; + +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation +{ + // 父模型对象 + protected $parent; + /** @var Model 当前关联的模型类 */ + protected $model; + /** @var Query 关联模型查询对象 */ + protected $query; + // 关联表外键 + protected $foreignKey; + // 关联表主键 + protected $localKey; + // 基础查询 + protected $baseQuery; + // 是否为自关联 + protected $selfRelation; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前的关联模型对象实例 + * @access public + * @return Model + */ + public function getModel() + { + return $this->query->getModel(); + } + + /** + * 获取关联的查询对象 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 设置当前关联为自关联 + * @access public + * @param bool $self 是否自关联 + * @return $this + */ + public function selfRelation($self = true) + { + $this->selfRelation = $self; + return $this; + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation() + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @return mixed + */ + protected function resultSetBuild($resultSet) + { + return (new $this->model)->toCollection($resultSet); + } + + protected function getQueryFields($model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, $model) + { + if ($fields) { + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + } else { + $fields = $model . '.*'; + } + + return $fields; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof Query) { + return $this; + } else { + $this->baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/source/thinkphp/library/think/model/relation/BelongsTo.php b/source/thinkphp/library/think/model/relation/BelongsTo.php new file mode 100644 index 0000000..c1cbab9 --- /dev/null +++ b/source/thinkphp/library/think/model/relation/BelongsTo.php @@ -0,0 +1,243 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class BelongsTo extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $joinType JOIN类型 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + $this->relation = $relation; + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @access public + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $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($this->query, [ + $localKey => [ + 'in', + $range, + ], + ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere($this->query, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $foreignKey = $this->foreignKey; + $pk = $model->getPk(); + + $this->parent->setAttr($foreignKey, $model->$pk); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $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() + { + 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/source/thinkphp/library/think/model/relation/BelongsToMany.php b/source/thinkphp/library/think/model/relation/BelongsToMany.php new file mode 100644 index 0000000..a41c45c --- /dev/null +++ b/source/thinkphp/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,644 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Collection; +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +class BelongsToMany extends Relation +{ + // 中间表表名 + protected $middle; + // 中间表模型名称 + protected $pivotName; + // 中间表模型对象 + protected $pivot; + // 中间表数据名称 + protected $pivotDataName = 'pivot'; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + if (false !== strpos($table, '\\')) { + $this->pivotName = $table; + $this->middle = basename(str_replace('\\', '/', $table)); + } else { + $this->middle = $table; + } + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + + if ('think\model\Pivot' == get_class($this->pivot)) { + $this->pivot->name($this->middle); + } + } + + /** + * 设置中间表模型 + * @param $pivot + * @return $this + */ + public function pivot($pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function pivotDataName($name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 获取中间表更新条件 + * @param $data + * @return array + */ + protected function getUpdateWhere($data) + { + return [ + $this->localKey => $data[$this->localKey], + $this->foreignKey => $data[$this->foreignKey], + ]; + } + + /** + * 实例化中间表模型 + * @param array $data + * @param bool $isUpdate + * @return Pivot + * @throws Exception + */ + protected function newPivot($data = [], $isUpdate = false) + { + $class = $this->pivotName ?: '\\think\\model\\Pivot'; + $pivot = new $class($data, $this->parent, $this->middle); + if ($pivot instanceof Pivot) { + return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 合成中间表模型 + * @param array|Collection|Paginator $models + */ + protected function hydratePivot($models) + { + foreach ($models as $model) { + $pivot = []; + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + } + } + + /** + * 创建关联查询Query对象 + * @return Query + */ + protected function buildQuery() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $pk = $this->parent->getPk(); + // 关联查询 + $condition['pivot.' . $localKey] = $this->parent->$pk; + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $result = $this->buildQuery()->relation($subRelation)->select(); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载select方法 + * @param null $data + * @return false|\PDOStatement|string|Collection + */ + public function select($data = null) + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载paginate方法 + * @param null $listRows + * @param bool $simple + * @param array $config + * @return Paginator + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + $result = $this->buildQuery()->paginate($listRows, $simple, $config); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载find方法 + * @param null $data + * @return array|false|\PDOStatement|string|Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + if ($result) { + $this->hydratePivot([$result]); + } + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @param $field + * @param null $op + * @param null $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $field = 'pivot.' . $field; + $this->query->where($field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $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, + ], + ], $relation, $subRelation); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + $pk = $result->$pk; + $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + } + return $count; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + 'pivot.' . $this->localKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->count(); + } + + /** + * 多对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + $data[$pivot[$this->localKey]][] = $set; + } + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access public + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->getTable(); + $fields = $this->getQueryFields($tableName); + + $query = $this->query->field($fields) + ->field(true, false, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + return $query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return integer + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = false; + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + $result = $this->attach($data, $pivotData); + } + return $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $model->save($data); + $id = $model->getLastInsID(); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->insert($pivot, true); + $result[] = $this->newPivot($pivot, true); + } + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot + * @throws Exception + */ + public function attached($data) + { + if ($data instanceof Model) { + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } else { + $id = $data; + } + + $pk = $this->parent->getPk(); + + $pivot = $this->pivot->where($this->localKey, $this->parent->$pk)->where($this->foreignKey, $id)->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + if (isset($id)) { + $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + } + $this->pivot->where($pivot)->delete(); + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + } + + /** + * 数据同步 + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + $pk = $this->parent->getPk(); + $current = $this->pivot->where($this->localKey, $this->parent->$pk) + ->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() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $table = $this->pivot->getTable(); + $this->query->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/source/thinkphp/library/think/model/relation/HasMany.php b/source/thinkphp/library/think/model/relation/HasMany.php new file mode 100644 index 0000000..ebab051 --- /dev/null +++ b/source/thinkphp/library/think/model/relation/HasMany.php @@ -0,0 +1,318 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +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, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany($this->query, [ + $this->foreignKey => [ + 'in', + $range, + ], + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $data = $this->eagerlyOneToMany($this->query, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + // 关联数据封装 + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $localKey = $this->localKey; + $count = 0; + if (isset($result->$localKey)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where($this->foreignKey, $result->$localKey)->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + $localKey = $this->localKey ?: $this->parent->getPk(); + return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count(); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure + * @return array + */ + protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + { + $foreignKey = $this->foreignKey; + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + } + $list = $model->removeWhereField($foreignKey)->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + $model = new $this->model(); + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->group($model . '.' . $this->localKey) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->where($where); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + 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/source/thinkphp/library/think/model/relation/HasManyThrough.php b/source/thinkphp/library/think/model/relation/HasManyThrough.php new file mode 100644 index 0000000..3a9a548 --- /dev/null +++ b/source/thinkphp/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,157 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 关联主键 + */ + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->through = $through; + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + {} + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + {} + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + {} + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $through = $this->through; + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $through::getTable(); + $pk = (new $through)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $this->query->field($alias . '.*')->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/source/thinkphp/library/think/model/relation/HasOne.php b/source/thinkphp/library/think/model/relation/HasOne.php new file mode 100644 index 0000000..db74e4a --- /dev/null +++ b/source/thinkphp/library/think/model/relation/HasOne.php @@ -0,0 +1,215 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param string $joinType JOIN类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + // 执行关联定义方法 + $localKey = $this->localKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @return Query + */ + public function has() + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation])->field($relation . '.' . $foreignKey)->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $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->query, [ + $foreignKey => [ + 'in', + $range, + ], + ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere($this->query, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + 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/source/thinkphp/library/think/model/relation/MorphMany.php b/source/thinkphp/library/think/model/relation/MorphMany.php new file mode 100644 index 0000000..2755d57 --- /dev/null +++ b/source/thinkphp/library/think/model/relation/MorphMany.php @@ -0,0 +1,314 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $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->eagerlyMorphToMany([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $data = $this->eagerlyMorphToMany([ + $this->morphKey => $result->$pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query->where([ + $this->morphKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + $this->morphType => $this->type, + ])->fetchSql()->count(); + } + + /** + * 多态一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + $model = new $this->model(); + + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + 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 array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/source/thinkphp/library/think/model/relation/MorphOne.php b/source/thinkphp/library/think/model/relation/MorphOne.php new file mode 100644 index 0000000..5ec7172 --- /dev/null +++ b/source/thinkphp/library/think/model/relation/MorphOne.php @@ -0,0 +1,263 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphOne extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $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, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + $this->morphKey => $pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->find(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + $model = new $this->model(); + + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + 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() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/source/thinkphp/library/think/model/relation/MorphTo.php b/source/thinkphp/library/think/model/relation/MorphTo.php new file mode 100644 index 0000000..7d45265 --- /dev/null +++ b/source/thinkphp/library/think/model/relation/MorphTo.php @@ -0,0 +1,299 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + protected $relation; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + return (new $model); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return mixed + */ + public function getRelation($subRelation = '', $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类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + 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, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + throw new Exception('relation data not exists :' . $this->model); + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + $result->setRelation($attr, $relationModel); + } + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 多态MorphTo 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param string $relation 关联名 + * @param $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + if ($data) { + $data->setParent(clone $result); + $data->isUpdate(true); + } + $result->setRelation(Loader::parseName($relation), $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate($model, $type = '') + { + $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() + { + $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); + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/source/thinkphp/library/think/model/relation/OneToOne.php b/source/thinkphp/library/think/model/relation/OneToOne.php new file mode 100644 index 0000000..353ce21 --- /dev/null +++ b/source/thinkphp/library/think/model/relation/OneToOne.php @@ -0,0 +1,337 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + // 关联方法名 + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($query->getModel())))); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $name); + $field = null; + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + } else { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + } + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [ & $query]); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } + $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr() + { + return $this->bindAttr; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } else { + $relationModel = null; + } + $result->setRelation(Loader::parseName($relation), $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @param array $bindAttr 绑定属性 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result, $bindAttr) + { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($result->$key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $result->setAttr($key, $model ? $model->$attr : null); + } + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure + * @return array + */ + protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + { + $this->baseQuery = true; + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + if ($field = $model->getOptions('with_field')) { + $model->field($field)->removeOption('with_field'); + } + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$key] = $set; + } + return $data; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/source/thinkphp/library/think/paginator/driver/Bootstrap.php b/source/thinkphp/library/think/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..c5ac60d --- /dev/null +++ b/source/thinkphp/library/think/paginator/driver/Bootstrap.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton($text = "«") + { + + 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($text = '»') + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks() + { + 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 int $page + * @return string + */ + protected function getAvailablePageWrapper($url, $page) + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots() + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls) + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getPageLinkWrapper($url, $page) + { + if ($page == $this->currentPage()) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/source/thinkphp/library/think/process/Builder.php b/source/thinkphp/library/think/process/Builder.php new file mode 100644 index 0000000..da56163 --- /dev/null +++ b/source/thinkphp/library/think/process/Builder.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +use think\Process; + +class Builder +{ + private $arguments; + private $cwd; + private $env = null; + private $input; + private $timeout = 60; + private $options = []; + private $inheritEnv = true; + private $prefix = []; + private $outputDisabled = false; + + /** + * 构造方法 + * @param string[] $arguments 参数 + */ + public function __construct(array $arguments = []) + { + $this->arguments = $arguments; + } + + /** + * 创建一个实例 + * @param string[] $arguments 参数 + * @return self + */ + public static function create(array $arguments = []) + { + return new static($arguments); + } + + /** + * 添加一个参数 + * @param string $argument 参数 + * @return self + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * 添加一个前缀 + * @param string|array $prefix + * @return self + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : [$prefix]; + + return $this; + } + + /** + * 设置参数 + * @param string[] $arguments + * @return self + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * 设置工作目录 + * @param null|string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * 是否初始化环境变量 + * @param bool $inheritEnv + * @return self + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * 设置环境变量 + * @param string $name + * @param null|string $value + * @return self + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * 添加环境变量 + * @param array $variables + * @return self + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) + { + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 设置超时时间 + * @param float|null $timeout + * @return self + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * 设置proc_open选项 + * @param string $name + * @param string $value + * @return self + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + /** + * 禁止输出 + * @return self + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启输出 + * @return self + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * 创建一个Process实例 + * @return Process + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new \LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = array_merge($this->prefix, $this->arguments); + $script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); + + if ($this->inheritEnv) { + // include $_ENV for BC purposes + $env = array_replace($_ENV, $_SERVER, $this->env); + } else { + $env = $this->env; + } + + $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/source/thinkphp/library/think/process/Utils.php b/source/thinkphp/library/think/process/Utils.php new file mode 100644 index 0000000..f94c648 --- /dev/null +++ b/source/thinkphp/library/think/process/Utils.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +class Utils +{ + + /** + * 转义字符串 + * @param string $argument + * @return string + */ + public static function escapeArgument($argument) + { + + if ('' === $argument) { + return escapeshellarg($argument); + } + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"' . $escapedArgument . '"'; + } + return $escapedArgument; + } + + /** + * 验证并进行规范化Process输入。 + * @param string $caller + * @param mixed $input + * @return string + * @throws \InvalidArgumentException + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + } + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } + +} diff --git a/source/thinkphp/library/think/process/exception/Failed.php b/source/thinkphp/library/think/process/exception/Failed.php new file mode 100644 index 0000000..5295082 --- /dev/null +++ b/source/thinkphp/library/think/process/exception/Failed.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed 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.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + 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/source/thinkphp/library/think/process/exception/Timeout.php b/source/thinkphp/library/think/process/exception/Timeout.php new file mode 100644 index 0000000..d5f1162 --- /dev/null +++ b/source/thinkphp/library/think/process/exception/Timeout.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Timeout extends \RuntimeException +{ + + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $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 $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + 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/source/thinkphp/library/think/process/pipes/Pipes.php b/source/thinkphp/library/think/process/pipes/Pipes.php new file mode 100644 index 0000000..82396b8 --- /dev/null +++ b/source/thinkphp/library/think/process/pipes/Pipes.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +abstract class Pipes +{ + + /** @var array */ + public $pipes = []; + + /** @var string */ + protected $inputBuffer = ''; + /** @var resource|null */ + protected $input; + + /** @var bool */ + private $blocked = true; + + const CHUNK_SIZE = 16384; + + /** + * 返回用于 proc_open 描述符的数组 + * @return array + */ + abstract public function getDescriptors(); + + /** + * 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 + * @return string[] + */ + abstract public function getFiles(); + + /** + * 文件句柄和管道中读取数据。 + * @param bool $blocking 是否使用阻塞调用 + * @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 + * @return string[] + */ + abstract public function readAndWrite($blocking, $close = false); + + /** + * 返回当前状态如果有打开的文件句柄或管道。 + * @return bool + */ + abstract public function areOpen(); + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = []; + } + + /** + * 检查系统调用已被中断 + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (null !== $this->input) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } +} diff --git a/source/thinkphp/library/think/process/pipes/Unix.php b/source/thinkphp/library/think/process/pipes/Unix.php new file mode 100644 index 0000000..fd99a5d --- /dev/null +++ b/source/thinkphp/library/think/process/pipes/Unix.php @@ -0,0 +1,196 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Unix extends Pipes +{ + + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $disableOutput; + + public function __construct($ttyMode, $ptyMode, $input, $disableOutput) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $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() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + + if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + if (empty($this->pipes)) { + return []; + } + + $this->unblock(); + + $read = []; + + if (null !== $this->input) { + $r = array_merge($this->pipes, ['input' => $this->input]); + } else { + $r = $this->pipes; + } + + unset($r[0]); + + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + + if (0 === $n) { + return $read; + } + + foreach ($r as $pipe) { + + $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; + $data = ''; + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { + $data .= $dataread; + } + + if ('' !== $data) { + if ('input' === $type) { + $this->inputBuffer .= $data; + } else { + $read[$type] = $data; + } + } + + if (false === $data || (true === $close && feof($pipe) && '' === $data)) { + if ('input' === $type) { + $this->input = null; + } else { + fclose($this->pipes[$type]); + unset($this->pipes[$type]); + } + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } + + /** + * 创建一个新的 UnixPipes 实例 + * @param Process $process + * @param string|resource $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); + } +} diff --git a/source/thinkphp/library/think/process/pipes/Windows.php b/source/thinkphp/library/think/process/pipes/Windows.php new file mode 100644 index 0000000..bba7e9b --- /dev/null +++ b/source/thinkphp/library/think/process/pipes/Windows.php @@ -0,0 +1,228 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Windows extends Pipes +{ + + /** @var array */ + private $files = []; + /** @var array */ + private $fileHandles = []; + /** @var array */ + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + /** @var bool */ + private $disableOutput; + + public function __construct($disableOutput, $input) + { + $this->disableOutput = (bool) $disableOutput; + + if (!$this->disableOutput) { + + $this->files = [ + Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), + Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), + ]; + foreach ($this->files as $offset => $file) { + $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); + if (false === $this->fileHandles[$offset]) { + throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + } + } + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = $input; + } + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->write($blocking, $close); + + $read = []; + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + if (0 !== fseek($fileHandle, $this->readBytes[$type])) { + continue; + } + $data = ''; + $dataread = null; + while (!feof($fileHandle)) { + if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { + $data .= $dataread; + } + } + if (0 < $length = strlen($data)) { + $this->readBytes[$type] += $length; + $read[$type] = $data; + } + + if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { + fclose($this->fileHandles[$type]); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes && (bool) $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = []; + } + + /** + * 创建一个新的 WindowsPipes 实例。 + * @param Process $process + * @param $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isOutputDisabled(), $input); + } + + /** + * 删除临时文件 + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = []; + } + + /** + * 写入到 stdin 输入 + * @param bool $blocking + * @param bool $close + */ + private function write($blocking, $close) + { + if (empty($this->pipes)) { + return; + } + + $this->unblock(); + + $r = null !== $this->input ? ['input' => $this->input] : null; + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return; + } + + if (0 === $n) { + return; + } + + if (null !== $w && 0 < count($r)) { + $data = ''; + while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { + $data .= $dataread; + } + + $this->inputBuffer .= $data; + + if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { + $this->input = null; + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + } +} diff --git a/source/thinkphp/library/think/response/Json.php b/source/thinkphp/library/think/response/Json.php new file mode 100644 index 0000000..c906bfc --- /dev/null +++ b/source/thinkphp/library/think/response/Json.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/source/thinkphp/library/think/response/Jsonp.php b/source/thinkphp/library/think/response/Jsonp.php new file mode 100644 index 0000000..404bacb --- /dev/null +++ b/source/thinkphp/library/think/response/Jsonp.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\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'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + 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/source/thinkphp/library/think/response/Redirect.php b/source/thinkphp/library/think/response/Redirect.php new file mode 100644 index 0000000..91694cb --- /dev/null +++ b/source/thinkphp/library/think/response/Redirect.php @@ -0,0 +1,105 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\Response; +use think\Session; +use think\Url; + +class Redirect extends Response +{ + + protected $options = []; + + // URL参数 + protected $params = []; + + public function __construct($data = '', $code = 302, array $header = [], array $options = []) + { + parent::__construct($data, $code, $header, $options); + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + $this->header['Location'] = $this->getTargetUrl(); + 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) { + Session::flash($key, $val); + } + } else { + Session::flash($name, $value); + } + return $this; + } + + /** + * 获取跳转地址 + * @return string + */ + public function getTargetUrl() + { + if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { + return $this->data; + } else { + return Url::build($this->data, $this->params); + } + } + + public function params($params = []) + { + $this->params = $params; + return $this; + } + + /** + * 记住当前url后跳转 + * @return $this + */ + public function remember() + { + Session::set('redirect_url', Request::instance()->url()); + return $this; + } + + /** + * 跳转到上次记住的url + * @return $this + */ + public function restore() + { + if (Session::has('redirect_url')) { + $this->data = Session::get('redirect_url'); + Session::delete('redirect_url'); + } + return $this; + } +} diff --git a/source/thinkphp/library/think/response/View.php b/source/thinkphp/library/think/response/View.php new file mode 100644 index 0000000..48f944a --- /dev/null +++ b/source/thinkphp/library/think/response/View.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Config; +use think\Response; +use think\View as ViewTemplate; + +class View extends Response +{ + // 输出参数 + protected $options = []; + protected $vars = []; + protected $replace = []; + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // 渲染模板输出 + return ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch($data, $this->vars, $this->replace); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars($name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return isset($this->vars[$name]) ? $this->vars[$name] : null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + return $this; + } else { + $this->vars[$name] = $value; + } + return $this; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + +} diff --git a/source/thinkphp/library/think/response/Xml.php b/source/thinkphp/library/think/response/Xml.php new file mode 100644 index 0000000..3bdc052 --- /dev/null +++ b/source/thinkphp/library/think/response/Xml.php @@ -0,0 +1,102 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Collection; +use think\Model; +use think\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'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($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编码 + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) + { + 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编码 + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, $item, $id) + { + $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/source/thinkphp/library/think/session/driver/Memcache.php b/source/thinkphp/library/think/session/driver/Memcache.php new file mode 100644 index 0000000..877d7bd --- /dev/null +++ b/source/thinkphp/library/think/session/driver/Memcache.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcache extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'persistent' => true, // 长连接 + 'session_name' => '', // memcache key前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcache')) { + throw new Exception('not support:memcache'); + } + $this->handler = new \Memcache; + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->config['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : + $this->handler->addServer($host, $port, $this->config['persistent'], 1); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/source/thinkphp/library/think/session/driver/Memcached.php b/source/thinkphp/library/think/session/driver/Memcached.php new file mode 100644 index 0000000..2994a07 --- /dev/null +++ b/source/thinkphp/library/think/session/driver/Memcached.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcached extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'session_name' => '', // memcache key前缀 + 'username' => '', //账号 + 'password' => '', //密码 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcached')) { + throw new Exception('not support:memcached'); + } + $this->handler = new \Memcached; + // 设置连接超时时间(单位:毫秒) + if ($this->config['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); + } + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + $this->handler->addServers($servers); + if ('' != $this->config['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->quit(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/source/thinkphp/library/think/session/driver/Redis.php b/source/thinkphp/library/think/session/driver/Redis.php new file mode 100644 index 0000000..8d05126 --- /dev/null +++ b/source/thinkphp/library/think/session/driver/Redis.php @@ -0,0 +1,128 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Redis extends SessionHandler +{ + /** @var \Redis */ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // redis主机 + 'port' => 6379, // redis端口 + 'password' => '', // 密码 + 'select' => 0, // 操作库 + 'expire' => 3600, // 有效期(秒) + 'timeout' => 0, // 超时时间(秒) + 'persistent' => true, // 是否长连接 + 'session_name' => '', // sessionkey前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + * @return bool + * @throws Exception + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('redis')) { + throw new Exception('not support:redis'); + } + $this->handler = new \Redis; + + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } + + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + if ($this->config['expire'] > 0) { + return $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); + } else { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData); + } + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID) > 0; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return bool + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/source/thinkphp/library/think/template/TagLib.php b/source/thinkphp/library/think/template/TagLib.php new file mode 100644 index 0000000..c5b72f9 --- /dev/null +++ b/source/thinkphp/library/think/template/TagLib.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use think\Exception; + +/** + * 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 \stdClass $template 模板引擎对象 + */ + public function __construct($template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(&$content, $lib = '') + { + $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); + } + return; + } + + /** + * 按标签生成正则 + * @access private + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, $close) + { + $begin = $this->tpl->config('taglib_begin'); + $end = $this->tpl->config('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($str, $name, $alias = '') + { + $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->config('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->config('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($condition) + { + if (strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(&$name) + { + $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); + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + // 获取标签定义 + public function getTags() + { + return $this->tags; + } +} diff --git a/source/thinkphp/library/think/template/driver/File.php b/source/thinkphp/library/think/template/driver/File.php new file mode 100644 index 0000000..a9a86bf --- /dev/null +++ b/source/thinkphp/library/think/template/driver/File.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use think\Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void|array + */ + public function write($cacheFile, $content) + { + // 检测模板目录 + $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); + } + } + + /** + * 读取编译编译 + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read($cacheFile, $vars = []) + { + $this->cacheFile = $cacheFile; + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return boolean + */ + public function check($cacheFile, $cacheTime) + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + if (0 != $cacheTime && $_SERVER['REQUEST_TIME'] > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + return true; + } +} diff --git a/source/thinkphp/library/think/template/taglib/Cx.php b/source/thinkphp/library/think/template/taglib/Cx.php new file mode 100644 index 0000000..31e0698 --- /dev/null +++ b/source/thinkphp/library/think/template/taglib/Cx.php @@ -0,0 +1,673 @@ + +// +---------------------------------------------------------------------- + +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($tag, $content) + { + $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|void + */ + public function tagVolist($tag, $content) + { + $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 .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * 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|void + */ + public function tagForeach($tag, $content) + { + // 直接使用表达式 + 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 .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * 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($tag, $content) + { + $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($tag, $content) + { + $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($tag) + { + $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($tag, $content) + { + $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($tag, $content) + { + $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 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefault($tag) + { + $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($tag, $content) + { + $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($tag, $content) + { + $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($tag, $content) + { + $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($tag, $content) + { + $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($tag, $content) + { + $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($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined($tag, $content) + { + $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($tag, $content) + { + $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($tag, $content) + { + $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($tag, $content) + { + $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($tag, $content) + { + //设置默认值 + $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($tag, $content) + { + $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($tag, $content) + { + $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/source/thinkphp/library/think/view/driver/Php.php b/source/thinkphp/library/think/view/driver/Php.php new file mode 100644 index 0000000..f594a43 --- /dev/null +++ b/source/thinkphp/library/think/view/driver/Php.php @@ -0,0 +1,160 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; + +class Php +{ + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DS, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + protected $template; + protected $content; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + 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($template, $data = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + $this->template = $template; + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + + extract($data, EXTR_OVERWRITE); + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display($content, $data = []) + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return void + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return isset($this->config[$name]) ? $this->config[$name] : null; + } else { + $this->config[$name] = $value; + } + } + +} diff --git a/source/thinkphp/library/think/view/driver/Think.php b/source/thinkphp/library/think/view/driver/Think.php new file mode 100644 index 0000000..a314ad6 --- /dev/null +++ b/source/thinkphp/library/think/view/driver/Think.php @@ -0,0 +1,167 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; +use think\Template; + +class Think +{ + // 模板引擎实例 + private $template; + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $this->template = new Template($this->config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $data = [], $config = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + $this->template->fetch($template, $data, $config); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($template, $data = [], $config = []) + { + $this->template->display($template, $data, $config); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + // 分析模板文件规则 + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置或者获取模板引擎参数 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->template->config($name); + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return $this->template->config($name); + } else { + $this->template->$name = $value; + $this->config[$name] = $value; + } + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } +} diff --git a/source/thinkphp/library/traits/controller/Jump.php b/source/thinkphp/library/traits/controller/Jump.php new file mode 100644 index 0000000..6a57224 --- /dev/null +++ b/source/thinkphp/library/traits/controller/Jump.php @@ -0,0 +1,167 @@ +error(); + * $this->redirect(); + * } + * } + */ +namespace traits\controller; + +use think\Config; +use think\exception\HttpResponseException; +use think\Request; +use think\Response; +use think\response\Redirect; +use think\Url; +use think\View as ViewTemplate; + +trait Jump +{ + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url) && !is_null(Request::instance()->server('HTTP_REFERER'))) { + $url = Request::instance()->server('HTTP_REFERER'); + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_success_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url)) { + $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_error_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 返回封装后的 API 数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param int $code 返回的 code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'time' => Request::instance()->server('REQUEST_TIME'), + 'data' => $data, + ]; + $type = $type ?: $this->getResponseType(); + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * URL 重定向 + * @access protected + * @param string $url 跳转的 URL 表达式 + * @param array|int $params 其它 URL 参数 + * @param int $code http code + * @param array $with 隐式传参 + * @return void + * @throws HttpResponseException + */ + protected function redirect($url, $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + $response = new Redirect($url); + $response->code($code)->params($params)->with($with); + + throw new HttpResponseException($response); + } + + /** + * 获取当前的 response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + return Request::instance()->isAjax() + ? Config::get('default_ajax_return') + : Config::get('default_return_type'); + } +} diff --git a/source/thinkphp/library/traits/model/SoftDelete.php b/source/thinkphp/library/traits/model/SoftDelete.php new file mode 100644 index 0000000..70f31ba --- /dev/null +++ b/source/thinkphp/library/traits/model/SoftDelete.php @@ -0,0 +1,200 @@ +getDeleteTimeField(); + + if ($field && !empty($this->data[$field])) { + return true; + } + return false; + } + + /** + * 查询包含软删除的数据 + * @access public + * @return Query + */ + public static function withTrashed() + { + return (new static )->getQuery(); + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed() + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model->getQuery()->useSoftDelete($field, ['not null', '']); + } else { + return $model->getQuery(); + } + } + + /** + * 删除当前的记录 + * @access public + * @param bool $force 是否强制删除 + * @return integer + */ + public function delete($force = false) + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $name = $this->getDeleteTimeField(); + if ($name && !$force) { + // 软删除 + $this->data[$name] = $this->autoWriteTimestamp($name); + $result = $this->isUpdate()->save(); + } else { + // 强制删除当前模型数据 + $result = $this->getQuery()->where($this->getWhere())->delete(); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name); + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection || is_array($result)) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } + + $this->trigger('after_delete', $this); + + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表(支持闭包查询条件) + * @param bool $force 是否强制删除 + * @return integer 成功删除的记录数 + */ + public static function destroy($data, $force = false) + { + if (is_null($data)) { + return 0; + } + + // 包含软删除数据 + $query = (new static())->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; + } + + $count = 0; + if ($resultSet = $query->select($data)) { + foreach ($resultSet as $data) { + $result = $data->delete($force); + $count += $result; + } + } + + return $count; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return integer + */ + public function restore($where = []) + { + if (empty($where)) { + $pk = $this->getPk(); + $where[$pk] = $this->getData($pk); + } + + $name = $this->getDeleteTimeField(); + + if ($name) { + // 恢复删除 + return $this->getQuery() + ->useSoftDelete($name, ['not null', '']) + ->where($where) + ->update([$name => null]); + } else { + return 0; + } + } + + /** + * 查询默认不包含软删除数据 + * @access protected + * @param Query $query 查询对象 + * @return Query + */ + protected function base($query) + { + $field = $this->getDeleteTimeField(true); + return $field ? $query->useSoftDelete($field) : $query; + } + + /** + * 获取软删除字段 + * @access public + * @param bool $read 是否查询操作(写操作的时候会自动去掉表别名) + * @return string + */ + protected function getDeleteTimeField($read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? + $this->deleteTime : + 'delete_time'; + + if (false === $field) { + return false; + } + + if (!strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } +} diff --git a/source/thinkphp/library/traits/think/Instance.php b/source/thinkphp/library/traits/think/Instance.php new file mode 100644 index 0000000..428c8fd --- /dev/null +++ b/source/thinkphp/library/traits/think/Instance.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- + +namespace traits\think; + +use think\Exception; + +trait Instance +{ + /** + * @var null|static 实例对象 + */ + protected static $instance = null; + + /** + * 获取示例 + * @param array $options 实例配置 + * @return static + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) self::$instance = new self($options); + + return self::$instance; + } + + /** + * 静态调用 + * @param string $method 调用方法 + * @param array $params 调用参数 + * @return mixed + * @throws Exception + */ + public static function __callStatic($method, array $params) + { + if (is_null(self::$instance)) self::$instance = new self(); + + $call = substr($method, 1); + + if (0 !== strpos($method, '_') || !is_callable([self::$instance, $call])) { + throw new Exception("method not exists:" . $method); + } + + return call_user_func_array([self::$instance, $call], $params); + } +} diff --git a/source/thinkphp/logo.png b/source/thinkphp/logo.png new file mode 100644 index 0000000..25fd059 Binary files /dev/null and b/source/thinkphp/logo.png differ diff --git a/source/thinkphp/phpunit.xml b/source/thinkphp/phpunit.xml new file mode 100644 index 0000000..7c6ef03 --- /dev/null +++ b/source/thinkphp/phpunit.xml @@ -0,0 +1,35 @@ + + + + + ./tests/thinkphp/ + + + + + + + + ./ + + tests + vendor + + + + + + + + + + diff --git a/source/thinkphp/start.php b/source/thinkphp/start.php new file mode 100644 index 0000000..adb1bc6 --- /dev/null +++ b/source/thinkphp/start.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 1. 加载基础文件 +require __DIR__ . '/base.php'; + +// 2. 执行应用 +App::run()->send(); diff --git a/source/thinkphp/tpl/default_index.tpl b/source/thinkphp/tpl/default_index.tpl new file mode 100644 index 0000000..8538b4d --- /dev/null +++ b/source/thinkphp/tpl/default_index.tpl @@ -0,0 +1,10 @@ +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

    :)

    ThinkPHP V5
    十年磨一剑 - 为API开发设计的高性能框架

    [ V5.0 版本由 七牛云 独家赞助发布 ]
    '; + } +} diff --git a/source/thinkphp/tpl/dispatch_jump.tpl b/source/thinkphp/tpl/dispatch_jump.tpl new file mode 100644 index 0000000..583376b --- /dev/null +++ b/source/thinkphp/tpl/dispatch_jump.tpl @@ -0,0 +1,49 @@ +{__NOLAYOUT__} + + + + + 跳转提示 + + + +
    + + +

    :)

    +

    + + +

    :(

    +

    + + +

    +

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

    +
    + + + diff --git a/source/thinkphp/tpl/page_trace.tpl b/source/thinkphp/tpl/page_trace.tpl new file mode 100644 index 0000000..7c5df6f --- /dev/null +++ b/source/thinkphp/tpl/page_trace.tpl @@ -0,0 +1,71 @@ +
    + + +
    +
    +
    + +
    + + diff --git a/source/thinkphp/tpl/think_exception.tpl b/source/thinkphp/tpl/think_exception.tpl new file mode 100644 index 0000000..21bbafc --- /dev/null +++ b/source/thinkphp/tpl/think_exception.tpl @@ -0,0 +1,537 @@ +'.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); + } + } +?> + + + + + <?php echo \think\Lang::get('System Error'); ?> + + + + + +
    + +
    + +
    +
    + +
    +
    +

    [

    +
    +

    +
    + +
    + +
    +
      $value) { ?>
    +
    + +
    +

    Call Stack

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

    + +
    + + + +
    +

    Exception Datas

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + +
    +

    Environment Variables

    + $value) { ?> +
    + +
    +
    +
    empty
    +
    + +

    +
    + $val) { ?> +
    +
    +
    + +
    +
    + +
    + +
    + +
    + + + + + + + + diff --git a/source/vendor/aliyuncs/oss-sdk-php/.coveralls.yml b/source/vendor/aliyuncs/oss-sdk-php/.coveralls.yml new file mode 100644 index 0000000..850cc59 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/.coveralls.yml @@ -0,0 +1,2 @@ +coverage_clover: coverage.xml +json_path: coverage.json diff --git a/source/vendor/aliyuncs/oss-sdk-php/.gitignore b/source/vendor/aliyuncs/oss-sdk-php/.gitignore new file mode 100644 index 0000000..7cdb514 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/.gitignore @@ -0,0 +1,8 @@ +vendor +composer.lock +doc +output +.idea +.buildpath +.project +.settings diff --git a/source/vendor/aliyuncs/oss-sdk-php/.travis.yml b/source/vendor/aliyuncs/oss-sdk-php/.travis.yml new file mode 100644 index 0000000..0b40ba2 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/.travis.yml @@ -0,0 +1,21 @@ +language: php +php: + - 7.1 + - 7.0 + - 5.6 + - 5.5 + - 5.4 +install: + - composer self-update + - composer install --no-interaction +script: + - php vendor/bin/phpunit +after_success: + - php vendor/bin/coveralls -v +env: + global: + - secure: SzmQ854lQmhV6ZkAG7lQNTY3CkazrXnDSb6VMwPU/sdaLGxPO159AW3fJS5d0sO/XN1P8x5WNkoA4i9soDlLBRibEEISNUM/2EMnpszsRymZ9o97PrS2IgORXTUL/OF+rpATzyNVB2p+2l9hBLiGf17exMSA5iOeY7W6E+VKPGi8TFykgbGUnLKU0h1hV3rzmtfGjOXcSpvYU/hxeZD/J/+6m5Gic9b/pNS+AbfTj7Y7Ru9tNsnyUP29V/vtEYtpQir3ZxQiSiUv9idybgGnJBOMYydJofb/mpFYHhYLSWqtMKGNLpeawmqs4z8S1Tvx5U5uzW5+h/mpzhvBaFlWGpm8t89BQxun5LVX5NiYCrV7TqaLitGp1cSpMjMDnrnSTNzk1exVz+rWZZcWS7yB9ULYA681GA8StXWk167qB7Y30iK1dFK3+2mDN2cEY+qLs8+bupDowQ4eOM+eqfhxX8F8+ouKcKomETsjiIwL+CUsIe6wjvnYFWb1YlRhbsI75bblHApflohnt6gVSJ78ZPqID+u2oUMjmIWXLTnRR2Y2tgEW8uqHeIoQ8BBntLdQDmv0BO4FpnGQIwrUUwQYeNzEM0DOr3hWZhyDR6Xvl+9H0l52xjANaSqpuTZfC3zmeFTG7kIjydvxNePRrony6XAawL9BvI7aKWuVF6YVjPM= + - secure: nEhsU8aUQqsAJeuger+boh51oTpeo4YNG7vUWbKxdwVDIrcLb+l7r7RvTlxU7mt8IZTWwicgri18mh+Wi04BwX4ulBA1SCs8jPbL51KEo5izoDGGtLSd2fuPHdslYSrwagrvq90EPnDT/7fHWn/TAoT+rueZzjNyCu5IGSgL3GnXaUThsJ82NMePL2YRdP4Q1qmtZPRFBOkOQ6F0heuV8fw8sLyTO3txaCQum9YneGxrWxOl/E8zB0qtlnPwLE8ogaHZMQh2/jThmTbI5UqwRTxV4f0qoD5eJYH+j0fslsSAjsg/HPnSuVcnccK3zSU+s2sV4dPCcISzECJvZEObwipfxOGhdqt5gMcxHhn8qVsbT97iIh106pG/BJCDgQd2EeVW8WfCi6cCuCKIMipvVkMypkmjQHWU1XaqPzILl7g5diW9Ctp2C4Akq5dYdrdu8IrnVK1ShtkQVaWU+S/Bht8VU5gYP7olPW/GdTz7sceU1NOIC4NPXqmWKbfavR98U5dkHMLMvzABYL1Q87h+KhPD1c14NUyw3YENUW7REiF/X5lERRm5H0kJ/1JqAa+AgeHQEGmPVuZV2s/na4b0S1479QRVmSM/6ZzXQpU+Y8jCRfETpUFA4S331369kirHgCqDlxyIntuEKrzivD02/O+5C3eJ0WHRz6QsN2/R4qg= + - secure: ZTvzNXEZP4efl+a/3VGMmdabfUQp83v5/lecMns039Ro7UuZYPdtbPtpPnpjaTI6Htd22A4Rva5BU/3JCQJAyQvpbKNn5sGou2SmfQu3o0SyhggSB7gWjYAf707aW1j4bHYfP8IjDS5NjuVk3AqXeNSUuLRUXRmwSOB0lSYiRhiTJY+pUdBl382Hx4NbhIU/gmOzRoJCs7coTip8IURXYEHPi5dnDWluajxI+TgNXFccSgEleeQDJajYgXmpLb2EhSj8piipOnVgaCEE5bh5fbp32024Qq38SGHKcbfnwj2IInpZpZESJknRKoqAlFjdOJhork82dBcvAr5JxCBZKx5IuwXcTjxkQ6tRtBeqhPLPFuX3MQ8WrtU+wniPM0RCH/VoFkUKO7JGQDwmoi2AKago4PsuDs4P6Y6CeuOVpcso731GwwMNhIJcyrJJivXprQNEGsEw+9wLjU1qNYs6IIA3S/gPzFrNbdX5Wf8vxt0vLpgYvBNtPnLMejMtknuyfVzf5iKuVVoGPDTEz+ajs06+jfoPfm/4sLTaLghuVH7Adm74OpF769JQNnQYKwJuu4bNlcbLJChulCEMBOx7myqo/9O6RCTuqzHaGmVWNot4RGqRFHgJGl/JJf0WpAVitbhbRH3kGoyKb6jFM74CJbPsE7OORlJLDC3cdD3C8Pk= + - secure: Qr5NR4CVzBKCQgRgMH0x772TPJ1+brx3UCvtRNu8fi4j3p8bz+HDMjBaBDSFmEB09nunLI55/8mj88/5GXmnpFs8+CPTkcW+QZOcxg3cxpI4SNmxoB12/ZawlFHAqSUaRRE7RUWVkY3KL8tIGjEZcFyUBQ1DVNX3OMpiKs3NLtHa7oUIknyBxdSokm4kpLhSXYe7WmO0vhuZbMZE0S1EISToiBS6AdhGUEbTLJ/vNsIDY07fu6+Vh3HxVbyUFPqUZGlkZpQ+2xMJ3kiqPBMrXtRF/IhhPjORDil6Ns9SQ8/AAlaCddvYvRaT4Pjv2/aX+t3l28qI1qmryPtWXpce5UXecWGYqdRpSJc6Y/pEt4m4FeeGoEFWnSPGIs7FRmeiis8q2rojGZ18i4vI/k4iHmqEBnTlMp3SWnRb9L1adJ8ZAWln8aC88gkQXm67w7+1CxLycerbYj9H1ugqHENuHcxv4uHUcZgEENX3EWatu8i9+K2IUuU/2zcmpu7qtsziYcoyW8DOOmYpJfXGMLtmF9+pqp/Tp6i0tltFSEfmY3N8o7xvv3enLvFHsjL+3ElFdd1blUPSrvZJHgA9M3lJ+QF1RJZCpJqgPlQ0XOZK1Bf4P46zpEj01wKaK4JQrkLPRXhbBOuIJn5O6WlFJyPX4+SaBfwTzb4AvM4aUg2TgTg= + - secure: Inw5ftA8fxvhMHRZwoZzATxn4WICJsCq7ZX4y2gI+b/8u0JQIsbLgY9WTYV+XdSxOoNwuVa1oUxEWI0aDORtXKC3XaIXXKrwndag0zxS77JEYwWvQsjM7BhEbF7MF7MYk8rRXpn6mbfGAT/NfqEOx91RCY8UKeMzD0oPkpkBnJ9Ekuod6JBBq+7j3v4mYUItA8pxvw7b4Pdd4z2xzjgOwNhJYMOCpts50DWZI+WXj0HvTYaMXe5mJJtORK5lsr0a9cbsBqAzE6l+3zGI8XkgHn130ux5XH3DE7hZBeti3ZNaO3d2Vv+496/1EObG0rSFk+z3dmNKqjMz4nh3bYIkdLMegwmgCWs2mvQhkwYhzmnPRHVSERrgZjSWnuKn2PKnBar6tui9KaLNgpo2j3jWpwMLJ3bGAfE5JtMppxAxNqj/q+YB2UZo7Mn7EDjkTDjgxCuazTJwWqH7xxCOykWPABBI17P3JaOXQJEK39LavpfSMm3kdmU0ocpUs7FniLuFm6xL71VxY1wHG10yskczEcFHZ3iyIyGM+xum4vbt5y6Yg+zfdExYQsbrxHDDZ3HbHY3tEU0WhM55vrC42NIXRWqXqJ8OAxpl4nivfx96eoBAThiUU9xXtZmh7WRFVYsstoGtxZwfk5+bi+oeVO9kih4xabwbgHgL9BTc1TR1C4U= diff --git a/source/vendor/aliyuncs/oss-sdk-php/CHANGELOG.md b/source/vendor/aliyuncs/oss-sdk-php/CHANGELOG.md new file mode 100644 index 0000000..042a72a --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/CHANGELOG.md @@ -0,0 +1,92 @@ +# ChangeLog - Aliyun OSS SDK for PHP + +## v2.3.0 / 2018-01-05 + +* 修复:putObject支持创建空文件 +* 修复:createBucket支持IA/Archive +* 增加:支持restoreObject +* 增加:支持Symlink功能 +* 增加:支持getBucketLocation +* 增加:支持getBucketMeta +* 增加:支持代理服务器Proxy + +## v2.2.4 / 2017-04-25 + +* fix getObject to local file bug + +## v2.2.3 / 2017-04-14 + +* fix md5 check + +## v2.2.2 / 2017-01-18 + +* 解决在php7上运行连接数和内存bug + +## v2.2.1 / 2016-12-01 + +* 禁止http curl自动填充Accept-Encoding + +## v2.2.0 / 2016-11-22 + +* 修复PutObject/CompleteMultipartUpload的返回值问题(#26) + +## v2.1.0 / 2016-11-12 + +* 增加[RTMP](https://help.aliyun.com/document_detail/44297.html)接口 +* 增加支持[图片服务](https://help.aliyun.com/document_detail/44686.html) + +## v2.0.7 / 2016-06-17 + +* Support append object + +## v2.0.6 + +* Trim access key id/secret and endpoint +* Refine tests and setup travis CI + +## v2.0.5 + +* 增加Add/Delete/Get BucketCname接口 + +## v2.0.4 + +* 增加Put/Get Object Acl接口 + +## v2.0.3 + +* 修复Util中的常量定义在低于5.6的PHP版本中报错的问题 + +## v2.0.2 + +* 修复multipart上传时无法指定Content-Type的问题 + +## v2.0.1 + +* 增加对ListObjects/ListMultipartUploads时特殊字符的处理 +* 提供接口获取OssException中的详细信息 + + +## 2015.11.25 + +* **大版本升级,不再兼容以前接口,新版本对易用性做了很大的改进,建议用户迁移到新版本。** + +## 修改内容 + +* 不再支持PHP 5.2版本 + +### 新增内容 + +* 引入命名空间 +* 接口命名修正,采用驼峰式命名 +* 接口入参修改,把常用参数从Options参数中提出来 +* 接口返回结果修改,对返回结果进行处理,用户可以直接得到容易处理的数据结构  +* OssClient的构造函数变更 +* 支持CNAME和IP格式的Endpoint地址 +* 重新整理sample文件组织结构,使用function组织功能点 +* 增加设置连接超时,请求超时的接口 +* 去掉Object Group相关的已经过时的接口 +* OssException中的message改为英文 + +### 问题修复 + +* object名称校验不完备 diff --git a/source/vendor/aliyuncs/oss-sdk-php/LICENSE.md b/source/vendor/aliyuncs/oss-sdk-php/LICENSE.md new file mode 100644 index 0000000..3183de8 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/LICENSE.md @@ -0,0 +1,21 @@ +#The MIT License (MIT) + +Copyright (c) ali-sdk and other contributors. + +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/source/vendor/aliyuncs/oss-sdk-php/README-CN.md b/source/vendor/aliyuncs/oss-sdk-php/README-CN.md new file mode 100644 index 0000000..8c0cf84 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/README-CN.md @@ -0,0 +1,149 @@ +# Aliyun OSS SDK for PHP + +[![Latest Stable Version](https://poser.pugx.org/aliyuncs/oss-sdk-php/v/stable)](https://packagist.org/packages/aliyuncs/oss-sdk-php) +[![Build Status](https://travis-ci.org/aliyun/aliyun-oss-php-sdk.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-oss-php-sdk) +[![Coverage Status](https://coveralls.io/repos/github/aliyun/aliyun-oss-php-sdk/badge.svg?branch=master)](https://coveralls.io/github/aliyun/aliyun-oss-php-sdk?branch=master) + +## [README of English](https://github.com/aliyun/aliyun-oss-php-sdk/blob/master/README.md) + +## 概述 + +阿里云对象存储(Object Storage Service,简称OSS),是阿里云对外提供的海量、安全、低成本、高可靠的云存储服务。用户可以通过调用API,在任何应用、任何时间、任何地点上传和下载数据,也可以通过用户Web控制台对数据进行简单的管理。OSS适合存放任意文件类型,适合各种网站、开发企业及开发者使用。 + + +## 运行环境 +- PHP 5.3+ +- cURL extension + +提示: + +- Ubuntu下可以使用apt-get包管理器安装php的cURL扩展 `sudo apt-get install php5-curl` + +## 安装方法 + +1. 如果您通过composer管理您的项目依赖,可以在你的项目根目录运行: + + $ composer require aliyuncs/oss-sdk-php + + 或者在你的`composer.json`中声明对Aliyun OSS SDK for PHP的依赖: + + "require": { + "aliyuncs/oss-sdk-php": "~2.0" + } + + 然后通过`composer install`安装依赖。composer安装完成后,在您的PHP代码中引入依赖即可: + + require_once __DIR__ . '/vendor/autoload.php'; + +2. 您也可以直接下载已经打包好的[phar文件][releases-page],然后在你 + 的代码中引入这个文件即可: + + require_once '/path/to/oss-sdk-php.phar'; + +3. 下载SDK源码,在您的代码中引入SDK目录下的`autoload.php`文件: + + require_once '/path/to/oss-sdk/autoload.php'; + +## 快速使用 + +### 常用类 + +| 类名 | 解释 | +|:------------------|:------------------------------------| +|OSS\OssClient | OSS客户端类,用户通过OssClient的实例调用接口 | +|OSS\Core\OssException | OSS异常类,用户在使用的过程中,只需要注意这个异常| + +### OssClient初始化 + +SDK的OSS操作通过OssClient类完成的,下面代码创建一个OssClient对象: + +```php +"; ; +$accessKeySecret = "<您从OSS获得的AccessKeySecret>"; +$endpoint = "<您选定的OSS数据中心访问域名,例如oss-cn-hangzhou.aliyuncs.com>"; +try { + $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint); +} catch (OssException $e) { + print $e->getMessage(); +} +``` + +### 文件操作 + +文件(又称对象,Object)是OSS中最基本的数据单元,您可以把它简单地理解为文件,用下面代码可以实现一个Object的上传: + +```php +"; +$object = "<您使用的Object名字,注意命名规范>"; +$content = "Hello, OSS!"; // 上传的文件内容 +try { + $ossClient->putObject($bucket, $object, $content); +} catch (OssException $e) { + print $e->getMessage(); +} +``` + +### 存储空间操作 + +存储空间(又称Bucket)是一个用户用来管理所存储Object的存储空间,对于用户来说是一个管理Object的单元,所有的Object都必须隶属于某个Bucket。您可以按照下面的代码新建一个Bucket: + +```php +"; +try { + $ossClient->createBucket($bucket); +} catch (OssException $e) { + print $e->getMessage(); +} +``` + +### 返回结果处理 + +OssClient提供的接口返回返回数据分为两种: + +* Put,Delete类接口,接口返回null,如果没有OssException,即可认为操作成功 +* Get,List类接口,接口返回对应的数据,如果没有OssException,即可认为操作成功,举个例子: + +```php +listBuckets(); +$bucketList = $bucketListInfo->getBucketList(); +foreach($bucketList as $bucket) { + print($bucket->getLocation() . "\t" . $bucket->getName() . "\t" . $bucket->getCreatedate() . "\n"); +} +``` +上面代码中的$bucketListInfo的数据类型是 `OSS\Model\BucketListInfo` + + +### 运行Sample程序 + +1. 修改 `samples/Config.php`, 补充配置信息 +2. 执行 `cd samples/ && php RunAll.php` + +### 运行单元测试 + +1. 执行`composer install`下载依赖的库 +2. 设置环境变量 + + export OSS_ACCESS_KEY_ID=access-key-id + export OSS_ACCESS_KEY_SECRET=access-key-secret + export OSS_ENDPOINT=endpoint + export OSS_BUCKET=bucket-name + +3. 执行 `php vendor/bin/phpunit` + +## License + +- MIT + +## 联系我们 + +- [阿里云OSS官方网站](http://oss.aliyun.com) +- [阿里云OSS官方论坛](http://bbs.aliyun.com) +- [阿里云OSS官方文档中心](http://www.aliyun.com/product/oss#Docs) +- 阿里云官方技术支持:[提交工单](https://workorder.console.aliyun.com/#/ticket/createIndex) + +[releases-page]: https://github.com/aliyun/aliyun-oss-php-sdk/releases +[phar-composer]: https://github.com/clue/phar-composer diff --git a/source/vendor/aliyuncs/oss-sdk-php/README.md b/source/vendor/aliyuncs/oss-sdk-php/README.md new file mode 100644 index 0000000..3c1da26 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/README.md @@ -0,0 +1,150 @@ +# Alibaba Cloud OSS SDK for PHP + +[![Latest Stable Version](https://poser.pugx.org/aliyuncs/oss-sdk-php/v/stable)](https://packagist.org/packages/aliyuncs/oss-sdk-php) +[![Build Status](https://travis-ci.org/aliyun/aliyun-oss-php-sdk.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-oss-php-sdk) +[![Coverage Status](https://coveralls.io/repos/github/aliyun/aliyun-oss-php-sdk/badge.svg?branch=master)](https://coveralls.io/github/aliyun/aliyun-oss-php-sdk?branch=master) + +## [README of Chinese](https://github.com/aliyun/aliyun-oss-php-sdk/blob/master/README-CN.md) + +## Overview + +Alibaba Cloud Object Storage Service (OSS) is a cloud storage service provided by Alibaba Cloud, featuring a massive capacity, security, a low cost, and high reliability. You can upload and download data on any application anytime and anywhere by calling APIs, and perform simple management of data through the web console. The OSS can store any type of files and therefore applies to various websites, development enterprises and developers. + + +## Run environment +- PHP 5.3+. +- cURL extension. + +Tips: + +- In Ubuntu, you can use the ***apt-get*** package manager to install the *PHP cURL extension*: `sudo apt-get install php5-curl`. + +## Install OSS PHP SDK + +- If you use the ***composer*** to manage project dependencies, run the following command in your project's root directory: + + composer require aliyuncs/oss-sdk-php + + You can also declare the dependency on Alibaba Cloud OSS SDK for PHP in the `composer.json` file. + + "require": { + "aliyuncs/oss-sdk-php": "~2.0" + } + + Then run `composer install` to install the dependency. After the Composer Dependency Manager is installed, import the dependency in your PHP code: + + require_once __DIR__ . '/vendor/autoload.php'; + +- You can also directly download the packaged [PHAR File][releases-page], and + introduce the file to your code: + + require_once '/path/to/oss-sdk-php.phar'; + +- Download the SDK source code, and introduce the `autoload.php` file under the SDK directory to your code: + + require_once '/path/to/oss-sdk/autoload.php'; + +## Quick use + +### Common classes + +| Class | Explanation | +|:------------------|:------------------------------------| +|OSS\OSSClient | OSS client class. An OSSClient instance can be used to call the interface. | +|OSS\Core\OSSException |OSS Exception class . You only need to pay attention to this exception when you use the OSSClient. | + +### Initialize an OSSClient + +The SDK's operations for the OSS are performed through the OSSClient class. The code below creates an OSSClient object: + +```php +"; +$accessKeySecret = ""; +$endpoint = ""; +try { + $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint); +} catch (OssException $e) { + print $e->getMessage(); +} +``` + +### Operations on objects + +Objects are the most basic data units on the OSS. You can simply consider objects as files. The following code uploads an object: + +```php +"; +$object = ""; +$content = "Hello, OSS!"; // Content of the uploaded file +try { + $ossClient->putObject($bucket, $object, $content); +} catch (OssException $e) { + print $e->getMessage(); +} +``` + +### Operations on buckets + +Buckets are the space that you use to manage the stored objects. It is an object management unit for users. Each object must belong to a bucket. You can create a bucket with the following code: + +```php +"; +try { + $ossClient->createBucket($bucket); +} catch (OssException $e) { + print $e->getMessage(); +} +``` + +### Handle returned results + +The OSSClient provides the following two types of returned data from interfaces: + +- Put and Delete interfaces: The *PUT* and *DELETE* operations are deemed successful if *null* is returned by the interfaces without *OSSException*. +- Get and List interfaces: The *GET* and *LIST* operations are deemed successful if the desired data is returned by the interfaces without *OSSException*. For example, + + ```php + listBuckets(); + $bucketList = $bucketListInfo->getBucketList(); + foreach($bucketList as $bucket) { + print($bucket->getLocation() . "\t" . $bucket->getName() . "\t" . $bucket->getCreatedate() . "\n"); + } + ``` +In the above code, $bucketListInfo falls into the 'OSS\Model\BucketListInfo' data type. + + +### Run a sample project + +- Modify `samples/Config.php` to complete the configuration information. +- Run `cd samples/ && php RunAll.php`. + +### Run a unit test + +- Run `composer install` to download the dependent libraries. +- Set the environment variable. + + export OSS_ACCESS_KEY_ID=access-key-id + export OSS_ACCESS_KEY_SECRET=access-key-secret + export OSS_ENDPOINT=endpoint + export OSS_BUCKET=bucket-name + +- Run `php vendor/bin/phpunit` + +## License + +- MIT + +## Contact us + +- [Alibaba Cloud OSS official website](http://oss.aliyun.com). +- [Alibaba Cloud OSS official forum](http://bbs.aliyun.com). +- [Alibaba Cloud OSS official documentation center](http://www.aliyun.com/product/oss#Docs). +- Alibaba Cloud official technical support: [Submit a ticket](https://workorder.console.aliyun.com/#/ticket/createIndex). + +[releases-page]: https://github.com/aliyun/aliyun-oss-php-sdk/releases +[phar-composer]: https://github.com/clue/phar-composer + diff --git a/source/vendor/aliyuncs/oss-sdk-php/autoload.php b/source/vendor/aliyuncs/oss-sdk-php/autoload.php new file mode 100644 index 0000000..ec13201 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/autoload.php @@ -0,0 +1,11 @@ +=5.3" + }, + "require-dev" : { + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "~1.0" + }, + "minimum-stability": "stable", + "autoload": { + "psr-4": {"OSS\\": "src/OSS"} + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/example.jpg b/source/vendor/aliyuncs/oss-sdk-php/example.jpg new file mode 100644 index 0000000..ffd46a2 Binary files /dev/null and b/source/vendor/aliyuncs/oss-sdk-php/example.jpg differ diff --git a/source/vendor/aliyuncs/oss-sdk-php/index.php b/source/vendor/aliyuncs/oss-sdk-php/index.php new file mode 100644 index 0000000..cdc28bc --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/index.php @@ -0,0 +1,3 @@ + + + + + + + ./src + + + + + + + + ./tests + ./tests/OSS/Tests/BucketCnameTest.php + + + diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/Bucket.php b/source/vendor/aliyuncs/oss-sdk-php/samples/Bucket.php new file mode 100644 index 0000000..bd16e65 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/Bucket.php @@ -0,0 +1,167 @@ +createBucket($bucket, OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE); +Common::println("bucket $bucket created"); + +// 判断Bucket是否存在 +$doesExist = $ossClient->doesBucketExist($bucket); +Common::println("bucket $bucket exist? " . ($doesExist ? "yes" : "no")); + +// 获取Bucket列表 +$bucketListInfo = $ossClient->listBuckets(); + +// 设置bucket的ACL +$ossClient->putBucketAcl($bucket, OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE); +Common::println("bucket $bucket acl put"); +// 获取bucket的ACL +$acl = $ossClient->getBucketAcl($bucket); +Common::println("bucket $bucket acl get: " . $acl); + + +//******************************* 完整用法参考下面函数 **************************************************** + +createBucket($ossClient, $bucket); +doesBucketExist($ossClient, $bucket); +deleteBucket($ossClient, $bucket); +putBucketAcl($ossClient, $bucket); +getBucketAcl($ossClient, $bucket); +listBuckets($ossClient); + +/** + * 创建一个存储空间 + * acl 指的是bucket的访问控制权限,有三种,私有读写,公共读私有写,公共读写。 + * 私有读写就是只有bucket的拥有者或授权用户才有权限操作 + * 三种权限分别对应 (OssClient::OSS_ACL_TYPE_PRIVATE,OssClient::OSS_ACL_TYPE_PUBLIC_READ, OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE) + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 要创建的存储空间名称 + * @return null + */ +function createBucket($ossClient, $bucket) +{ + try { + $ossClient->createBucket($bucket, OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 判断Bucket是否存在 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + */ +function doesBucketExist($ossClient, $bucket) +{ + try { + $res = $ossClient->doesBucketExist($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + if ($res === true) { + print(__FUNCTION__ . ": OK" . "\n"); + } else { + print(__FUNCTION__ . ": FAILED" . "\n"); + } +} + +/** + * 删除bucket,如果bucket不为空则bucket无法删除成功, 不为空表示bucket既没有object,也没有未完成的multipart上传时的parts + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 待删除的存储空间名称 + * @return null + */ +function deleteBucket($ossClient, $bucket) +{ + try { + $ossClient->deleteBucket($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 设置bucket的acl配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function putBucketAcl($ossClient, $bucket) +{ + $acl = OssClient::OSS_ACL_TYPE_PRIVATE; + try { + $ossClient->putBucketAcl($bucket, $acl); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + + +/** + * 获取bucket的acl配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getBucketAcl($ossClient, $bucket) +{ + try { + $res = $ossClient->getBucketAcl($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + print('acl: ' . $res); +} + + +/** + * 列出用户所有的Bucket + * + * @param OssClient $ossClient OssClient实例 + * @return null + */ +function listBuckets($ossClient) +{ + $bucketList = null; + try { + $bucketListInfo = $ossClient->listBuckets(); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + $bucketList = $bucketListInfo->getBucketList(); + foreach ($bucketList as $bucket) { + print($bucket->getLocation() . "\t" . $bucket->getName() . "\t" . $bucket->getCreatedate() . "\n"); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/BucketCors.php b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketCors.php new file mode 100644 index 0000000..cc5c0b9 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketCors.php @@ -0,0 +1,108 @@ +addAllowedHeader("x-oss-header"); +$rule->addAllowedOrigin("http://www.b.com"); +$rule->addAllowedMethod("POST"); +$rule->setMaxAgeSeconds(10); +$corsConfig->addRule($rule); +$ossClient->putBucketCors($bucket, $corsConfig); +Common::println("bucket $bucket corsConfig created:" . $corsConfig->serializeToXml()); + +// 获取cors配置 +$corsConfig = $ossClient->getBucketCors($bucket); +Common::println("bucket $bucket corsConfig fetched:" . $corsConfig->serializeToXml()); + +// 删除cors配置 +$ossClient->deleteBucketCors($bucket); +Common::println("bucket $bucket corsConfig deleted"); + +//******************************* 完整用法参考下面函数 ***************************************************** + +putBucketCors($ossClient, $bucket); +getBucketCors($ossClient, $bucket); +deleteBucketCors($ossClient, $bucket); +getBucketCors($ossClient, $bucket); + +/** + * 设置bucket的cors配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function putBucketCors($ossClient, $bucket) +{ + $corsConfig = new CorsConfig(); + $rule = new CorsRule(); + $rule->addAllowedHeader("x-oss-header"); + $rule->addAllowedOrigin("http://www.b.com"); + $rule->addAllowedMethod("POST"); + $rule->setMaxAgeSeconds(10); + $corsConfig->addRule($rule); + + try { + $ossClient->putBucketCors($bucket, $corsConfig); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 获取并打印bucket的cors配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getBucketCors($ossClient, $bucket) +{ + $corsConfig = null; + try { + $corsConfig = $ossClient->getBucketCors($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + print($corsConfig->serializeToXml() . "\n"); +} + +/** + * 删除bucket的所有的cors配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function deleteBucketCors($ossClient, $bucket) +{ + try { + $ossClient->deleteBucketCors($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/BucketLifecycle.php b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketLifecycle.php new file mode 100644 index 0000000..ec0c37f --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketLifecycle.php @@ -0,0 +1,109 @@ +addRule($lifecycleRule); +$ossClient->putBucketLifecycle($bucket, $lifecycleConfig); +Common::println("bucket $bucket lifecycleConfig created:" . $lifecycleConfig->serializeToXml()); + +//获取lifecycle规则 +$lifecycleConfig = $ossClient->getBucketLifecycle($bucket); +Common::println("bucket $bucket lifecycleConfig fetched:" . $lifecycleConfig->serializeToXml()); + +//删除bucket的lifecycle配置 +$ossClient->deleteBucketLifecycle($bucket); +Common::println("bucket $bucket lifecycleConfig deleted"); + + +//***************************** 完整用法参考下面函数 *********************************************** + +putBucketLifecycle($ossClient, $bucket); +getBucketLifecycle($ossClient, $bucket); +deleteBucketLifecycle($ossClient, $bucket); +getBucketLifecycle($ossClient, $bucket); + +/** + * 设置bucket的生命周期配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function putBucketLifecycle($ossClient, $bucket) +{ + $lifecycleConfig = new LifecycleConfig(); + $actions = array(); + $actions[] = new LifecycleAction(OssClient::OSS_LIFECYCLE_EXPIRATION, OssClient::OSS_LIFECYCLE_TIMING_DAYS, 3); + $lifecycleRule = new LifecycleRule("delete obsoleted files", "obsoleted/", "Enabled", $actions); + $lifecycleConfig->addRule($lifecycleRule); + $actions = array(); + $actions[] = new LifecycleAction(OssClient::OSS_LIFECYCLE_EXPIRATION, OssClient::OSS_LIFECYCLE_TIMING_DATE, '2022-10-12T00:00:00.000Z'); + $lifecycleRule = new LifecycleRule("delete temporary files", "temporary/", "Enabled", $actions); + $lifecycleConfig->addRule($lifecycleRule); + try { + $ossClient->putBucketLifecycle($bucket, $lifecycleConfig); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 获取bucket的生命周期配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getBucketLifecycle($ossClient, $bucket) +{ + $lifecycleConfig = null; + try { + $lifecycleConfig = $ossClient->getBucketLifecycle($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + print($lifecycleConfig->serializeToXml() . "\n"); +} + +/** + * 删除bucket的生命周期配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function deleteBucketLifecycle($ossClient, $bucket) +{ + try { + $ossClient->deleteBucketLifecycle($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + + diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/BucketLogging.php b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketLogging.php new file mode 100644 index 0000000..406e1d4 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketLogging.php @@ -0,0 +1,95 @@ +putBucketLogging($bucket, $bucket, "access.log", array()); +Common::println("bucket $bucket lifecycleConfig created"); + +// 获取Bucket访问日志记录规则 +$loggingConfig = $ossClient->getBucketLogging($bucket, array()); +Common::println("bucket $bucket lifecycleConfig fetched:" . $loggingConfig->serializeToXml()); + +// 删除Bucket访问日志记录规则 +$loggingConfig = $ossClient->getBucketLogging($bucket, array()); +Common::println("bucket $bucket lifecycleConfig deleted"); + +//******************************* 完整用法参考下面函数 **************************************************** + +putBucketLogging($ossClient, $bucket); +getBucketLogging($ossClient, $bucket); +deleteBucketLogging($ossClient, $bucket); +getBucketLogging($ossClient, $bucket); + +/** + * 设置bucket的Logging配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function putBucketLogging($ossClient, $bucket) +{ + $option = array(); + //访问日志存放在本bucket下 + $targetBucket = $bucket; + $targetPrefix = "access.log"; + + try { + $ossClient->putBucketLogging($bucket, $targetBucket, $targetPrefix, $option); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 获取bucket的Logging配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getBucketLogging($ossClient, $bucket) +{ + $loggingConfig = null; + $options = array(); + try { + $loggingConfig = $ossClient->getBucketLogging($bucket, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + print($loggingConfig->serializeToXml() . "\n"); +} + +/** + * 删除bucket的Logging配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function deleteBucketLogging($ossClient, $bucket) +{ + try { + $ossClient->deleteBucketLogging($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/BucketReferer.php b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketReferer.php new file mode 100644 index 0000000..3828df6 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketReferer.php @@ -0,0 +1,101 @@ +setAllowEmptyReferer(true); +$refererConfig->addReferer("www.aliiyun.com"); +$refererConfig->addReferer("www.aliiyuncs.com"); +$ossClient->putBucketReferer($bucket, $refererConfig); +Common::println("bucket $bucket refererConfig created:" . $refererConfig->serializeToXml()); +//获取Referer白名单 +$refererConfig = $ossClient->getBucketReferer($bucket); +Common::println("bucket $bucket refererConfig fetched:" . $refererConfig->serializeToXml()); + +//删除referer白名单 +$refererConfig = new RefererConfig(); +$ossClient->putBucketReferer($bucket, $refererConfig); +Common::println("bucket $bucket refererConfig deleted"); + + +//******************************* 完整用法参考下面函数 **************************************************** + +putBucketReferer($ossClient, $bucket); +getBucketReferer($ossClient, $bucket); +deleteBucketReferer($ossClient, $bucket); +getBucketReferer($ossClient, $bucket); + +/** + * 设置bucket的防盗链配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function putBucketReferer($ossClient, $bucket) +{ + $refererConfig = new RefererConfig(); + $refererConfig->setAllowEmptyReferer(true); + $refererConfig->addReferer("www.aliiyun.com"); + $refererConfig->addReferer("www.aliiyuncs.com"); + try { + $ossClient->putBucketReferer($bucket, $refererConfig); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 获取bucket的防盗链配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getBucketReferer($ossClient, $bucket) +{ + $refererConfig = null; + try { + $refererConfig = $ossClient->getBucketReferer($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + print($refererConfig->serializeToXml() . "\n"); +} + +/** + * 删除bucket的防盗链配置 + * Referer白名单不能直接清空,只能通过重新设置来覆盖之前的规则。 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function deleteBucketReferer($ossClient, $bucket) +{ + $refererConfig = new RefererConfig(); + try { + $ossClient->putBucketReferer($bucket, $refererConfig); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/BucketWebsite.php b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketWebsite.php new file mode 100644 index 0000000..54706f8 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/BucketWebsite.php @@ -0,0 +1,92 @@ +putBucketWebsite($bucket, $websiteConfig); +Common::println("bucket $bucket websiteConfig created:" . $websiteConfig->serializeToXml()); + +// 查看Bucket的静态网站托管状态 +$websiteConfig = $ossClient->getBucketWebsite($bucket); +Common::println("bucket $bucket websiteConfig fetched:" . $websiteConfig->serializeToXml()); + +// 删除Bucket的静态网站托管模式 +$ossClient->deleteBucketWebsite($bucket); +Common::println("bucket $bucket websiteConfig deleted"); + +//******************************* 完整用法参考下面函数 **************************************************** + +putBucketWebsite($ossClient, $bucket); +getBucketWebsite($ossClient, $bucket); +deleteBucketWebsite($ossClient, $bucket); +getBucketWebsite($ossClient, $bucket); + +/** + * 设置bucket的静态网站托管模式配置 + * + * @param $ossClient OssClient + * @param $bucket string 存储空间名称 + * @return null + */ +function putBucketWebsite($ossClient, $bucket) +{ + $websiteConfig = new WebsiteConfig("index.html", "error.html"); + try { + $ossClient->putBucketWebsite($bucket, $websiteConfig); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 获取bucket的静态网站托管状态 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getBucketWebsite($ossClient, $bucket) +{ + $websiteConfig = null; + try { + $websiteConfig = $ossClient->getBucketWebsite($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + print($websiteConfig->serializeToXml() . "\n"); +} + +/** + * 删除bucket的静态网站托管模式配置 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function deleteBucketWebsite($ossClient, $bucket) +{ + try { + $ossClient->deleteBucketWebsite($bucket); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/Callback.php b/source/vendor/aliyuncs/oss-sdk-php/samples/Callback.php new file mode 100644 index 0000000..8612a1c --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/Callback.php @@ -0,0 +1,83 @@ + $url, + OssClient::OSS_CALLBACK_VAR => $var + ); +$result = $ossClient->putObject($bucket, "b.file", "random content", $options); +Common::println($result['body']); +Common::println($result['info']['http_code']); + +/** + * completeMultipartUpload 使用callback上传内容到oss文件 + * callbackurl参数指定请求回调的服务器url + * callbackbodytype参数可为application/json或application/x-www-form-urlencoded, 可选参数,默认为application/x-www-form-urlencoded + * OSS_CALLBACK_VAR参数可以不设置 + */ +$object = "multipart-callback-test.txt"; +$copiedObject = "multipart-callback-test.txt.copied"; +$ossClient->putObject($bucket, $copiedObject, file_get_contents(__FILE__)); + +/** + * step 1. 初始化一个分块上传事件, 也就是初始化上传Multipart, 获取upload id + */ +$upload_id = $ossClient->initiateMultipartUpload($bucket, $object); + +/** + * step 2. uploadPartCopy + */ +$copyId = 1; +$eTag = $ossClient->uploadPartCopy($bucket, $copiedObject, $bucket, $object, $copyId, $upload_id); +$upload_parts[] = array( + 'PartNumber' => $copyId, + 'ETag' => $eTag, + ); +$listPartsInfo = $ossClient->listParts($bucket, $object, $upload_id); + +/** + * step 3. + */ +$json = + '{ + "callbackUrl":"callback.oss-demo.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"{\"mimeType\":${mimeType},\"size\":${size},\"x:var1\":${x:var1},\"x:var2\":${x:var2}}", + "callbackBodyType":"application/json" + }'; +$var = + '{ + "x:var1":"value1", + "x:var2":"值2" + }'; +$options = array(OssClient::OSS_CALLBACK => $json, + OssClient::OSS_CALLBACK_VAR => $var); + +$result = $ossClient->completeMultipartUpload($bucket, $object, $upload_id, $upload_parts, $options); +Common::println($result['body']); +Common::println($result['info']['http_code']); diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/Common.php b/source/vendor/aliyuncs/oss-sdk-php/samples/Common.php new file mode 100644 index 0000000..f419d17 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/Common.php @@ -0,0 +1,84 @@ +getMessage() . "\n"); + return null; + } + return $ossClient; + } + + public static function getBucketName() + { + return self::bucket; + } + + /** + * 工具方法,创建一个存储空间,如果发生异常直接exit + */ + public static function createBucket() + { + $ossClient = self::getOssClient(); + if (is_null($ossClient)) exit(1); + $bucket = self::getBucketName(); + $acl = OssClient::OSS_ACL_TYPE_PUBLIC_READ; + try { + $ossClient->createBucket($bucket, $acl); + } catch (OssException $e) { + + $message = $e->getMessage(); + if (\OSS\Core\OssUtil::startsWith($message, 'http status: 403')) { + echo "Please Check your AccessKeyId and AccessKeySecret" . "\n"; + exit(0); + } elseif (strpos($message, "BucketAlreadyExists") !== false) { + echo "Bucket already exists. Please check whether the bucket belongs to you, or it was visited with correct endpoint. " . "\n"; + exit(0); + } + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + } + + public static function println($message) + { + if (!empty($message)) { + echo strval($message) . "\n"; + } + } +} + +Common::createBucket(); diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/Config.php b/source/vendor/aliyuncs/oss-sdk-php/samples/Config.php new file mode 100644 index 0000000..35c0dc7 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/Config.php @@ -0,0 +1,15 @@ +uploadFile($bucketName, $object, "example.jpg"); + +// 图片缩放 +$options = array( + OssClient::OSS_FILE_DOWNLOAD => $download_file, + OssClient::OSS_PROCESS => "image/resize,m_fixed,h_100,w_100", ); +$ossClient->getObject($bucketName, $object, $options); +printImage("imageResize",$download_file); + +// 图片裁剪 +$options = array( + OssClient::OSS_FILE_DOWNLOAD => $download_file, + OssClient::OSS_PROCESS => "image/crop,w_100,h_100,x_100,y_100,r_1", ); +$ossClient->getObject($bucketName, $object, $options); +printImage("iamgeCrop", $download_file); + +// 图片旋转 +$options = array( + OssClient::OSS_FILE_DOWNLOAD => $download_file, + OssClient::OSS_PROCESS => "image/rotate,90", ); +$ossClient->getObject($bucketName, $object, $options); +printImage("imageRotate", $download_file); + +// 图片锐化 +$options = array( + OssClient::OSS_FILE_DOWNLOAD => $download_file, + OssClient::OSS_PROCESS => "image/sharpen,100", ); +$ossClient->getObject($bucketName, $object, $options); +printImage("imageSharpen", $download_file); + +// 图片水印 +$options = array( + OssClient::OSS_FILE_DOWNLOAD => $download_file, + OssClient::OSS_PROCESS => "image/watermark,text_SGVsbG8g5Zu-54mH5pyN5YqhIQ", ); +$ossClient->getObject($bucketName, $object, $options); +printImage("imageWatermark", $download_file); + +// 图片格式转换 +$options = array( + OssClient::OSS_FILE_DOWNLOAD => $download_file, + OssClient::OSS_PROCESS => "image/format,png", ); +$ossClient->getObject($bucketName, $object, $options); +printImage("imageFormat", $download_file); + +// 获取图片信息 +$options = array( + OssClient::OSS_FILE_DOWNLOAD => $download_file, + OssClient::OSS_PROCESS => "image/info", ); +$ossClient->getObject($bucketName, $object, $options); +printImage("imageInfo", $download_file); + + +/** + * 生成一个带签名的可用于浏览器直接打开的url, URL的有效期是3600秒 + */ + $timeout = 3600; +$options = array( + OssClient::OSS_PROCESS => "image/resize,m_lfit,h_100,w_100", + ); +$signedUrl = $ossClient->signUrl($bucketName, $object, $timeout, "GET", $options); +Common::println("rtmp url: \n" . $signedUrl); + +//最后删除上传的$object +$ossClient->deleteObject($bucketName, $object); + +function printImage($func, $imageFile) +{ + $array = getimagesize($imageFile); + Common::println("$func, image width: " . $array[0]); + Common::println("$func, image height: " . $array[1]); + Common::println("$func, image type: " . ($array[2] === 2 ? 'jpg' : 'png')); + Common::println("$func, image size: " . ceil(filesize($imageFile))); +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/LiveChannel.php b/source/vendor/aliyuncs/oss-sdk-php/samples/LiveChannel.php new file mode 100644 index 0000000..2f7d3a8 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/LiveChannel.php @@ -0,0 +1,125 @@ + 'live channel test', + 'type' => 'HLS', + 'fragDuration' => 10, + 'fragCount' => 5, + 'playListName' => 'hello.m3u8' + )); +$info = $ossClient->putBucketLiveChannel($bucket, 'test_rtmp_live', $config); +Common::println("bucket $bucket liveChannel created:\n" . +"live channel name: ". $info->getName() . "\n" . +"live channel description: ". $info->getDescription() . "\n" . +"publishurls: ". $info->getPublishUrls()[0] . "\n" . +"playurls: ". $info->getPlayUrls()[0] . "\n"); + +/** + 对创建好的频道,可以使用listBucketLiveChannels来进行列举已达到管理的目的。 + prefix可以按照前缀过滤list出来的频道。 + max_keys表示迭代器内部一次list出来的频道的最大数量,这个值最大不能超过1000,不填写的话默认为100。 + */ +$list = $ossClient->listBucketLiveChannels($bucket); +Common::println("bucket $bucket listLiveChannel:\n" . +"list live channel prefix: ". $list->getPrefix() . "\n" . +"list live channel marker: ". $list->getMarker() . "\n" . +"list live channel maxkey: ". $list->getMaxKeys() . "\n" . +"list live channel IsTruncated: ". $list->getIsTruncated() . "\n" . +"list live channel getNextMarker: ". $list->getNextMarker() . "\n"); + +foreach($list->getChannelList() as $list) +{ + Common::println("bucket $bucket listLiveChannel:\n" . + "list live channel IsTruncated: ". $list->getName() . "\n" . + "list live channel Description: ". $list->getDescription() . "\n" . + "list live channel Status: ". $list->getStatus() . "\n" . + "list live channel getNextMarker: ". $list->getLastModified() . "\n"); +} +/** + 创建直播频道之后拿到推流用的play_url(rtmp推流的url,如果Bucket不是公共读写权限那么还需要带上签名,见下文示例)和推流用的publish_url(推流产生的m3u8文件的url) + */ +$play_url = $ossClient->signRtmpUrl($bucket, "test_rtmp_live", 3600, array('params' => array('playlistName' => 'playlist.m3u8'))); +Common::println("bucket $bucket rtmp url: \n" . $play_url); +$play_url = $ossClient->signRtmpUrl($bucket, "test_rtmp_live", 3600); +Common::println("bucket $bucket rtmp url: \n" . $play_url); + +/** + 创建好直播频道,如果想把这个频道禁用掉(断掉正在推的流或者不再允许向一个地址推流),应该使用putLiveChannelStatus接口,将频道的status改成“Disabled”,如果要将一个禁用状态的频道启用,那么也是调用这个接口,将status改成“Enabled” + */ +$resp = $ossClient->putLiveChannelStatus($bucket, "test_rtmp_live", "enabled"); + +/** + 创建好直播频道之后调用getLiveChannelInfo可以得到频道相关的信息 + */ +$info = $ossClient->getLiveChannelInfo($bucket, 'test_rtmp_live'); +Common::println("bucket $bucket LiveChannelInfo:\n" . +"live channel info description: ". $info->getDescription() . "\n" . +"live channel info status: ". $info->getStatus() . "\n" . +"live channel info type: ". $info->getType() . "\n" . +"live channel info fragDuration: ". $info->getFragDuration() . "\n" . +"live channel info fragCount: ". $info->getFragCount() . "\n" . +"live channel info playListName: ". $info->getPlayListName() . "\n"); + +/** + 如果想查看一个频道历史推流记录,可以调用getLiveChannelHistory。目前最多可以看到10次推流的记录 + */ +$history = $ossClient->getLiveChannelHistory($bucket, "test_rtmp_live"); +if (count($history->getLiveRecordList()) != 0) +{ + foreach($history->getLiveRecordList() as $recordList) + { + Common::println("bucket $bucket liveChannelHistory:\n" . + "live channel history startTime: ". $recordList->getStartTime() . "\n" . + "live channel history endTime: ". $recordList->getEndTime() . "\n" . + "live channel history remoteAddr: ". $recordList->getRemoteAddr() . "\n"); + } +} + +/** + 对于正在推流的频道调用get_live_channel_stat可以获得流的状态信息。 + 如果频道正在推流,那么stat_result中的所有字段都有意义。 + 如果频道闲置或者处于“Disabled”状态,那么status为“Idle”或“Disabled”,其他字段无意义。 + */ +$status = $ossClient->getLiveChannelStatus($bucket, "test_rtmp_live"); +Common::println("bucket $bucket listLiveChannel:\n" . +"live channel status status: ". $status->getStatus() . "\n" . +"live channel status ConnectedTime: ". $status->getConnectedTime() . "\n" . +"live channel status VideoWidth: ". $status->getVideoWidth() . "\n" . +"live channel status VideoHeight: ". $status->getVideoHeight() . "\n" . +"live channel status VideoFrameRate: ". $status->getVideoFrameRate() . "\n" . +"live channel status VideoBandwidth: ". $status->getVideoBandwidth() . "\n" . +"live channel status VideoCodec: ". $status->getVideoCodec() . "\n" . +"live channel status AudioBandwidth: ". $status->getAudioBandwidth() . "\n" . +"live channel status AudioSampleRate: ". $status->getAudioSampleRate() . "\n" . +"live channel status AdioCodec: ". $status->getAudioCodec() . "\n"); + +/** + * 如果希望利用直播推流产生的ts文件生成一个点播列表,可以使用postVodPlaylist方法。 + * 指定起始时间为当前时间减去60秒,结束时间为当前时间,这意味着将生成一个长度为60秒的点播视频。 + * 播放列表指定为“vod_playlist.m3u8”,也就是说这个接口调用成功之后会在OSS上生成一个名叫“vod_playlist.m3u8”的播放列表文件。 + */ +$current_time = time(); +$ossClient->postVodPlaylist($bucket, + "test_rtmp_live", "vod_playlist.m3u8", + array('StartTime' => $current_time - 60, + 'EndTime' => $current_time) +); + +/** + * 如果一个直播频道已经不打算再使用了,那么可以调用delete_live_channel来删除频道。 + */ +$ossClient->deleteBucketLiveChannel($bucket, "test_rtmp_live"); diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/MultipartUpload.php b/source/vendor/aliyuncs/oss-sdk-php/samples/MultipartUpload.php new file mode 100644 index 0000000..e8d69a3 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/MultipartUpload.php @@ -0,0 +1,182 @@ +multiuploadFile($bucket, "file.php", __FILE__, array()); +Common::println("local file " . __FILE__ . " is uploaded to the bucket $bucket, file.php"); + + +// 上传本地目录到bucket内的targetdir子目录中 +$ossClient->uploadDir($bucket, "targetdir", __DIR__); +Common::println("local dir " . __DIR__ . " is uploaded to the bucket $bucket, targetdir/"); + + +// 列出当前未完成的分片上传 +$listMultipartUploadInfo = $ossClient->listMultipartUploads($bucket, array()); + + +//******************************* 完整用法参考下面函数 **************************************************** + +multiuploadFile($ossClient, $bucket); +putObjectByRawApis($ossClient, $bucket); +uploadDir($ossClient, $bucket); +listMultipartUploads($ossClient, $bucket); + +/** + * 通过multipart上传文件 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function multiuploadFile($ossClient, $bucket) +{ + $object = "test/multipart-test.txt"; + $file = __FILE__; + $options = array(); + + try { + $ossClient->multiuploadFile($bucket, $object, $file, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 使用基本的api分阶段进行分片上传 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @throws OssException + */ +function putObjectByRawApis($ossClient, $bucket) +{ + $object = "test/multipart-test.txt"; + /** + * step 1. 初始化一个分块上传事件, 也就是初始化上传Multipart, 获取upload id + */ + try { + $uploadId = $ossClient->initiateMultipartUpload($bucket, $object); + } catch (OssException $e) { + printf(__FUNCTION__ . ": initiateMultipartUpload FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": initiateMultipartUpload OK" . "\n"); + /* + * step 2. 上传分片 + */ + $partSize = 10 * 1024 * 1024; + $uploadFile = __FILE__; + $uploadFileSize = filesize($uploadFile); + $pieces = $ossClient->generateMultiuploadParts($uploadFileSize, $partSize); + $responseUploadPart = array(); + $uploadPosition = 0; + $isCheckMd5 = true; + foreach ($pieces as $i => $piece) { + $fromPos = $uploadPosition + (integer)$piece[$ossClient::OSS_SEEK_TO]; + $toPos = (integer)$piece[$ossClient::OSS_LENGTH] + $fromPos - 1; + $upOptions = array( + $ossClient::OSS_FILE_UPLOAD => $uploadFile, + $ossClient::OSS_PART_NUM => ($i + 1), + $ossClient::OSS_SEEK_TO => $fromPos, + $ossClient::OSS_LENGTH => $toPos - $fromPos + 1, + $ossClient::OSS_CHECK_MD5 => $isCheckMd5, + ); + if ($isCheckMd5) { + $contentMd5 = OssUtil::getMd5SumForFile($uploadFile, $fromPos, $toPos); + $upOptions[$ossClient::OSS_CONTENT_MD5] = $contentMd5; + } + //2. 将每一分片上传到OSS + try { + $responseUploadPart[] = $ossClient->uploadPart($bucket, $object, $uploadId, $upOptions); + } catch (OssException $e) { + printf(__FUNCTION__ . ": initiateMultipartUpload, uploadPart - part#{$i} FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + printf(__FUNCTION__ . ": initiateMultipartUpload, uploadPart - part#{$i} OK\n"); + } + $uploadParts = array(); + foreach ($responseUploadPart as $i => $eTag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + /** + * step 3. 完成上传 + */ + try { + $ossClient->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts); + } catch (OssException $e) { + printf(__FUNCTION__ . ": completeMultipartUpload FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + printf(__FUNCTION__ . ": completeMultipartUpload OK\n"); +} + +/** + * 按照目录上传文件 + * + * @param OssClient $ossClient OssClient + * @param string $bucket 存储空间名称 + * + */ +function uploadDir($ossClient, $bucket) +{ + $localDirectory = "."; + $prefix = "samples/codes"; + try { + $ossClient->uploadDir($bucket, $prefix, $localDirectory); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + printf(__FUNCTION__ . ": completeMultipartUpload OK\n"); +} + +/** + * 获取当前未完成的分片上传列表 + * + * @param $ossClient OssClient + * @param $bucket string + */ +function listMultipartUploads($ossClient, $bucket) +{ + $options = array( + 'max-uploads' => 100, + 'key-marker' => '', + 'prefix' => '', + 'upload-id-marker' => '' + ); + try { + $listMultipartUploadInfo = $ossClient->listMultipartUploads($bucket, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": listMultipartUploads FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + printf(__FUNCTION__ . ": listMultipartUploads OK\n"); + $listUploadInfo = $listMultipartUploadInfo->getUploads(); + var_dump($listUploadInfo); +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/Object.php b/source/vendor/aliyuncs/oss-sdk-php/samples/Object.php new file mode 100644 index 0000000..3bf024b --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/Object.php @@ -0,0 +1,517 @@ +putObject($bucket, "b.file", "hi, oss"); +Common::println("b.file is created"); +Common::println($result['x-oss-request-id']); +Common::println($result['etag']); +Common::println($result['content-md5']); +Common::println($result['body']); + +// 上传本地文件 +$result = $ossClient->uploadFile($bucket, "c.file", __FILE__); +Common::println("c.file is created"); +Common::println("b.file is created"); +Common::println($result['x-oss-request-id']); +Common::println($result['etag']); +Common::println($result['content-md5']); +Common::println($result['body']); + +// 下载object到本地变量 +$content = $ossClient->getObject($bucket, "b.file"); +Common::println("b.file is fetched, the content is: " . $content); + +// 给object添加symlink +$content = $ossClient->putSymlink($bucket, "test-symlink", "b.file"); +Common::println("test-symlink is created"); +Common::println($result['x-oss-request-id']); +Common::println($result['etag']); + +// 获取symlink +$content = $ossClient->getSymlink($bucket, "test-symlink"); +Common::println("test-symlink refer to : " . $content[OssClient::OSS_SYMLINK_TARGET]); + +// 下载object到本地文件 +$options = array( + OssClient::OSS_FILE_DOWNLOAD => "./c.file.localcopy", +); +$ossClient->getObject($bucket, "c.file", $options); +Common::println("b.file is fetched to the local file: c.file.localcopy"); +Common::println("b.file is created"); + +// 拷贝object +$result = $ossClient->copyObject($bucket, "c.file", $bucket, "c.file.copy"); +Common::println("lastModifiedTime: " . $result[0]); +Common::println("ETag: " . $result[1]); + +// 判断object是否存在 +$doesExist = $ossClient->doesObjectExist($bucket, "c.file.copy"); +Common::println("file c.file.copy exist? " . ($doesExist ? "yes" : "no")); + +// 删除object +$result = $ossClient->deleteObject($bucket, "c.file.copy"); +Common::println("c.file.copy is deleted"); +Common::println("b.file is created"); +Common::println($result['x-oss-request-id']); + +// 判断object是否存在 +$doesExist = $ossClient->doesObjectExist($bucket, "c.file.copy"); +Common::println("file c.file.copy exist? " . ($doesExist ? "yes" : "no")); + +// 批量删除object +$result = $ossClient->deleteObjects($bucket, array("b.file", "c.file")); +foreach($result as $object) + Common::println($object); + +sleep(2); +unlink("c.file.localcopy"); + +//******************************* 完整用法参考下面函数 **************************************************** + +listObjects($ossClient, $bucket); +listAllObjects($ossClient, $bucket); +createObjectDir($ossClient, $bucket); +putObject($ossClient, $bucket); +uploadFile($ossClient, $bucket); +getObject($ossClient, $bucket); +getObjectToLocalFile($ossClient, $bucket); +copyObject($ossClient, $bucket); +modifyMetaForObject($ossClient, $bucket); +getObjectMeta($ossClient, $bucket); +deleteObject($ossClient, $bucket); +deleteObjects($ossClient, $bucket); +doesObjectExist($ossClient, $bucket); +getSymlink($ossClient, $bucket); +putSymlink($ossClient, $bucket); +/** + * 创建虚拟目录 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function createObjectDir($ossClient, $bucket) +{ + try { + $ossClient->createObjectDir($bucket, "dir"); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 把本地变量的内容到文件 + * + * 简单上传,上传指定变量的内存值作为object的内容 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function putObject($ossClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $content = file_get_contents(__FILE__); + $options = array(); + try { + $ossClient->putObject($bucket, $object, $content, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + + +/** + * 上传指定的本地文件内容 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function uploadFile($ossClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $filePath = __FILE__; + $options = array(); + + try { + $ossClient->uploadFile($bucket, $object, $filePath, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 列出Bucket内所有目录和文件, 注意如果符合条件的文件数目超过设置的max-keys, 用户需要使用返回的nextMarker作为入参,通过 + * 循环调用ListObjects得到所有的文件,具体操作见下面的 listAllObjects 示例 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function listObjects($ossClient, $bucket) +{ + $prefix = 'oss-php-sdk-test/'; + $delimiter = '/'; + $nextMarker = ''; + $maxkeys = 1000; + $options = array( + 'delimiter' => $delimiter, + 'prefix' => $prefix, + 'max-keys' => $maxkeys, + 'marker' => $nextMarker, + ); + try { + $listObjectInfo = $ossClient->listObjects($bucket, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + $objectList = $listObjectInfo->getObjectList(); // 文件列表 + $prefixList = $listObjectInfo->getPrefixList(); // 目录列表 + if (!empty($objectList)) { + print("objectList:\n"); + foreach ($objectList as $objectInfo) { + print($objectInfo->getKey() . "\n"); + } + } + if (!empty($prefixList)) { + print("prefixList: \n"); + foreach ($prefixList as $prefixInfo) { + print($prefixInfo->getPrefix() . "\n"); + } + } +} + +/** + * 列出Bucket内所有目录和文件, 根据返回的nextMarker循环得到所有Objects + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function listAllObjects($ossClient, $bucket) +{ + //构造dir下的文件和虚拟目录 + for ($i = 0; $i < 100; $i += 1) { + $ossClient->putObject($bucket, "dir/obj" . strval($i), "hi"); + $ossClient->createObjectDir($bucket, "dir/obj" . strval($i)); + } + + $prefix = 'dir/'; + $delimiter = '/'; + $nextMarker = ''; + $maxkeys = 30; + + while (true) { + $options = array( + 'delimiter' => $delimiter, + 'prefix' => $prefix, + 'max-keys' => $maxkeys, + 'marker' => $nextMarker, + ); + var_dump($options); + try { + $listObjectInfo = $ossClient->listObjects($bucket, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + // 得到nextMarker,从上一次listObjects读到的最后一个文件的下一个文件开始继续获取文件列表 + $nextMarker = $listObjectInfo->getNextMarker(); + $listObject = $listObjectInfo->getObjectList(); + $listPrefix = $listObjectInfo->getPrefixList(); + var_dump(count($listObject)); + var_dump(count($listPrefix)); + if ($nextMarker === '') { + break; + } + } +} + +/** + * 获取object的内容 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getObject($ossClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $options = array(); + try { + $content = $ossClient->getObject($bucket, $object, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + if (file_get_contents(__FILE__) === $content) { + print(__FUNCTION__ . ": FileContent checked OK" . "\n"); + } else { + print(__FUNCTION__ . ": FileContent checked FAILED" . "\n"); + } +} + +/** + * put symlink + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function putSymlink($ossClient, $bucket) +{ + $symlink = "test-samples-symlink"; + $object = "test-samples-object"; + try { + $ossClient->putObject($bucket, $object, 'test-content'); + $ossClient->putSymlink($bucket, $symlink, $object); + $content = $ossClient->getObject($bucket, $symlink); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + if ($content == 'test-content') { + print(__FUNCTION__ . ": putSymlink checked OK" . "\n"); + } else { + print(__FUNCTION__ . ": putSymlink checked FAILED" . "\n"); + } +} + +/** + * 获取symlink + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getSymlink($ossClient, $bucket) +{ + $symlink = "test-samples-symlink"; + $object = "test-samples-object"; + try { + $ossClient->putObject($bucket, $object, 'test-content'); + $ossClient->putSymlink($bucket, $symlink, $object); + $content = $ossClient->getSymlink($bucket, $symlink); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + if ($content[OssClient::OSS_SYMLINK_TARGET]) { + print(__FUNCTION__ . ": getSymlink checked OK" . "\n"); + } else { + print(__FUNCTION__ . ": getSymlink checked FAILED" . "\n"); + } +} + +/** + * get_object_to_local_file + * + * 获取object + * 将object下载到指定的文件 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getObjectToLocalFile($ossClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $localfile = "upload-test-object-name.txt"; + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $localfile, + ); + + try { + $ossClient->getObject($bucket, $object, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK, please check localfile: 'upload-test-object-name.txt'" . "\n"); + if (file_get_contents($localfile) === file_get_contents(__FILE__)) { + print(__FUNCTION__ . ": FileContent checked OK" . "\n"); + } else { + print(__FUNCTION__ . ": FileContent checked FAILED" . "\n"); + } + if (file_exists($localfile)) { + unlink($localfile); + } +} + +/** + * 拷贝object + * 当目的object和源object完全相同时,表示修改object的meta信息 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function copyObject($ossClient, $bucket) +{ + $fromBucket = $bucket; + $fromObject = "oss-php-sdk-test/upload-test-object-name.txt"; + $toBucket = $bucket; + $toObject = $fromObject . '.copy'; + $options = array(); + + try { + $ossClient->copyObject($fromBucket, $fromObject, $toBucket, $toObject, $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 修改Object Meta + * 利用copyObject接口的特性:当目的object和源object完全相同时,表示修改object的meta信息 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function modifyMetaForObject($ossClient, $bucket) +{ + $fromBucket = $bucket; + $fromObject = "oss-php-sdk-test/upload-test-object-name.txt"; + $toBucket = $bucket; + $toObject = $fromObject; + $copyOptions = array( + OssClient::OSS_HEADERS => array( + 'Cache-Control' => 'max-age=60', + 'Content-Disposition' => 'attachment; filename="xxxxxx"', + ), + ); + try { + $ossClient->copyObject($fromBucket, $fromObject, $toBucket, $toObject, $copyOptions); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 获取object meta, 也就是getObjectMeta接口 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function getObjectMeta($ossClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + try { + $objectMeta = $ossClient->getObjectMeta($bucket, $object); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + if (isset($objectMeta[strtolower('Content-Disposition')]) && + 'attachment; filename="xxxxxx"' === $objectMeta[strtolower('Content-Disposition')] + ) { + print(__FUNCTION__ . ": ObjectMeta checked OK" . "\n"); + } else { + print(__FUNCTION__ . ": ObjectMeta checked FAILED" . "\n"); + } +} + +/** + * 删除object + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function deleteObject($ossClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + try { + $ossClient->deleteObject($bucket, $object); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + + +/** + * 批量删除object + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function deleteObjects($ossClient, $bucket) +{ + $objects = array(); + $objects[] = "oss-php-sdk-test/upload-test-object-name.txt"; + $objects[] = "oss-php-sdk-test/upload-test-object-name.txt.copy"; + try { + $ossClient->deleteObjects($bucket, $objects); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); +} + +/** + * 判断object是否存在 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + */ +function doesObjectExist($ossClient, $bucket) +{ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + try { + $exist = $ossClient->doesObjectExist($bucket, $object); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + var_dump($exist); +} + diff --git a/source/vendor/aliyuncs/oss-sdk-php/samples/RunAll.php b/source/vendor/aliyuncs/oss-sdk-php/samples/RunAll.php new file mode 100644 index 0000000..a4d6d9b --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/samples/RunAll.php @@ -0,0 +1,13 @@ +uploadFile($bucket, "a.file", __FILE__); + +// 生成GetObject的签名url,用户可以使用这个url直接在浏览器下载 +$signedUrl = $ossClient->signUrl($bucket, "a.file", 3600); +Common::println($signedUrl); + +// 生成用于putObject的签名URL,用户可以直接用put方法使用这个url上传文件到 "a.file" +$signedUrl = $ossClient->signUrl($bucket, "a.file", "3600", "PUT"); +Common::println($signedUrl); + +// 生成从本地文件上传PutObject的签名url, 用户可以直接使用这个url把本地文件上传到 "a.file" +$signedUrl = $ossClient->signUrl($bucket, "a.file", 3600, "PUT", array('Content-Type' => 'txt')); +Common::println($signedUrl); + +//******************************* 完整用法参考下面函数 **************************************************** + +getSignedUrlForPuttingObject($ossClient, $bucket); +getSignedUrlForPuttingObjectFromFile($ossClient, $bucket); +getSignedUrlForGettingObject($ossClient, $bucket); + +/** + * 生成GetObject的签名url,主要用于私有权限下的读访问控制 + * + * @param $ossClient OssClient OssClient实例 + * @param $bucket string 存储空间名称 + * @return null + */ +function getSignedUrlForGettingObject($ossClient, $bucket) +{ + $object = "test/test-signature-test-upload-and-download.txt"; + $timeout = 3600; + try { + $signedUrl = $ossClient->signUrl($bucket, $object, $timeout); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": signedUrl: " . $signedUrl . "\n"); + /** + * 可以类似的代码来访问签名的URL,也可以输入到浏览器中去访问 + */ + $request = new RequestCore($signedUrl); + $request->set_method('GET'); + $request->add_header('Content-Type', ''); + $request->send_request(); + $res = new ResponseCore($request->get_response_header(), $request->get_response_body(), $request->get_response_code()); + if ($res->isOK()) { + print(__FUNCTION__ . ": OK" . "\n"); + } else { + print(__FUNCTION__ . ": FAILED" . "\n"); + }; +} + +/** + * 生成PutObject的签名url,主要用于私有权限下的写访问控制 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @return null + * @throws OssException + */ +function getSignedUrlForPuttingObject($ossClient, $bucket) +{ + $object = "test/test-signature-test-upload-and-download.txt"; + $timeout = 3600; + $options = NULL; + try { + $signedUrl = $ossClient->signUrl($bucket, $object, $timeout, "PUT"); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": signedUrl: " . $signedUrl . "\n"); + $content = file_get_contents(__FILE__); + + $request = new RequestCore($signedUrl); + $request->set_method('PUT'); + $request->add_header('Content-Type', ''); + $request->add_header('Content-Length', strlen($content)); + $request->set_body($content); + $request->send_request(); + $res = new ResponseCore($request->get_response_header(), + $request->get_response_body(), $request->get_response_code()); + if ($res->isOK()) { + print(__FUNCTION__ . ": OK" . "\n"); + } else { + print(__FUNCTION__ . ": FAILED" . "\n"); + }; +} + +/** + * 生成PutObject的签名url,主要用于私有权限下的写访问控制, 用户可以利用生成的signedUrl + * 从文件上传文件 + * + * @param OssClient $ossClient OssClient实例 + * @param string $bucket 存储空间名称 + * @throws OssException + */ +function getSignedUrlForPuttingObjectFromFile($ossClient, $bucket) +{ + $file = __FILE__; + $object = "test/test-signature-test-upload-and-download.txt"; + $timeout = 3600; + $options = array('Content-Type' => 'txt'); + try { + $signedUrl = $ossClient->signUrl($bucket, $object, $timeout, "PUT", $options); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": signedUrl: " . $signedUrl . "\n"); + + $request = new RequestCore($signedUrl); + $request->set_method('PUT'); + $request->add_header('Content-Type', 'txt'); + $request->set_read_file($file); + $request->set_read_stream_size(filesize($file)); + $request->send_request(); + $res = new ResponseCore($request->get_response_header(), + $request->get_response_body(), $request->get_response_code()); + if ($res->isOK()) { + print(__FUNCTION__ . ": OK" . "\n"); + } else { + print(__FUNCTION__ . ": FAILED" . "\n"); + }; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/MimeTypes.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/MimeTypes.php new file mode 100644 index 0000000..e9b88ff --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/MimeTypes.php @@ -0,0 +1,262 @@ + 1) { + $ext = strtolower(end($parts)); + if (isset(self::$mime_types[$ext])) { + return self::$mime_types[$ext]; + } + } + + return null; + } + + private static $mime_types = array( + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'apk' => 'application/vnd.android.package-archive', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'doc' => 'application/msword', + 'ogg' => 'audio/ogg', + 'pdf' => 'application/pdf', + 'rtf' => 'text/rtf', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'sxw' => 'application/vnd.sun.xml.writer', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sxc' => 'application/vnd.sun.xml.calc', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'sxd' => 'application/vnd.sun.xml.draw', + 'std' => 'application/vnd.sun.xml.draw.template', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxm' => 'application/vnd.sun.xml.math', + 'sis' => 'application/vnd.symbian.install', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'bcpio' => 'application/x-bcpio', + 'torrent' => 'application/x-bittorrent', + 'bz2' => 'application/x-bzip2', + 'vcd' => 'application/x-cdlink', + 'pgn' => 'application/x-chess-pgn', + 'cpio' => 'application/x-cpio', + 'csh' => 'application/x-csh', + 'dvi' => 'application/x-dvi', + 'spl' => 'application/x-futuresplash', + 'gtar' => 'application/x-gtar', + 'hdf' => 'application/x-hdf', + 'jar' => 'application/java-archive', + 'jnlp' => 'application/x-java-jnlp-file', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'ksp' => 'application/x-kspread', + 'chrt' => 'application/x-kchart', + 'kil' => 'application/x-killustrator', + 'latex' => 'application/x-latex', + 'rpm' => 'application/x-rpm', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', + 'tex' => 'application/x-tex', + 'man' => 'application/x-troff-man', + 'me' => 'application/x-troff-me', + 'ms' => 'application/x-troff-ms', + 'ustar' => 'application/x-ustar', + 'src' => 'application/x-wais-source', + 'zip' => 'application/zip', + 'm3u' => 'audio/x-mpegurl', + 'ra' => 'audio/x-pn-realaudio', + 'wav' => 'audio/x-wav', + 'wma' => 'audio/x-ms-wma', + 'wax' => 'audio/x-ms-wax', + 'pdb' => 'chemical/x-pdb', + 'xyz' => 'chemical/x-xyz', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'ief' => 'image/ief', + 'png' => 'image/png', + 'wbmp' => 'image/vnd.wap.wbmp', + 'ras' => 'image/x-cmu-raster', + 'pnm' => 'image/x-portable-anymap', + 'pbm' => 'image/x-portable-bitmap', + 'pgm' => 'image/x-portable-graymap', + 'ppm' => 'image/x-portable-pixmap', + 'rgb' => 'image/x-rgb', + 'xbm' => 'image/x-xbitmap', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'css' => 'text/css', + 'rtx' => 'text/richtext', + 'tsv' => 'text/tab-separated-values', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'wml' => 'text/vnd.wap.wml', + 'wmls' => 'text/vnd.wap.wmlscript', + 'etx' => 'text/x-setext', + 'mxu' => 'video/vnd.mpegurl', + 'flv' => 'video/x-flv', + 'wm' => 'video/x-ms-wm', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wvx' => 'video/x-ms-wvx', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'ice' => 'x-conference/x-cooltalk', + '3gp' => 'video/3gpp', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'atom' => 'application/atom+xml', + 'au' => 'audio/basic', + 'bin' => 'application/octet-stream', + 'cdf' => 'application/x-netcdf', + 'cgm' => 'image/cgm', + 'class' => 'application/octet-stream', + 'dcr' => 'application/x-director', + 'dif' => 'video/x-dv', + 'dir' => 'application/x-director', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dtd' => 'application/xml-dtd', + 'dv' => 'video/x-dv', + 'dxr' => 'application/x-director', + 'eps' => 'application/postscript', + 'exe' => 'application/octet-stream', + 'ez' => 'application/andrew-inset', + 'gram' => 'application/srgs', + 'grxml' => 'application/srgs+xml', + 'gz' => 'application/x-gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ifb' => 'text/calendar', + 'iges' => 'model/iges', + 'igs' => 'model/iges', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'kar' => 'audio/midi', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'm4a' => 'audio/mp4a-latm', + 'm4p' => 'audio/mp4a-latm', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'mac' => 'image/x-macpaint', + 'mathml' => 'application/mathml+xml', + 'mesh' => 'model/mesh', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpga' => 'audio/mpeg', + 'msh' => 'model/mesh', + 'nc' => 'application/x-netcdf', + 'oda' => 'application/oda', + 'ogv' => 'video/ogv', + 'pct' => 'image/pict', + 'pic' => 'image/pict', + 'pict' => 'image/pict', + 'pnt' => 'image/x-macpaint', + 'pntg' => 'image/x-macpaint', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'qti' => 'image/x-quicktime', + 'qtif' => 'image/x-quicktime', + 'ram' => 'audio/x-pn-realaudio', + 'rdf' => 'application/rdf+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'roff' => 'application/x-troff', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'silo' => 'model/mesh', + 'skd' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'skp' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'snd' => 'audio/basic', + 'so' => 'application/octet-stream', + 'svg' => 'image/svg+xml', + 't' => 'application/x-troff', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tr' => 'application/x-troff', + 'txt' => 'text/plain', + 'vrml' => 'model/vrml', + 'vxml' => 'application/voicexml+xml', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wrl' => 'model/vrml', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + ); +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/OssException.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/OssException.php new file mode 100644 index 0000000..b0e9e8b --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/OssException.php @@ -0,0 +1,54 @@ +details = $details; + } else { + $message = $details; + parent::__construct($message); + } + } + + public function getHTTPStatus() + { + return isset($this->details['status']) ? $this->details['status'] : ''; + } + + public function getRequestId() + { + return isset($this->details['request-id']) ? $this->details['request-id'] : ''; + } + + public function getErrorCode() + { + return isset($this->details['code']) ? $this->details['code'] : ''; + } + + public function getErrorMessage() + { + return isset($this->details['message']) ? $this->details['message'] : ''; + } + + public function getDetails() + { + return isset($this->details['body']) ? $this->details['body'] : ''; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/OssUtil.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/OssUtil.php new file mode 100644 index 0000000..6e5d413 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Core/OssUtil.php @@ -0,0 +1,461 @@ + $value) { + if (is_string($key) && !is_array($value)) { + $temp[] = rawurlencode($key) . '=' . rawurlencode($value); + } + } + return implode('&', $temp); + } + + /** + * 转义字符替换 + * + * @param string $subject + * @return string + */ + public static function sReplace($subject) + { + $search = array('<', '>', '&', '\'', '"'); + $replace = array('<', '>', '&', ''', '"'); + return str_replace($search, $replace, $subject); + } + + /** + * 检查是否是中文编码 + * + * @param $str + * @return int + */ + public static function chkChinese($str) + { + return preg_match('/[\x80-\xff]./', $str); + } + + /** + * 检测是否GB2312编码 + * + * @param string $str + * @return boolean false UTF-8编码 TRUE GB2312编码 + */ + public static function isGb2312($str) + { + for ($i = 0; $i < strlen($str); $i++) { + $v = ord($str[$i]); + if ($v > 127) { + if (($v >= 228) && ($v <= 233)) { + if (($i + 2) >= (strlen($str) - 1)) return true; // not enough characters + $v1 = ord($str[$i + 1]); + $v2 = ord($str[$i + 2]); + if (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) + return false; + else + return true; + } + } + } + return false; + } + + /** + * 检测是否GBK编码 + * + * @param string $str + * @param boolean $gbk + * @return boolean + */ + public static function checkChar($str, $gbk = true) + { + for ($i = 0; $i < strlen($str); $i++) { + $v = ord($str[$i]); + if ($v > 127) { + if (($v >= 228) && ($v <= 233)) { + if (($i + 2) >= (strlen($str) - 1)) return $gbk ? true : FALSE; // not enough characters + $v1 = ord($str[$i + 1]); + $v2 = ord($str[$i + 2]); + if ($gbk) { + return (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) ? FALSE : TRUE;//GBK + } else { + return (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) ? TRUE : FALSE; + } + } + } + } + return $gbk ? TRUE : FALSE; + } + + /** + * 检验bucket名称是否合法 + * bucket的命名规范: + * 1. 只能包括小写字母,数字 + * 2. 必须以小写字母或者数字开头 + * 3. 长度必须在3-63字节之间 + * + * @param string $bucket Bucket名称 + * @return boolean + */ + public static function validateBucket($bucket) + { + $pattern = '/^[a-z0-9][a-z0-9-]{2,62}$/'; + if (!preg_match($pattern, $bucket)) { + return false; + } + return true; + } + + /** + * 检验object名称是否合法 + * object命名规范: + * 1. 规则长度必须在1-1023字节之间 + * 2. 使用UTF-8编码 + * 3. 不能以 "/" "\\"开头 + * + * @param string $object Object名称 + * @return boolean + */ + public static function validateObject($object) + { + $pattern = '/^.{1,1023}$/'; + if (empty($object) || !preg_match($pattern, $object) || + self::startsWith($object, '/') || self::startsWith($object, '\\') + ) { + return false; + } + return true; + } + + + /** + * 判断字符串$str是不是以$findMe开始 + * + * @param string $str + * @param string $findMe + * @return bool + */ + public static function startsWith($str, $findMe) + { + if (strpos($str, $findMe) === 0) { + return true; + } else { + return false; + } + } + + /** + * 生成createBucketXmlBody接口的xml消息 + * + * @param string $storageClass + * @return string + */ + public static function createBucketXmlBody($storageClass) + { + $xml = new \SimpleXMLElement(''); + $xml->addChild('StorageClass', $storageClass); + return $xml->asXML(); + } + + /** + * 检验$options + * + * @param array $options + * @throws OssException + * @return boolean + */ + public static function validateOptions($options) + { + //$options + if ($options != NULL && !is_array($options)) { + throw new OssException ($options . ':' . 'option must be array'); + } + } + + /** + * 检查上传文件的内容是否合法 + * + * @param $content string + * @throws OssException + */ + public static function validateContent($content) + { + if (empty($content)) { + throw new OssException("http body content is invalid"); + } + } + + /** + * 校验BUCKET/OBJECT/OBJECT GROUP是否为空 + * + * @param string $name + * @param string $errMsg + * @throws OssException + * @return void + */ + public static function throwOssExceptionWithMessageIfEmpty($name, $errMsg) + { + if (empty($name)) { + throw new OssException($errMsg); + } + } + + /** + * 仅供测试使用的接口,请勿使用 + * + * @param $filename + * @param $size + */ + public static function generateFile($filename, $size) + { + if (file_exists($filename) && $size == filesize($filename)) { + echo $filename . " already exists, no need to create again. "; + return; + } + $part_size = 1 * 1024 * 1024; + $fp = fopen($filename, "w"); + $characters = << 0) { + if ($size < $part_size) { + $write_size = $size; + } else { + $write_size = $part_size; + } + $size -= $write_size; + $a = $characters[rand(0, $charactersLength - 1)]; + $content = str_repeat($a, $write_size); + $flag = fwrite($fp, $content); + if (!$flag) { + echo "write to " . $filename . " failed.
    "; + break; + } + } + } else { + echo "open " . $filename . " failed.
    "; + } + fclose($fp); + } + + /** + * 得到文件的md5编码 + * + * @param $filename + * @param $from_pos + * @param $to_pos + * @return string + */ + public static function getMd5SumForFile($filename, $from_pos, $to_pos) + { + $content_md5 = ""; + if (($to_pos - $from_pos) > self::OSS_MAX_PART_SIZE) { + return $content_md5; + } + $filesize = filesize($filename); + if ($from_pos >= $filesize || $to_pos >= $filesize || $from_pos < 0 || $to_pos < 0) { + return $content_md5; + } + + $total_length = $to_pos - $from_pos + 1; + $buffer = 8192; + $left_length = $total_length; + if (!file_exists($filename)) { + return $content_md5; + } + + if (false === $fh = fopen($filename, 'rb')) { + return $content_md5; + } + + fseek($fh, $from_pos); + $data = ''; + while (!feof($fh)) { + if ($left_length >= $buffer) { + $read_length = $buffer; + } else { + $read_length = $left_length; + } + if ($read_length <= 0) { + break; + } else { + $data .= fread($fh, $read_length); + $left_length = $left_length - $read_length; + } + } + fclose($fh); + $content_md5 = base64_encode(md5($data, true)); + return $content_md5; + } + + /** + * 检测是否windows系统,因为windows系统默认编码为GBK + * + * @return bool + */ + public static function isWin() + { + return strtoupper(substr(PHP_OS, 0, 3)) == "WIN"; + } + + /** + * 主要是由于windows系统编码是gbk,遇到中文时候,如果不进行转换处理会出现找不到文件的问题 + * + * @param $file_path + * @return string + */ + public static function encodePath($file_path) + { + if (self::chkChinese($file_path) && self::isWin()) { + $file_path = iconv('utf-8', 'gbk', $file_path); + } + return $file_path; + } + + /** + * 判断用户输入的endpoint是否是 xxx.xxx.xxx.xxx:port 或者 xxx.xxx.xxx.xxx的ip格式 + * + * @param string $endpoint 需要做判断的endpoint + * @return boolean + */ + public static function isIPFormat($endpoint) + { + $ip_array = explode(":", $endpoint); + $hostname = $ip_array[0]; + $ret = filter_var($hostname, FILTER_VALIDATE_IP); + if (!$ret) { + return false; + } else { + return true; + } + } + + /** + * 生成DeleteMultiObjects接口的xml消息 + * + * @param string[] $objects + * @param bool $quiet + * @return string + */ + public static function createDeleteObjectsXmlBody($objects, $quiet) + { + $xml = new \SimpleXMLElement(''); + $xml->addChild('Quiet', $quiet); + foreach ($objects as $object) { + $sub_object = $xml->addChild('Object'); + $object = OssUtil::sReplace($object); + $sub_object->addChild('Key', $object); + } + return $xml->asXML(); + } + + /** + * 生成CompleteMultipartUpload接口的xml消息 + * + * @param array[] $listParts + * @return string + */ + public static function createCompleteMultipartUploadXmlBody($listParts) + { + $xml = new \SimpleXMLElement(''); + foreach ($listParts as $node) { + $part = $xml->addChild('Part'); + $part->addChild('PartNumber', $node['PartNumber']); + $part->addChild('ETag', $node['ETag']); + } + return $xml->asXML(); + } + + /** + * 读取目录 + * + * @param string $dir + * @param string $exclude + * @param bool $recursive + * @return string[] + */ + public static function readDir($dir, $exclude = ".|..|.svn|.git", $recursive = false) + { + $file_list_array = array(); + $base_path = $dir; + $exclude_array = explode("|", $exclude); + $exclude_array = array_unique(array_merge($exclude_array, array('.', '..'))); + + if ($recursive) { + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir)) as $new_file) { + if ($new_file->isDir()) continue; + $object = str_replace($base_path, '', $new_file); + if (!in_array(strtolower($object), $exclude_array)) { + $object = ltrim($object, '/'); + if (is_file($new_file)) { + $key = md5($new_file . $object, false); + $file_list_array[$key] = array('path' => $new_file, 'file' => $object,); + } + } + } + } else if ($handle = opendir($dir)) { + while (false !== ($file = readdir($handle))) { + if (!in_array(strtolower($file), $exclude_array)) { + $new_file = $dir . '/' . $file; + $object = $file; + $object = ltrim($object, '/'); + if (is_file($new_file)) { + $key = md5($new_file . $object, false); + $file_list_array[$key] = array('path' => $new_file, 'file' => $object,); + } + } + } + closedir($handle); + } + return $file_list_array; + } + + /** + * Decode key based on the encoding type + * + * @param string $key + * @param string $encoding + * @return string + */ + public static function decodeKey($key, $encoding) + { + if ($encoding == "") { + return $key; + } + + if ($encoding == "url") { + return rawurldecode($key); + } else { + throw new OssException("Unrecognized encoding type: " . $encoding); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/LICENSE b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/LICENSE new file mode 100644 index 0000000..49b38bd --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2006-2010 Ryan Parman, Foleeo Inc., and contributors. 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 Ryan Parman, Foleeo Inc. 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 HOLDERS +AND 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/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/RequestCore.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/RequestCore.php new file mode 100644 index 0000000..ddbda0d --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/RequestCore.php @@ -0,0 +1,896 @@ +). + */ + public $request_class = 'OSS\Http\RequestCore'; + + /** + * The default class to use for HTTP Responses (defaults to ). + */ + public $response_class = 'OSS\Http\ResponseCore'; + + /** + * Default useragent string to use. + */ + public $useragent = 'RequestCore/1.4.3'; + + /** + * File to read from while streaming up. + */ + public $read_file = null; + + /** + * The resource to read from while streaming up. + */ + public $read_stream = null; + + /** + * The size of the stream to read from. + */ + public $read_stream_size = null; + + /** + * The length already read from the stream. + */ + public $read_stream_read = 0; + + /** + * File to write to while streaming down. + */ + public $write_file = null; + + /** + * The resource to write to while streaming down. + */ + public $write_stream = null; + + /** + * Stores the intended starting seek position. + */ + public $seek_position = null; + + /** + * The location of the cacert.pem file to use. + */ + public $cacert_location = false; + + /** + * The state of SSL certificate verification. + */ + public $ssl_verification = false; + + /** + * The user-defined callback function to call when a stream is read from. + */ + public $registered_streaming_read_callback = null; + + /** + * The user-defined callback function to call when a stream is written to. + */ + public $registered_streaming_write_callback = null; + + /** + * 请求超时时间, 默认是5184000秒,6天 + * + * @var int + */ + public $timeout = 5184000; + + /** + * 连接超时时间,默认是10秒 + * + * @var int + */ + public $connect_timeout = 10; + + /*%******************************************************************************************%*/ + // CONSTANTS + + /** + * GET HTTP Method + */ + const HTTP_GET = 'GET'; + + /** + * POST HTTP Method + */ + const HTTP_POST = 'POST'; + + /** + * PUT HTTP Method + */ + const HTTP_PUT = 'PUT'; + + /** + * DELETE HTTP Method + */ + const HTTP_DELETE = 'DELETE'; + + /** + * HEAD HTTP Method + */ + const HTTP_HEAD = 'HEAD'; + + + /*%******************************************************************************************%*/ + // CONSTRUCTOR/DESTRUCTOR + + /** + * Constructs a new instance of this class. + * + * @param string $url (Optional) The URL to request or service endpoint to query. + * @param string $proxy (Optional) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port` + * @param array $helpers (Optional) An associative array of classnames to use for request, and response functionality. Gets passed in automatically by the calling class. + * @return $this A reference to the current instance. + */ + public function __construct($url = null, $proxy = null, $helpers = null) + { + // Set some default values. + $this->request_url = $url; + $this->method = self::HTTP_GET; + $this->request_headers = array(); + $this->request_body = ''; + + // Set a new Request class if one was set. + if (isset($helpers['request']) && !empty($helpers['request'])) { + $this->request_class = $helpers['request']; + } + + // Set a new Request class if one was set. + if (isset($helpers['response']) && !empty($helpers['response'])) { + $this->response_class = $helpers['response']; + } + + if ($proxy) { + $this->set_proxy($proxy); + } + + return $this; + } + + /** + * Destructs the instance. Closes opened file handles. + * + * @return $this A reference to the current instance. + */ + public function __destruct() + { + if (isset($this->read_file) && isset($this->read_stream)) { + fclose($this->read_stream); + } + + if (isset($this->write_file) && isset($this->write_stream)) { + fclose($this->write_stream); + } + + return $this; + } + + + /*%******************************************************************************************%*/ + // REQUEST METHODS + + /** + * Sets the credentials to use for authentication. + * + * @param string $user (Required) The username to authenticate with. + * @param string $pass (Required) The password to authenticate with. + * @return $this A reference to the current instance. + */ + public function set_credentials($user, $pass) + { + $this->username = $user; + $this->password = $pass; + return $this; + } + + /** + * Adds a custom HTTP header to the cURL request. + * + * @param string $key (Required) The custom HTTP header to set. + * @param mixed $value (Required) The value to assign to the custom HTTP header. + * @return $this A reference to the current instance. + */ + public function add_header($key, $value) + { + $this->request_headers[$key] = $value; + return $this; + } + + /** + * Removes an HTTP header from the cURL request. + * + * @param string $key (Required) The custom HTTP header to set. + * @return $this A reference to the current instance. + */ + public function remove_header($key) + { + if (isset($this->request_headers[$key])) { + unset($this->request_headers[$key]); + } + return $this; + } + + /** + * Set the method type for the request. + * + * @param string $method (Required) One of the following constants: , , , , . + * @return $this A reference to the current instance. + */ + public function set_method($method) + { + $this->method = strtoupper($method); + return $this; + } + + /** + * Sets a custom useragent string for the class. + * + * @param string $ua (Required) The useragent string to use. + * @return $this A reference to the current instance. + */ + public function set_useragent($ua) + { + $this->useragent = $ua; + return $this; + } + + /** + * Set the body to send in the request. + * + * @param string $body (Required) The textual content to send along in the body of the request. + * @return $this A reference to the current instance. + */ + public function set_body($body) + { + $this->request_body = $body; + return $this; + } + + /** + * Set the URL to make the request to. + * + * @param string $url (Required) The URL to make the request to. + * @return $this A reference to the current instance. + */ + public function set_request_url($url) + { + $this->request_url = $url; + return $this; + } + + /** + * Set additional CURLOPT settings. These will merge with the default settings, and override if + * there is a duplicate. + * + * @param array $curlopts (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings. + * @return $this A reference to the current instance. + */ + public function set_curlopts($curlopts) + { + $this->curlopts = $curlopts; + return $this; + } + + /** + * Sets the length in bytes to read from the stream while streaming up. + * + * @param integer $size (Required) The length in bytes to read from the stream. + * @return $this A reference to the current instance. + */ + public function set_read_stream_size($size) + { + $this->read_stream_size = $size; + + return $this; + } + + /** + * Sets the resource to read from while streaming up. Reads the stream from its current position until + * EOF or `$size` bytes have been read. If `$size` is not given it will be determined by and + * . + * + * @param resource $resource (Required) The readable resource to read from. + * @param integer $size (Optional) The size of the stream to read. + * @return $this A reference to the current instance. + */ + public function set_read_stream($resource, $size = null) + { + if (!isset($size) || $size < 0) { + $stats = fstat($resource); + + if ($stats && $stats['size'] >= 0) { + $position = ftell($resource); + + if ($position !== false && $position >= 0) { + $size = $stats['size'] - $position; + } + } + } + + $this->read_stream = $resource; + + return $this->set_read_stream_size($size); + } + + /** + * Sets the file to read from while streaming up. + * + * @param string $location (Required) The readable location to read from. + * @return $this A reference to the current instance. + */ + public function set_read_file($location) + { + $this->read_file = $location; + $read_file_handle = fopen($location, 'r'); + + return $this->set_read_stream($read_file_handle); + } + + /** + * Sets the resource to write to while streaming down. + * + * @param resource $resource (Required) The writeable resource to write to. + * @return $this A reference to the current instance. + */ + public function set_write_stream($resource) + { + $this->write_stream = $resource; + + return $this; + } + + /** + * Sets the file to write to while streaming down. + * + * @param string $location (Required) The writeable location to write to. + * @return $this A reference to the current instance. + */ + public function set_write_file($location) + { + $this->write_file = $location; + } + + /** + * Set the proxy to use for making requests. + * + * @param string $proxy (Required) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port` + * @return $this A reference to the current instance. + */ + public function set_proxy($proxy) + { + $proxy = parse_url($proxy); + $proxy['user'] = isset($proxy['user']) ? $proxy['user'] : null; + $proxy['pass'] = isset($proxy['pass']) ? $proxy['pass'] : null; + $proxy['port'] = isset($proxy['port']) ? $proxy['port'] : null; + $this->proxy = $proxy; + return $this; + } + + /** + * Set the intended starting seek position. + * + * @param integer $position (Required) The byte-position of the stream to begin reading from. + * @return $this A reference to the current instance. + */ + public function set_seek_position($position) + { + $this->seek_position = isset($position) ? (integer)$position : null; + + return $this; + } + + /** + * A callback function that is invoked by cURL for streaming up. + * + * @param resource $curl_handle (Required) The cURL handle for the request. + * @param resource $header_content (Required) The header callback result. + * @return headers from a stream. + */ + public function streaming_header_callback($curl_handle, $header_content) + { + $code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); + + if (isset($this->write_file) && intval($code) / 100 == 2 && !isset($this->write_file_handle)) + { + $this->write_file_handle = fopen($this->write_file, 'w'); + $this->set_write_stream($this->write_file_handle); + } + + $this->response_raw_headers .= $header_content; + return strlen($header_content); + } + + + /** + * Register a callback function to execute whenever a data stream is read from using + * . + * + * The user-defined callback function should accept three arguments: + * + *
      + *
    • $curl_handle - resource - Required - The cURL handle resource that represents the in-progress transfer.
    • + *
    • $file_handle - resource - Required - The file handle resource that represents the file on the local file system.
    • + *
    • $length - integer - Required - The length in kilobytes of the data chunk that was transferred.
    • + *
    + * + * @param string|array|function $callback (Required) The callback function is called by , so you can pass the following values:
      + *
    • The name of a global function to execute, passed as a string.
    • + *
    • A method to execute, passed as array('ClassName', 'MethodName').
    • + *
    • An anonymous function (PHP 5.3+).
    + * @return $this A reference to the current instance. + */ + public function register_streaming_read_callback($callback) + { + $this->registered_streaming_read_callback = $callback; + + return $this; + } + + /** + * Register a callback function to execute whenever a data stream is written to using + * . + * + * The user-defined callback function should accept two arguments: + * + *
      + *
    • $curl_handle - resource - Required - The cURL handle resource that represents the in-progress transfer.
    • + *
    • $length - integer - Required - The length in kilobytes of the data chunk that was transferred.
    • + *
    + * + * @param string|array|function $callback (Required) The callback function is called by , so you can pass the following values:
      + *
    • The name of a global function to execute, passed as a string.
    • + *
    • A method to execute, passed as array('ClassName', 'MethodName').
    • + *
    • An anonymous function (PHP 5.3+).
    + * @return $this A reference to the current instance. + */ + public function register_streaming_write_callback($callback) + { + $this->registered_streaming_write_callback = $callback; + + return $this; + } + + + /*%******************************************************************************************%*/ + // PREPARE, SEND, AND PROCESS REQUEST + + /** + * A callback function that is invoked by cURL for streaming up. + * + * @param resource $curl_handle (Required) The cURL handle for the request. + * @param resource $file_handle (Required) The open file handle resource. + * @param integer $length (Required) The maximum number of bytes to read. + * @return binary Binary data from a stream. + */ + public function streaming_read_callback($curl_handle, $file_handle, $length) + { + // Once we've sent as much as we're supposed to send... + if ($this->read_stream_read >= $this->read_stream_size) { + // Send EOF + return ''; + } + + // If we're at the beginning of an upload and need to seek... + if ($this->read_stream_read == 0 && isset($this->seek_position) && $this->seek_position !== ftell($this->read_stream)) { + if (fseek($this->read_stream, $this->seek_position) !== 0) { + throw new RequestCore_Exception('The stream does not support seeking and is either not at the requested position or the position is unknown.'); + } + } + + $read = fread($this->read_stream, min($this->read_stream_size - $this->read_stream_read, $length)); // Remaining upload data or cURL's requested chunk size + $this->read_stream_read += strlen($read); + + $out = $read === false ? '' : $read; + + // Execute callback function + if ($this->registered_streaming_read_callback) { + call_user_func($this->registered_streaming_read_callback, $curl_handle, $file_handle, $out); + } + + return $out; + } + + /** + * A callback function that is invoked by cURL for streaming down. + * + * @param resource $curl_handle (Required) The cURL handle for the request. + * @param binary $data (Required) The data to write. + * @return integer The number of bytes written. + */ + public function streaming_write_callback($curl_handle, $data) + { + $code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); + + if (intval($code) / 100 != 2) + { + $this->response_error_body .= $data; + return strlen($data); + } + + $length = strlen($data); + $written_total = 0; + $written_last = 0; + + while ($written_total < $length) { + $written_last = fwrite($this->write_stream, substr($data, $written_total)); + + if ($written_last === false) { + return $written_total; + } + + $written_total += $written_last; + } + + // Execute callback function + if ($this->registered_streaming_write_callback) { + call_user_func($this->registered_streaming_write_callback, $curl_handle, $written_total); + } + + return $written_total; + } + + /** + * Prepares and adds the details of the cURL request. This can be passed along to a + * function. + * + * @return resource The handle for the cURL object. + * + */ + public function prep_request() + { + $curl_handle = curl_init(); + + // Set default options. + curl_setopt($curl_handle, CURLOPT_URL, $this->request_url); + curl_setopt($curl_handle, CURLOPT_FILETIME, true); + curl_setopt($curl_handle, CURLOPT_FRESH_CONNECT, false); +// curl_setopt($curl_handle, CURLOPT_CLOSEPOLICY, CURLCLOSEPOLICY_LEAST_RECENTLY_USED); + curl_setopt($curl_handle, CURLOPT_MAXREDIRS, 5); + curl_setopt($curl_handle, CURLOPT_HEADER, true); + curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl_handle, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout); + curl_setopt($curl_handle, CURLOPT_NOSIGNAL, true); + curl_setopt($curl_handle, CURLOPT_REFERER, $this->request_url); + curl_setopt($curl_handle, CURLOPT_USERAGENT, $this->useragent); + curl_setopt($curl_handle, CURLOPT_HEADERFUNCTION, array($this, 'streaming_header_callback')); + curl_setopt($curl_handle, CURLOPT_READFUNCTION, array($this, 'streaming_read_callback')); + + // Verification of the SSL cert + if ($this->ssl_verification) { + curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, 2); + } else { + curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false); + } + + // chmod the file as 0755 + if ($this->cacert_location === true) { + curl_setopt($curl_handle, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem'); + } elseif (is_string($this->cacert_location)) { + curl_setopt($curl_handle, CURLOPT_CAINFO, $this->cacert_location); + } + + // Debug mode + if ($this->debug_mode) { + curl_setopt($curl_handle, CURLOPT_VERBOSE, true); + } + + // Handle open_basedir & safe mode + if (!ini_get('safe_mode') && !ini_get('open_basedir')) { + curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true); + } + + // Enable a proxy connection if requested. + if ($this->proxy) { + + $host = $this->proxy['host']; + $host .= ($this->proxy['port']) ? ':' . $this->proxy['port'] : ''; + curl_setopt($curl_handle, CURLOPT_PROXY, $host); + + if (isset($this->proxy['user']) && isset($this->proxy['pass'])) { + curl_setopt($curl_handle, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']); + } + } + + // Set credentials for HTTP Basic/Digest Authentication. + if ($this->username && $this->password) { + curl_setopt($curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_setopt($curl_handle, CURLOPT_USERPWD, $this->username . ':' . $this->password); + } + + // Handle the encoding if we can. + if (extension_loaded('zlib')) { + curl_setopt($curl_handle, CURLOPT_ENCODING, ''); + } + + // Process custom headers + if (isset($this->request_headers) && count($this->request_headers)) { + $temp_headers = array(); + + foreach ($this->request_headers as $k => $v) { + $temp_headers[] = $k . ': ' . $v; + } + + curl_setopt($curl_handle, CURLOPT_HTTPHEADER, $temp_headers); + } + + switch ($this->method) { + case self::HTTP_PUT: + //unset($this->read_stream); + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'PUT'); + if (isset($this->read_stream)) { + if (!isset($this->read_stream_size) || $this->read_stream_size < 0) { + throw new RequestCore_Exception('The stream size for the streaming upload cannot be determined.'); + } + curl_setopt($curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size); + curl_setopt($curl_handle, CURLOPT_UPLOAD, true); + } else { + curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body); + } + break; + + case self::HTTP_POST: + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'POST'); + if (isset($this->read_stream)) { + if (!isset($this->read_stream_size) || $this->read_stream_size < 0) { + throw new RequestCore_Exception('The stream size for the streaming upload cannot be determined.'); + } + curl_setopt($curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size); + curl_setopt($curl_handle, CURLOPT_UPLOAD, true); + } else { + curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body); + } + break; + + case self::HTTP_HEAD: + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, self::HTTP_HEAD); + curl_setopt($curl_handle, CURLOPT_NOBODY, 1); + break; + + default: // Assumed GET + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, $this->method); + if (isset($this->write_stream) || isset($this->write_file)) { + curl_setopt($curl_handle, CURLOPT_WRITEFUNCTION, array($this, 'streaming_write_callback')); + curl_setopt($curl_handle, CURLOPT_HEADER, false); + } else { + curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body); + } + break; + } + + // Merge in the CURLOPTs + if (isset($this->curlopts) && sizeof($this->curlopts) > 0) { + foreach ($this->curlopts as $k => $v) { + curl_setopt($curl_handle, $k, $v); + } + } + + return $curl_handle; + } + + /** + * Take the post-processed cURL data and break it down into useful header/body/info chunks. Uses the + * data stored in the `curl_handle` and `response` properties unless replacement data is passed in via + * parameters. + * + * @param resource $curl_handle (Optional) The reference to the already executed cURL request. + * @param string $response (Optional) The actual response content itself that needs to be parsed. + * @return ResponseCore A object containing a parsed HTTP response. + */ + public function process_response($curl_handle = null, $response = null) + { + // Accept a custom one if it's passed. + if ($curl_handle && $response) { + $this->response = $response; + } + + // As long as this came back as a valid resource... + if (is_resource($curl_handle)) { + // Determine what's what. + $header_size = curl_getinfo($curl_handle, CURLINFO_HEADER_SIZE); + $this->response_headers = substr($this->response, 0, $header_size); + $this->response_body = substr($this->response, $header_size); + $this->response_code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); + $this->response_info = curl_getinfo($curl_handle); + + if (intval($this->response_code) / 100 != 2 && isset($this->write_file)) + { + $this->response_headers = $this->response_raw_headers; + $this->response_body = $this->response_error_body; + } + + // Parse out the headers + $this->response_headers = explode("\r\n\r\n", trim($this->response_headers)); + $this->response_headers = array_pop($this->response_headers); + $this->response_headers = explode("\r\n", $this->response_headers); + array_shift($this->response_headers); + + // Loop through and split up the headers. + $header_assoc = array(); + foreach ($this->response_headers as $header) { + $kv = explode(': ', $header); + $header_assoc[strtolower($kv[0])] = isset($kv[1]) ? $kv[1] : ''; + } + + // Reset the headers to the appropriate property. + $this->response_headers = $header_assoc; + $this->response_headers['info'] = $this->response_info; + $this->response_headers['info']['method'] = $this->method; + + if ($curl_handle && $response) { + return new ResponseCore($this->response_headers, $this->response_body, $this->response_code); + } + } + + // Return false + return false; + } + + /** + * Sends the request, calling necessary utility functions to update built-in properties. + * + * @param boolean $parse (Optional) Whether to parse the response with ResponseCore or not. + * @return string The resulting unparsed data from the request. + */ + public function send_request($parse = false) + { + set_time_limit(0); + + $curl_handle = $this->prep_request(); + $this->response = curl_exec($curl_handle); + + if ($this->response === false) { + throw new RequestCore_Exception('cURL resource: ' . (string)$curl_handle . '; cURL error: ' . curl_error($curl_handle) . ' (' . curl_errno($curl_handle) . ')'); + } + + $parsed_response = $this->process_response($curl_handle, $this->response); + + curl_close($curl_handle); + + if ($parse) { + return $parsed_response; + } + + return $this->response; + } + + /*%******************************************************************************************%*/ + // RESPONSE METHODS + + /** + * Get the HTTP response headers from the request. + * + * @param string $header (Optional) A specific header value to return. Defaults to all headers. + * @return string|array All or selected header values. + */ + public function get_response_header($header = null) + { + if ($header) { + return $this->response_headers[strtolower($header)]; + } + return $this->response_headers; + } + + /** + * Get the HTTP response body from the request. + * + * @return string The response body. + */ + public function get_response_body() + { + return $this->response_body; + } + + /** + * Get the HTTP response code from the request. + * + * @return string The HTTP response code. + */ + public function get_response_code() + { + return $this->response_code; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/RequestCore_Exception.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/RequestCore_Exception.php new file mode 100644 index 0000000..cb4e83c --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Http/RequestCore_Exception.php @@ -0,0 +1,8 @@ +). + * @param string $body (Required) XML-formatted response from AWS. + * @param integer $status (Optional) HTTP response status code from the request. + * @return Mixed Contains an `header` property (HTTP headers as an associative array), a or `body` property, and an `status` code. + */ + public function __construct($header, $body, $status = null) + { + $this->header = $header; + $this->body = $body; + $this->status = $status; + + return $this; + } + + /** + * Did we receive the status code we expected? + * + * @param integer|array $codes (Optional) The status code(s) to expect. Pass an for a single acceptable value, or an of integers for multiple acceptable values. + * @return boolean Whether we received the expected status code or not. + */ + public function isOK($codes = array(200, 201, 204, 206)) + { + if (is_array($codes)) { + return in_array($this->status, $codes); + } + + return $this->status === $codes; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/BucketInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/BucketInfo.php new file mode 100644 index 0000000..9b89674 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/BucketInfo.php @@ -0,0 +1,78 @@ +location = $location; + $this->name = $name; + $this->createDate = $createDate; + } + + /** + * 得到bucket所在的region + * + * @return string + */ + public function getLocation() + { + return $this->location; + } + + /** + * 得到bucket的名称 + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 得到bucket的创建时间 + * + * @return string + */ + public function getCreateDate() + { + return $this->createDate; + } + + /** + * bucket所在的region + * + * @var string + */ + private $location; + /** + * bucket的名称 + * + * @var string + */ + private $name; + + /** + * bucket的创建事件 + * + * @var string + */ + private $createDate; + +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/BucketListInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/BucketListInfo.php new file mode 100644 index 0000000..910717f --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/BucketListInfo.php @@ -0,0 +1,39 @@ +bucketList = $bucketList; + } + + /** + * 得到BucketInfo列表 + * + * @return BucketInfo[] + */ + public function getBucketList() + { + return $this->bucketList; + } + + /** + * BucketInfo信息列表 + * + * @var array + */ + private $bucketList = array(); +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CnameConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CnameConfig.php new file mode 100644 index 0000000..f3597d2 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CnameConfig.php @@ -0,0 +1,99 @@ +cnameList = array(); + } + + /** + * @return array + * @example + * array(2) { + * [0]=> + * array(3) { + * ["Domain"]=> + * string(11) "www.foo.com" + * ["Status"]=> + * string(7) "enabled" + * ["LastModified"]=> + * string(8) "20150101" + * } + * [1]=> + * array(3) { + * ["Domain"]=> + * string(7) "bar.com" + * ["Status"]=> + * string(8) "disabled" + * ["LastModified"]=> + * string(8) "20160101" + * } + * } + */ + public function getCnames() + { + return $this->cnameList; + } + + + public function addCname($cname) + { + if (count($this->cnameList) >= self::OSS_MAX_RULES) { + throw new OssException( + "num of cname in the config exceeds self::OSS_MAX_RULES: " . strval(self::OSS_MAX_RULES)); + } + $this->cnameList[] = array('Domain' => $cname); + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + if (!isset($xml->Cname)) return; + foreach ($xml->Cname as $entry) { + $cname = array(); + foreach ($entry as $key => $value) { + $cname[strval($key)] = strval($value); + } + $this->cnameList[] = $cname; + } + } + + public function serializeToXml() + { + $strXml = << + + +EOF; + $xml = new \SimpleXMLElement($strXml); + foreach ($this->cnameList as $cname) { + $node = $xml->addChild('Cname'); + foreach ($cname as $key => $value) { + $node->addChild($key, $value); + } + } + return $xml->asXML(); + } + + public function __toString() + { + return $this->serializeToXml(); + } + + const OSS_MAX_RULES = 10; + + private $cnameList = array(); +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CorsConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CorsConfig.php new file mode 100644 index 0000000..c44c10a --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CorsConfig.php @@ -0,0 +1,113 @@ +rules = array(); + } + + /** + * 得到CorsRule列表 + * + * @return CorsRule[] + */ + public function getRules() + { + return $this->rules; + } + + + /** + * 添加一条CorsRule + * + * @param CorsRule $rule + * @throws OssException + */ + public function addRule($rule) + { + if (count($this->rules) >= self::OSS_MAX_RULES) { + throw new OssException("num of rules in the config exceeds self::OSS_MAX_RULES: " . strval(self::OSS_MAX_RULES)); + } + $this->rules[] = $rule; + } + + /** + * 从xml数据中解析出CorsConfig + * + * @param string $strXml + * @throws OssException + * @return null + */ + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + if (!isset($xml->CORSRule)) return; + foreach ($xml->CORSRule as $rule) { + $corsRule = new CorsRule(); + foreach ($rule as $key => $value) { + if ($key === self::OSS_CORS_ALLOWED_HEADER) { + $corsRule->addAllowedHeader(strval($value)); + } elseif ($key === self::OSS_CORS_ALLOWED_METHOD) { + $corsRule->addAllowedMethod(strval($value)); + } elseif ($key === self::OSS_CORS_ALLOWED_ORIGIN) { + $corsRule->addAllowedOrigin(strval($value)); + } elseif ($key === self::OSS_CORS_EXPOSE_HEADER) { + $corsRule->addExposeHeader(strval($value)); + } elseif ($key === self::OSS_CORS_MAX_AGE_SECONDS) { + $corsRule->setMaxAgeSeconds(strval($value)); + } + } + $this->addRule($corsRule); + } + return; + } + + /** + * 生成xml字符串 + * + * @return string + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + foreach ($this->rules as $rule) { + $xmlRule = $xml->addChild('CORSRule'); + $rule->appendToXml($xmlRule); + } + return $xml->asXML(); + } + + public function __toString() + { + return $this->serializeToXml(); + } + + const OSS_CORS_ALLOWED_ORIGIN = 'AllowedOrigin'; + const OSS_CORS_ALLOWED_METHOD = 'AllowedMethod'; + const OSS_CORS_ALLOWED_HEADER = 'AllowedHeader'; + const OSS_CORS_EXPOSE_HEADER = 'ExposeHeader'; + const OSS_CORS_MAX_AGE_SECONDS = 'MaxAgeSeconds'; + const OSS_MAX_RULES = 10; + + /** + * orsRule列表 + * + * @var CorsRule[] + */ + private $rules = array(); +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CorsRule.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CorsRule.php new file mode 100644 index 0000000..2cbe1c1 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/CorsRule.php @@ -0,0 +1,150 @@ +allowedOrigins[] = $allowedOrigin; + } + } + + /** + * Rule中增加一条allowedMethod + * + * @param string $allowedMethod + */ + public function addAllowedMethod($allowedMethod) + { + if (!empty($allowedMethod)) { + $this->allowedMethods[] = $allowedMethod; + } + } + + /** + * Rule中增加一条allowedHeader + * + * @param string $allowedHeader + */ + public function addAllowedHeader($allowedHeader) + { + if (!empty($allowedHeader)) { + $this->allowedHeaders[] = $allowedHeader; + } + } + + /** + * Rule中增加一条exposeHeader + * + * @param string $exposeHeader + */ + public function addExposeHeader($exposeHeader) + { + if (!empty($exposeHeader)) { + $this->exposeHeaders[] = $exposeHeader; + } + } + + /** + * @return int + */ + public function getMaxAgeSeconds() + { + return $this->maxAgeSeconds; + } + + /** + * @param int $maxAgeSeconds + */ + public function setMaxAgeSeconds($maxAgeSeconds) + { + $this->maxAgeSeconds = $maxAgeSeconds; + } + + /** + * 得到AllowedHeaders列表 + * + * @return string[] + */ + public function getAllowedHeaders() + { + return $this->allowedHeaders; + } + + /** + * 得到AllowedOrigins列表 + * + * @return string[] + */ + public function getAllowedOrigins() + { + return $this->allowedOrigins; + } + + /** + * 得到AllowedMethods列表 + * + * @return string[] + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } + + /** + * 得到ExposeHeaders列表 + * + * @return string[] + */ + public function getExposeHeaders() + { + return $this->exposeHeaders; + } + + /** + * 根据提供的xmlRule, 把this按照一定的规则插入到$xmlRule中 + * + * @param \SimpleXMLElement $xmlRule + * @throws OssException + */ + public function appendToXml(&$xmlRule) + { + if (!isset($this->maxAgeSeconds)) { + throw new OssException("maxAgeSeconds is not set in the Rule"); + } + foreach ($this->allowedOrigins as $allowedOrigin) { + $xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_ORIGIN, $allowedOrigin); + } + foreach ($this->allowedMethods as $allowedMethod) { + $xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_METHOD, $allowedMethod); + } + foreach ($this->allowedHeaders as $allowedHeader) { + $xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_HEADER, $allowedHeader); + } + foreach ($this->exposeHeaders as $exposeHeader) { + $xmlRule->addChild(CorsConfig::OSS_CORS_EXPOSE_HEADER, $exposeHeader); + } + $xmlRule->addChild(CorsConfig::OSS_CORS_MAX_AGE_SECONDS, strval($this->maxAgeSeconds)); + } + + private $allowedHeaders = array(); + private $allowedOrigins = array(); + private $allowedMethods = array(); + private $exposeHeaders = array(); + private $maxAgeSeconds = null; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelHistory.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelHistory.php new file mode 100644 index 0000000..6643444 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelHistory.php @@ -0,0 +1,34 @@ +liveRecordList; + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + + if (isset($xml->LiveRecord)) { + foreach ($xml->LiveRecord as $record) { + $liveRecord = new LiveChannelHistory(); + $liveRecord->parseFromXmlNode($record); + $this->liveRecordList[] = $liveRecord; + } + } + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $liveRecordList = array(); +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelInfo.php new file mode 100644 index 0000000..0b5edfc --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelInfo.php @@ -0,0 +1,68 @@ +description; + } + + public function getStatus() + { + return $this->status; + } + + public function getType() + { + return $this->type; + } + + public function getFragDuration() + { + return $this->fragDuration; + } + + public function getFragCount() + { + return $this->fragCount; + } + + public function getPlayListName() + { + return $this->playlistName; + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + + $this->description = strval($xml->Description); + $this->status = strval($xml->Status); + + if (isset($xml->Target)) { + foreach ($xml->Target as $target) { + $this->type = strval($target->Type); + $this->fragDuration = strval($target->FragDuration); + $this->fragCount = strval($target->FragCount); + $this->playlistName = strval($target->PlaylistName); + } + } + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $description; + private $status; + private $type; + private $fragDuration; + private $fragCount; + private $playlistName; +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelStatus.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelStatus.php new file mode 100644 index 0000000..2ee7a68 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/GetLiveChannelStatus.php @@ -0,0 +1,107 @@ +status; + } + + public function getConnectedTime() + { + return $this->connectedTime; + } + + public function getRemoteAddr() + { + return $this->remoteAddr; + } + + public function getVideoWidth() + { + return $this->videoWidth; + } + public function getVideoHeight() + { + return $this->videoHeight; + } + public function getVideoFrameRate() + { + return $this->videoFrameRate; + } + public function getVideoBandwidth() + { + return $this->videoBandwidth; + } + public function getVideoCodec() + { + return $this->videoCodec; + } + + public function getAudioBandwidth() + { + return $this->audioBandwidth; + } + public function getAudioSampleRate() + { + return $this->audioSampleRate; + } + public function getAudioCodec() + { + return $this->audioCodec; + } + + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + $this->status = strval($xml->Status); + $this->connectedTime = strval($xml->ConnectedTime); + $this->remoteAddr = strval($xml->RemoteAddr); + + if (isset($xml->Video)) { + foreach ($xml->Video as $video) { + $this->videoWidth = intval($video->Width); + $this->videoHeight = intval($video->Height); + $this->videoFrameRate = intval($video->FrameRate); + $this->videoBandwidth = intval($video->Bandwidth); + $this->videoCodec = strval($video->Codec); + } + } + + if (isset($xml->Video)) { + foreach ($xml->Audio as $audio) { + $this->audioBandwidth = intval($audio->Bandwidth); + $this->audioSampleRate = intval($audio->SampleRate); + $this->audioCodec = strval($audio->Codec); + } + } + + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $status; + private $connectedTime; + private $remoteAddr; + + private $videoWidth; + private $videoHeight; + private $videoFrameRate; + private $videoBandwidth; + private $videoCodec; + + private $audioBandwidth; + private $audioSampleRate; + private $audioCodec; + + +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleAction.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleAction.php new file mode 100644 index 0000000..5abd825 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleAction.php @@ -0,0 +1,88 @@ +action = $action; + $this->timeSpec = $timeSpec; + $this->timeValue = $timeValue; + } + + /** + * @return LifecycleAction + */ + public function getAction() + { + return $this->action; + } + + /** + * @param string $action + */ + public function setAction($action) + { + $this->action = $action; + } + + /** + * @return string + */ + public function getTimeSpec() + { + return $this->timeSpec; + } + + /** + * @param string $timeSpec + */ + public function setTimeSpec($timeSpec) + { + $this->timeSpec = $timeSpec; + } + + /** + * @return string + */ + public function getTimeValue() + { + return $this->timeValue; + } + + /** + * @param string $timeValue + */ + public function setTimeValue($timeValue) + { + $this->timeValue = $timeValue; + } + + /** + * appendToXml 把actions插入到xml中 + * + * @param \SimpleXMLElement $xmlRule + */ + public function appendToXml(&$xmlRule) + { + $xmlAction = $xmlRule->addChild($this->action); + $xmlAction->addChild($this->timeSpec, $this->timeValue); + } + + private $action; + private $timeSpec; + private $timeValue; + +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleConfig.php new file mode 100644 index 0000000..fc4f575 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleConfig.php @@ -0,0 +1,107 @@ +rules = array(); + $xml = simplexml_load_string($strXml); + if (!isset($xml->Rule)) return; + $this->rules = array(); + foreach ($xml->Rule as $rule) { + $id = strval($rule->ID); + $prefix = strval($rule->Prefix); + $status = strval($rule->Status); + $actions = array(); + foreach ($rule as $key => $value) { + if ($key === 'ID' || $key === 'Prefix' || $key === 'Status') continue; + $action = $key; + $timeSpec = null; + $timeValue = null; + foreach ($value as $timeSpecKey => $timeValueValue) { + $timeSpec = $timeSpecKey; + $timeValue = strval($timeValueValue); + } + $actions[] = new LifecycleAction($action, $timeSpec, $timeValue); + } + $this->rules[] = new LifecycleRule($id, $prefix, $status, $actions); + } + return; + } + + + /** + * 生成xml字符串 + * + * @return string + */ + public function serializeToXml() + { + + $xml = new \SimpleXMLElement(''); + foreach ($this->rules as $rule) { + $xmlRule = $xml->addChild('Rule'); + $rule->appendToXml($xmlRule); + } + return $xml->asXML(); + } + + /** + * + * 添加LifecycleRule + * + * @param LifecycleRule $lifecycleRule + * @throws OssException + */ + public function addRule($lifecycleRule) + { + if (!isset($lifecycleRule)) { + throw new OssException("lifecycleRule is null"); + } + $this->rules[] = $lifecycleRule; + } + + /** + * 将配置转换成字符串,便于用户查看 + * + * @return string + */ + public function __toString() + { + return $this->serializeToXml(); + } + + /** + * 得到所有的生命周期规则 + * + * @return LifecycleRule[] + */ + public function getRules() + { + return $this->rules; + } + + /** + * @var LifecycleRule[] + */ + private $rules; +} + + diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleRule.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleRule.php new file mode 100644 index 0000000..ec615b9 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LifecycleRule.php @@ -0,0 +1,126 @@ +id; + } + + /** + * @param string $id 规则ID + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * 得到文件前缀 + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * 设置文件前缀 + * + * @param string $prefix 文件前缀 + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Lifecycle规则的状态 + * + * @return string + */ + public function getStatus() + { + return $this->status; + } + + /** + * 设置Lifecycle规则状态 + * + * @param string $status + */ + public function setStatus($status) + { + $this->status = $status; + } + + /** + * + * @return LifecycleAction[] + */ + public function getActions() + { + return $this->actions; + } + + /** + * @param LifecycleAction[] $actions + */ + public function setActions($actions) + { + $this->actions = $actions; + } + + + /** + * LifecycleRule constructor. + * + * @param string $id 规则ID + * @param string $prefix 文件前缀 + * @param string $status 规则状态,可选[self::LIFECYCLE_STATUS_ENABLED, self::LIFECYCLE_STATUS_DISABLED] + * @param LifecycleAction[] $actions + */ + public function __construct($id, $prefix, $status, $actions) + { + $this->id = $id; + $this->prefix = $prefix; + $this->status = $status; + $this->actions = $actions; + } + + /** + * @param \SimpleXMLElement $xmlRule + */ + public function appendToXml(&$xmlRule) + { + $xmlRule->addChild('ID', $this->id); + $xmlRule->addChild('Prefix', $this->prefix); + $xmlRule->addChild('Status', $this->status); + foreach ($this->actions as $action) { + $action->appendToXml($xmlRule); + } + } + + private $id; + private $prefix; + private $status; + private $actions = array(); + + const LIFECYCLE_STATUS_ENABLED = 'Enabled'; + const LIFECYCLE_STATUS_DISABLED = 'Disabled'; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ListMultipartUploadInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ListMultipartUploadInfo.php new file mode 100644 index 0000000..105d005 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ListMultipartUploadInfo.php @@ -0,0 +1,134 @@ +bucket = $bucket; + $this->keyMarker = $keyMarker; + $this->uploadIdMarker = $uploadIdMarker; + $this->nextKeyMarker = $nextKeyMarker; + $this->nextUploadIdMarker = $nextUploadIdMarker; + $this->delimiter = $delimiter; + $this->prefix = $prefix; + $this->maxUploads = $maxUploads; + $this->isTruncated = $isTruncated; + $this->uploads = $uploads; + } + + /** + * 得到bucket名称 + * + * @return string + */ + public function getBucket() + { + return $this->bucket; + } + + /** + * @return string + */ + public function getKeyMarker() + { + return $this->keyMarker; + } + + /** + * + * @return string + */ + public function getUploadIdMarker() + { + return $this->uploadIdMarker; + } + + /** + * @return string + */ + public function getNextKeyMarker() + { + return $this->nextKeyMarker; + } + + /** + * @return string + */ + public function getNextUploadIdMarker() + { + return $this->nextUploadIdMarker; + } + + /** + * @return string + */ + public function getDelimiter() + { + return $this->delimiter; + } + + /** + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return int + */ + public function getMaxUploads() + { + return $this->maxUploads; + } + + /** + * @return string + */ + public function getIsTruncated() + { + return $this->isTruncated; + } + + /** + * @return UploadInfo[] + */ + public function getUploads() + { + return $this->uploads; + } + + private $bucket = ""; + private $keyMarker = ""; + private $uploadIdMarker = ""; + private $nextKeyMarker = ""; + private $nextUploadIdMarker = ""; + private $delimiter = ""; + private $prefix = ""; + private $maxUploads = 0; + private $isTruncated = "false"; + private $uploads = array(); +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ListPartsInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ListPartsInfo.php new file mode 100644 index 0000000..f1d10ee --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ListPartsInfo.php @@ -0,0 +1,97 @@ +bucket = $bucket; + $this->key = $key; + $this->uploadId = $uploadId; + $this->nextPartNumberMarker = $nextPartNumberMarker; + $this->maxParts = $maxParts; + $this->isTruncated = $isTruncated; + $this->listPart = $listPart; + } + + /** + * @return string + */ + public function getBucket() + { + return $this->bucket; + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string + */ + public function getUploadId() + { + return $this->uploadId; + } + + /** + * @return int + */ + public function getNextPartNumberMarker() + { + return $this->nextPartNumberMarker; + } + + /** + * @return int + */ + public function getMaxParts() + { + return $this->maxParts; + } + + /** + * @return string + */ + public function getIsTruncated() + { + return $this->isTruncated; + } + + /** + * @return array + */ + public function getListPart() + { + return $this->listPart; + } + + private $bucket = ""; + private $key = ""; + private $uploadId = ""; + private $nextPartNumberMarker = 0; + private $maxParts = 0; + private $isTruncated = ""; + private $listPart = array(); +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelConfig.php new file mode 100644 index 0000000..dadedc9 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelConfig.php @@ -0,0 +1,121 @@ +description = $option['description']; + } + if (isset($option['status'])) { + $this->status = $option['status']; + } + if (isset($option['type'])) { + $this->type = $option['type']; + } + if (isset($option['fragDuration'])) { + $this->fragDuration = $option['fragDuration']; + } + if (isset($option['fragCount'])) { + $this->fragCount = $option['fragCount']; + } + if (isset($option['playListName'])) { + $this->playListName = $option['playListName']; + } + } + + public function getDescription() + { + return $this->description; + } + + public function getStatus() + { + return $this->status; + } + + public function getType() + { + return $this->type; + } + + public function getFragDuration() + { + return $this->fragDuration; + } + + public function getFragCount() + { + return $this->fragCount; + } + + public function getPlayListName() + { + return $this->playListName; + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + $this->description = strval($xml->Description); + $this->status = strval($xml->Status); + $target = $xml->Target; + $this->type = strval($target->Type); + $this->fragDuration = intval($target->FragDuration); + $this->fragCount = intval($target->FragCount); + $this->playListName = strval($target->PlayListName); + } + + public function serializeToXml() + { + $strXml = << + + +EOF; + $xml = new \SimpleXMLElement($strXml); + if (isset($this->description)) { + $xml->addChild('Description', $this->description); + } + + if (isset($this->status)) { + $xml->addChild('Status', $this->status); + } + + $node = $xml->addChild('Target'); + $node->addChild('Type', $this->type); + + if (isset($this->fragDuration)) { + $node->addChild('FragDuration', $this->fragDuration); + } + + if (isset($this->fragCount)) { + $node->addChild('FragCount', $this->fragCount); + } + + if (isset($this->playListName)) { + $node->addChild('PlayListName', $this->playListName); + } + + return $xml->asXML(); + } + + public function __toString() + { + return $this->serializeToXml(); + } + + private $description; + private $status = "enabled"; + private $type; + private $fragDuration = 5; + private $fragCount = 3; + private $playListName = "playlist.m3u8"; +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelHistory.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelHistory.php new file mode 100644 index 0000000..1c1fd4d --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelHistory.php @@ -0,0 +1,59 @@ +startTime; + } + + public function getEndTime() + { + return $this->endTime; + } + + public function getRemoteAddr() + { + return $this->remoteAddr; + } + + public function parseFromXmlNode($xml) + { + if (isset($xml->StartTime)) { + $this->startTime = strval($xml->StartTime); + } + + if (isset($xml->EndTime)) { + $this->endTime = strval($xml->EndTime); + } + + if (isset($xml->RemoteAddr)) { + $this->remoteAddr = strval($xml->RemoteAddr); + } + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + $this->parseFromXmlNode($xml); + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $startTime; + private $endTime; + private $remoteAddr; +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelInfo.php new file mode 100644 index 0000000..c63ec54 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelInfo.php @@ -0,0 +1,107 @@ +name = $name; + $this->description = $description; + $this->publishUrls = array(); + $this->playUrls = array(); + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getPublishUrls() + { + return $this->publishUrls; + } + + public function getPlayUrls() + { + return $this->playUrls; + } + + public function getStatus() + { + return $this->status; + } + + public function getLastModified() + { + return $this->lastModified; + } + + public function getDescription() + { + return $this->description; + } + + public function setDescription($description) + { + $this->description = $description; + } + + public function parseFromXmlNode($xml) + { + if (isset($xml->Name)) { + $this->name = strval($xml->Name); + } + + if (isset($xml->Description)) { + $this->description = strval($xml->Description); + } + + if (isset($xml->Status)) { + $this->status = strval($xml->Status); + } + + if (isset($xml->LastModified)) { + $this->lastModified = strval($xml->LastModified); + } + + if (isset($xml->PublishUrls)) { + foreach ($xml->PublishUrls as $url) { + $this->publishUrls[] = strval($url->Url); + } + } + + if (isset($xml->PlayUrls)) { + foreach ($xml->PlayUrls as $url) { + $this->playUrls[] = strval($url->Url); + } + } + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + $this->parseFromXmlNode($xml); + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $name; + private $description; + private $publishUrls; + private $playUrls; + private $status; + private $lastModified; +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelListInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelListInfo.php new file mode 100644 index 0000000..bb5093a --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LiveChannelListInfo.php @@ -0,0 +1,107 @@ +bucket; + } + + public function setBucketName($name) + { + $this->bucket = $name; + } + + /** + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return string + */ + public function getMarker() + { + return $this->marker; + } + + /** + * @return int + */ + public function getMaxKeys() + { + return $this->maxKeys; + } + + /** + * @return mixed + */ + public function getIsTruncated() + { + return $this->isTruncated; + } + + /** + * @return LiveChannelInfo[] + */ + public function getChannelList() + { + return $this->channelList; + } + + /** + * @return string + */ + public function getNextMarker() + { + return $this->nextMarker; + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + + $this->prefix = strval($xml->Prefix); + $this->marker = strval($xml->Marker); + $this->maxKeys = intval($xml->MaxKeys); + $this->isTruncated = (strval($xml->IsTruncated) == 'true'); + $this->nextMarker = strval($xml->NextMarker); + + if (isset($xml->LiveChannel)) { + foreach ($xml->LiveChannel as $chan) { + $channel = new LiveChannelInfo(); + $channel->parseFromXmlNode($chan); + $this->channelList[] = $channel; + } + } + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $bucket = ''; + private $prefix = ''; + private $marker = ''; + private $nextMarker = ''; + private $maxKeys = 100; + private $isTruncated = 'false'; + private $channelList = array(); +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LoggingConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LoggingConfig.php new file mode 100644 index 0000000..978421a --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/LoggingConfig.php @@ -0,0 +1,86 @@ +targetBucket = $targetBucket; + $this->targetPrefix = $targetPrefix; + } + + /** + * @param $strXml + * @return null + */ + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + if (!isset($xml->LoggingEnabled)) return; + foreach ($xml->LoggingEnabled as $status) { + foreach ($status as $key => $value) { + if ($key === 'TargetBucket') { + $this->targetBucket = strval($value); + } elseif ($key === 'TargetPrefix') { + $this->targetPrefix = strval($value); + } + } + break; + } + } + + /** + * 序列化成xml字符串 + * + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + if (isset($this->targetBucket) && isset($this->targetPrefix)) { + $loggingEnabled = $xml->addChild('LoggingEnabled'); + $loggingEnabled->addChild('TargetBucket', $this->targetBucket); + $loggingEnabled->addChild('TargetPrefix', $this->targetPrefix); + } + return $xml->asXML(); + } + + /** + * @return string + */ + public function __toString() + { + return $this->serializeToXml(); + } + + /** + * @return string + */ + public function getTargetBucket() + { + return $this->targetBucket; + } + + /** + * @return string + */ + public function getTargetPrefix() + { + return $this->targetPrefix; + } + + private $targetBucket = ""; + private $targetPrefix = ""; + +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ObjectInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ObjectInfo.php new file mode 100644 index 0000000..2ae6c99 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ObjectInfo.php @@ -0,0 +1,93 @@ +key = $key; + $this->lastModified = $lastModified; + $this->eTag = $eTag; + $this->type = $type; + $this->size = $size; + $this->storageClass = $storageClass; + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string + */ + public function getLastModified() + { + return $this->lastModified; + } + + /** + * @return string + */ + public function getETag() + { + return $this->eTag; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getSize() + { + return $this->size; + } + + /** + * @return string + */ + public function getStorageClass() + { + return $this->storageClass; + } + + private $key = ""; + private $lastModified = ""; + private $eTag = ""; + private $type = ""; + private $size = 0; + private $storageClass = ""; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ObjectListInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ObjectListInfo.php new file mode 100644 index 0000000..dbe7c7a --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/ObjectListInfo.php @@ -0,0 +1,126 @@ +bucketName = $bucketName; + $this->prefix = $prefix; + $this->marker = $marker; + $this->nextMarker = $nextMarker; + $this->maxKeys = $maxKeys; + $this->delimiter = $delimiter; + $this->isTruncated = $isTruncated; + $this->objectList = $objectList; + $this->prefixList = $prefixList; + } + + /** + * @return string + */ + public function getBucketName() + { + return $this->bucketName; + } + + /** + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return string + */ + public function getMarker() + { + return $this->marker; + } + + /** + * @return int + */ + public function getMaxKeys() + { + return $this->maxKeys; + } + + /** + * @return string + */ + public function getDelimiter() + { + return $this->delimiter; + } + + /** + * @return mixed + */ + public function getIsTruncated() + { + return $this->isTruncated; + } + + /** + * 返回ListObjects接口返回数据中的ObjectInfo列表 + * + * @return ObjectInfo[] + */ + public function getObjectList() + { + return $this->objectList; + } + + /** + * 返回ListObjects接口返回数据中的PrefixInfo列表 + * + * @return PrefixInfo[] + */ + public function getPrefixList() + { + return $this->prefixList; + } + + /** + * @return string + */ + public function getNextMarker() + { + return $this->nextMarker; + } + + private $bucketName = ""; + private $prefix = ""; + private $marker = ""; + private $nextMarker = ""; + private $maxKeys = 0; + private $delimiter = ""; + private $isTruncated = null; + private $objectList = array(); + private $prefixList = array(); +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/PartInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/PartInfo.php new file mode 100644 index 0000000..439a84d --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/PartInfo.php @@ -0,0 +1,63 @@ +partNumber = $partNumber; + $this->lastModified = $lastModified; + $this->eTag = $eTag; + $this->size = $size; + } + + /** + * @return int + */ + public function getPartNumber() + { + return $this->partNumber; + } + + /** + * @return string + */ + public function getLastModified() + { + return $this->lastModified; + } + + /** + * @return string + */ + public function getETag() + { + return $this->eTag; + } + + /** + * @return int + */ + public function getSize() + { + return $this->size; + } + + private $partNumber = 0; + private $lastModified = ""; + private $eTag = ""; + private $size = 0; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/PrefixInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/PrefixInfo.php new file mode 100644 index 0000000..e61eac4 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/PrefixInfo.php @@ -0,0 +1,36 @@ +prefix = $prefix; + } + + /** + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + private $prefix; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/RefererConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/RefererConfig.php new file mode 100644 index 0000000..1d7d975 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/RefererConfig.php @@ -0,0 +1,93 @@ +AllowEmptyReferer)) return; + if (!isset($xml->RefererList)) return; + $this->allowEmptyReferer = + (strval($xml->AllowEmptyReferer) === 'TRUE' || strval($xml->AllowEmptyReferer) === 'true') ? true : false; + + foreach ($xml->RefererList->Referer as $key => $refer) { + $this->refererList[] = strval($refer); + } + } + + + /** + * 把RefererConfig序列化成xml + * + * @return string + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + if ($this->allowEmptyReferer) { + $xml->addChild('AllowEmptyReferer', 'true'); + } else { + $xml->addChild('AllowEmptyReferer', 'false'); + } + $refererList = $xml->addChild('RefererList'); + foreach ($this->refererList as $referer) { + $refererList->addChild('Referer', $referer); + } + return $xml->asXML(); + } + + /** + * @return string + */ + function __toString() + { + return $this->serializeToXml(); + } + + /** + * @param boolean $allowEmptyReferer + */ + public function setAllowEmptyReferer($allowEmptyReferer) + { + $this->allowEmptyReferer = $allowEmptyReferer; + } + + /** + * @param string $referer + */ + public function addReferer($referer) + { + $this->refererList[] = $referer; + } + + /** + * @return boolean + */ + public function isAllowEmptyReferer() + { + return $this->allowEmptyReferer; + } + + /** + * @return array + */ + public function getRefererList() + { + return $this->refererList; + } + + private $allowEmptyReferer = true; + private $refererList = array(); +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/StorageCapacityConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/StorageCapacityConfig.php new file mode 100644 index 0000000..05e6332 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/StorageCapacityConfig.php @@ -0,0 +1,74 @@ +storageCapacity = $storageCapacity; + } + + /** + * Not implemented + */ + public function parseFromXml($strXml) + { + throw new OssException("Not implemented."); + } + + /** + * 把StorageCapacityConfig序列化成xml + * + * @return string + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + $xml->addChild('StorageCapacity', strval($this->storageCapacity)); + return $xml->asXML(); + } + + /** + * To string + * + * @return string + */ + function __toString() + { + return $this->serializeToXml(); + } + + /** + * Set storage capacity + * + * @param int $storageCapacity + */ + public function setStorageCapacity($storageCapacity) + { + $this->storageCapacity = $storageCapacity; + } + + /** + * Get storage capacity + * + * @return int + */ + public function getStorageCapacity() + { + return $this->storageCapacity; + } + + private $storageCapacity = 0; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/UploadInfo.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/UploadInfo.php new file mode 100644 index 0000000..8eaa363 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/UploadInfo.php @@ -0,0 +1,55 @@ +key = $key; + $this->uploadId = $uploadId; + $this->initiated = $initiated; + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string + */ + public function getUploadId() + { + return $this->uploadId; + } + + /** + * @return string + */ + public function getInitiated() + { + return $this->initiated; + } + + private $key = ""; + private $uploadId = ""; + private $initiated = ""; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/WebsiteConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/WebsiteConfig.php new file mode 100644 index 0000000..8ea08a0 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/WebsiteConfig.php @@ -0,0 +1,76 @@ +indexDocument = $indexDocument; + $this->errorDocument = $errorDocument; + } + + /** + * @param string $strXml + * @return null + */ + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + if (isset($xml->IndexDocument) && isset($xml->IndexDocument->Suffix)) { + $this->indexDocument = strval($xml->IndexDocument->Suffix); + } + if (isset($xml->ErrorDocument) && isset($xml->ErrorDocument->Key)) { + $this->errorDocument = strval($xml->ErrorDocument->Key); + } + } + + /** + * 把WebsiteConfig序列化成xml + * + * @return string + * @throws OssException + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + $index_document_part = $xml->addChild('IndexDocument'); + $error_document_part = $xml->addChild('ErrorDocument'); + $index_document_part->addChild('Suffix', $this->indexDocument); + $error_document_part->addChild('Key', $this->errorDocument); + return $xml->asXML(); + } + + /** + * @return string + */ + public function getIndexDocument() + { + return $this->indexDocument; + } + + /** + * @return string + */ + public function getErrorDocument() + { + return $this->errorDocument; + } + + private $indexDocument = ""; + private $errorDocument = ""; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/XmlConfig.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/XmlConfig.php new file mode 100644 index 0000000..d353a22 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Model/XmlConfig.php @@ -0,0 +1,27 @@ +hostname = $this->checkEndpoint($endpoint, $isCName); + $this->accessKeyId = $accessKeyId; + $this->accessKeySecret = $accessKeySecret; + $this->securityToken = $securityToken; + $this->requestProxy = $requestProxy; + + self::checkEnv(); + } + + /** + * 列举用户所有的Bucket[GetService], Endpoint类型为cname不能进行此操作 + * + * @param array $options + * @throws OssException + * @return BucketListInfo + */ + public function listBuckets($options = NULL) + { + if ($this->hostType === self::OSS_HOST_TYPE_CNAME) { + throw new OssException("operation is not permitted with CName host"); + } + $this->precheckOptions($options); + $options[self::OSS_BUCKET] = ''; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $response = $this->auth($options); + $result = new ListBucketsResult($response); + return $result->getData(); + } + + /** + * 创建bucket,默认创建的bucket的ACL是OssClient::OSS_ACL_TYPE_PRIVATE + * + * @param string $bucket + * @param string $acl + * @param array $options + * @param string $storageType + * @return null + */ + public function createBucket($bucket, $acl = self::OSS_ACL_TYPE_PRIVATE, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_HEADERS] = array(self::OSS_ACL => $acl); + if (isset($options[self::OSS_STORAGE])) { + $this->precheckStorage($options[self::OSS_STORAGE]); + $options[self::OSS_CONTENT] = OssUtil::createBucketXmlBody($options[self::OSS_STORAGE]); + unset($options[self::OSS_STORAGE]); + } + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 删除bucket + * 如果Bucket不为空(Bucket中有Object,或者有分块上传的碎片),则Bucket无法删除, + * 必须删除Bucket中的所有Object以及碎片后,Bucket才能成功删除。 + * + * @param string $bucket + * @param array $options + * @return null + */ + public function deleteBucket($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 判断bucket是否存在 + * + * @param string $bucket + * @return bool + * @throws OssException + */ + public function doesBucketExist($bucket) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new ExistResult($response); + return $result->getData(); + } + + /** + * 获取bucket所属的数据中心位置信息 + * + * @param string $bucket + * @param array $options + * @throws OssException + * @return string + */ + public function getBucketLocation($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'location'; + $response = $this->auth($options); + $result = new GetLocationResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的Meta信息 + * + * @param string $bucket + * @param array $options 具体参考SDK文档 + * @return array + */ + public function getBucketMeta($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_HEAD; + $options[self::OSS_OBJECT] = '/'; + $response = $this->auth($options); + $result = new HeaderResult($response); + return $result->getData(); + } + + /** + * 获取bucket的ACL配置情况 + * + * @param string $bucket + * @param array $options + * @throws OssException + * @return string + */ + public function getBucketAcl($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new AclResult($response); + return $result->getData(); + } + + /** + * 设置bucket的ACL配置情况 + * + * @param string $bucket bucket名称 + * @param string $acl 读写权限,可选值 ['private', 'public-read', 'public-read-write'] + * @param array $options 可以为空 + * @throws OssException + * @return null + */ + public function putBucketAcl($bucket, $acl, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_HEADERS] = array(self::OSS_ACL => $acl); + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取object的ACL属性 + * + * @param string $bucket + * @param string $object + * @throws OssException + * @return string + */ + public function getObjectAcl($bucket, $object) + { + $options = array(); + $this->precheckCommon($bucket, $object, $options, true); + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new AclResult($response); + return $result->getData(); + } + + /** + * 设置object的ACL属性 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $acl 读写权限,可选值 ['default', 'private', 'public-read', 'public-read-write'] + * @throws OssException + * @return null + */ + public function putObjectAcl($bucket, $object, $acl) + { + $this->precheckCommon($bucket, $object, $options, true); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_HEADERS] = array(self::OSS_OBJECT_ACL => $acl); + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的访问日志配置情况 + * + * @param string $bucket bucket名称 + * @param array $options 可以为空 + * @throws OssException + * @return LoggingConfig + */ + public function getBucketLogging($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'logging'; + $response = $this->auth($options); + $result = new GetLoggingResult($response); + return $result->getData(); + } + + /** + * 开启Bucket访问日志记录功能,只有Bucket的所有者才能更改 + * + * @param string $bucket bucket名称 + * @param string $targetBucket 日志文件存放的bucket + * @param string $targetPrefix 日志的文件前缀 + * @param array $options 可以为空 + * @throws OssException + * @return null + */ + public function putBucketLogging($bucket, $targetBucket, $targetPrefix, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $this->precheckBucket($targetBucket, 'targetbucket is not allowed empty'); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'logging'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + + $loggingConfig = new LoggingConfig($targetBucket, $targetPrefix); + $options[self::OSS_CONTENT] = $loggingConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 关闭bucket访问日志记录功能 + * + * @param string $bucket bucket名称 + * @param array $options 可以为空 + * @throws OssException + * @return null + */ + public function deleteBucketLogging($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'logging'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 将bucket设置成静态网站托管模式 + * + * @param string $bucket bucket名称 + * @param WebsiteConfig $websiteConfig + * @param array $options 可以为空 + * @throws OssException + * @return null + */ + public function putBucketWebsite($bucket, $websiteConfig, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'website'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $websiteConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取bucket的静态网站托管状态 + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return WebsiteConfig + */ + public function getBucketWebsite($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'website'; + $response = $this->auth($options); + $result = new GetWebsiteResult($response); + return $result->getData(); + } + + /** + * 关闭bucket的静态网站托管模式 + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return null + */ + public function deleteBucketWebsite($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'website'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 在指定的bucket上设定一个跨域资源共享(CORS)的规则,如果原规则存在则覆盖原规则 + * + * @param string $bucket bucket名称 + * @param CorsConfig $corsConfig 跨域资源共享配置,具体规则参见SDK文档 + * @param array $options array + * @throws OssException + * @return null + */ + public function putBucketCors($bucket, $corsConfig, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cors'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $corsConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的CORS配置情况 + * + * @param string $bucket bucket名称 + * @param array $options 可以为空 + * @throws OssException + * @return CorsConfig + */ + public function getBucketCors($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cors'; + $response = $this->auth($options); + $result = new GetCorsResult($response, __FUNCTION__); + return $result->getData(); + } + + /** + * 关闭指定Bucket对应的CORS功能并清空所有规则 + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return null + */ + public function deleteBucketCors($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cors'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 为指定Bucket增加CNAME绑定 + * + * @param string $bucket bucket名称 + * @param string $cname + * @param array $options + * @throws OssException + * @return null + */ + public function addBucketCname($bucket, $cname, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cname'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $cnameConfig = new CnameConfig(); + $cnameConfig->addCname($cname); + $options[self::OSS_CONTENT] = $cnameConfig->serializeToXml(); + $options[self::OSS_COMP] = 'add'; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取指定Bucket已绑定的CNAME列表 + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return CnameConfig + */ + public function getBucketCname($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cname'; + $response = $this->auth($options); + $result = new GetCnameResult($response); + return $result->getData(); + } + + /** + * 解除指定Bucket的CNAME绑定 + * + * @param string $bucket bucket名称 + * @param CnameConfig $cnameConfig + * @param array $options + * @throws OssException + * @return null + */ + public function deleteBucketCname($bucket, $cname, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cname'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $cnameConfig = new CnameConfig(); + $cnameConfig->addCname($cname); + $options[self::OSS_CONTENT] = $cnameConfig->serializeToXml(); + $options[self::OSS_COMP] = 'delete'; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 为指定Bucket创建LiveChannel + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param LiveChannelConfig $channelConfig + * @param array $options + * @throws OssException + * @return LiveChannelInfo + */ + public function putBucketLiveChannel($bucket, $channelName, $channelConfig, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $channelConfig->serializeToXml(); + + $response = $this->auth($options); + $result = new PutLiveChannelResult($response); + $info = $result->getData(); + $info->setName($channelName); + $info->setDescription($channelConfig->getDescription()); + + return $info; + } + + /** + * 设置LiveChannel的status + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param string channelStatus $channelStatus 为enabled或disabled + * @param array $options + * @throws OssException + * @return null + */ + public function putLiveChannelStatus($bucket, $channelName, $channelStatus, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_LIVE_CHANNEL_STATUS] = $channelStatus; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取LiveChannel信息 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param array $options + * @throws OssException + * @return GetLiveChannelInfo + */ + public function getLiveChannelInfo($bucket, $channelName, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + + $response = $this->auth($options); + $result = new GetLiveChannelInfoResult($response); + return $result->getData(); + } + + /** + * 获取LiveChannel状态信息 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param array $options + * @throws OssException + * @return GetLiveChannelStatus + */ + public function getLiveChannelStatus($bucket, $channelName, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_COMP] = 'stat'; + + $response = $this->auth($options); + $result = new GetLiveChannelStatusResult($response); + return $result->getData(); + } + + /** + *获取LiveChannel推流记录 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param array $options + * @throws OssException + * @return GetLiveChannelHistory + */ + public function getLiveChannelHistory($bucket, $channelName, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_COMP] = 'history'; + + $response = $this->auth($options); + $result = new GetLiveChannelHistoryResult($response); + return $result->getData(); + } + + /** + *获取指定Bucket下的live channel列表 + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return LiveChannelListInfo + */ + public function listBucketLiveChannels($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_QUERY_STRING] = array( + 'prefix' => isset($options['prefix']) ? $options['prefix'] : '', + 'marker' => isset($options['marker']) ? $options['marker'] : '', + 'max-keys' => isset($options['max-keys']) ? $options['max-keys'] : '', + ); + $response = $this->auth($options); + $result = new ListLiveChannelResult($response); + $list = $result->getData(); + $list->setBucketName($bucket); + + return $list; + } + + /** + * 为指定LiveChannel生成播放列表 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param string $playlistName 指定生成的点播播放列表的名称,必须以“.m3u8”结尾 + * @param array $setTime startTime和EndTime以unix时间戳格式给定,跨度不能超过一天 + * @throws OssException + * @return null + */ + public function postVodPlaylist($bucket, $channelName, $playlistName, $setTime) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = $channelName . '/' . $playlistName; + $options[self::OSS_SUB_RESOURCE] = 'vod'; + $options[self::OSS_LIVE_CHANNEL_END_TIME] = $setTime['EndTime']; + $options[self::OSS_LIVE_CHANNEL_START_TIME] = $setTime['StartTime']; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 删除指定Bucket的LiveChannel + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param array $options + * @throws OssException + * @return null + */ + public function deleteBucketLiveChannel($bucket, $channelName, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 生成带签名的推流地址 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param int timeout 设置超时时间,单位为秒 + * @param array $options + * @throws OssException + * @return 推流地址 + */ + public function signRtmpUrl($bucket, $channelName, $timeout = 60, $options = NULL) + { + $this->precheckCommon($bucket, $channelName, $options, false); + $expires = time() + $timeout; + $proto = 'rtmp://'; + $hostname = $this->generateHostname($bucket); + $cano_params = ''; + $query_items = array(); + $params = isset($options['params']) ? $options['params'] : array(); + uksort($params, 'strnatcasecmp'); + foreach ($params as $key => $value) { + $cano_params = $cano_params . $key . ':' . $value . "\n"; + $query_items[] = rawurlencode($key) . '=' . rawurlencode($value); + } + $resource = '/' . $bucket . '/' . $channelName; + + $string_to_sign = $expires . "\n" . $cano_params . $resource; + $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $this->accessKeySecret, true)); + + $query_items[] = 'OSSAccessKeyId=' . rawurlencode($this->accessKeyId); + $query_items[] = 'Expires=' . rawurlencode($expires); + $query_items[] = 'Signature=' . rawurlencode($signature); + + return $proto . $hostname . '/live/' . $channelName . '?' . implode('&', $query_items); + } + + /** + * 检验跨域资源请求, 发送跨域请求之前会发送一个preflight请求(OPTIONS)并带上特定的来源域, + * HTTP方法和header信息等给OSS以决定是否发送真正的请求。 OSS可以通过putBucketCors接口 + * 来开启Bucket的CORS支持,开启CORS功能之后,OSS在收到浏览器preflight请求时会根据设定的 + * 规则评估是否允许本次请求 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $origin 请求来源域 + * @param string $request_method 表明实际请求中会使用的HTTP方法 + * @param string $request_headers 表明实际请求中会使用的除了简单头部之外的headers + * @param array $options + * @return array + * @throws OssException + * @link http://help.aliyun.com/document_detail/oss/api-reference/cors/OptionObject.html + */ + public function optionsObject($bucket, $object, $origin, $request_method, $request_headers, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_OPTIONS; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_HEADERS] = array( + self::OSS_OPTIONS_ORIGIN => $origin, + self::OSS_OPTIONS_REQUEST_HEADERS => $request_headers, + self::OSS_OPTIONS_REQUEST_METHOD => $request_method + ); + $response = $this->auth($options); + $result = new HeaderResult($response); + return $result->getData(); + } + + /** + * 设置Bucket的Lifecycle配置 + * + * @param string $bucket bucket名称 + * @param LifecycleConfig $lifecycleConfig Lifecycle配置类 + * @param array $options + * @throws OssException + * @return null + */ + public function putBucketLifecycle($bucket, $lifecycleConfig, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'lifecycle'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $lifecycleConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的Lifecycle配置情况 + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return LifecycleConfig + */ + public function getBucketLifecycle($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'lifecycle'; + $response = $this->auth($options); + $result = new GetLifecycleResult($response); + return $result->getData(); + } + + /** + * 删除指定Bucket的生命周期配置 + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return null + */ + public function deleteBucketLifecycle($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'lifecycle'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 设置一个bucket的referer访问白名单和是否允许referer字段为空的请求访问 + * Bucket Referer防盗链具体见OSS防盗链 + * + * @param string $bucket bucket名称 + * @param RefererConfig $refererConfig + * @param array $options + * @return ResponseCore + * @throws null + */ + public function putBucketReferer($bucket, $refererConfig, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'referer'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $refererConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的Referer配置情况 + * Bucket Referer防盗链具体见OSS防盗链 + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return RefererConfig + */ + public function getBucketReferer($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'referer'; + $response = $this->auth($options); + $result = new GetRefererResult($response); + return $result->getData(); + } + + /** + * 设置bucket的容量大小,单位GB + * 当bucket的容量大于设置的容量时,禁止继续写入 + * + * @param string $bucket bucket名称 + * @param int $storageCapacity + * @param array $options + * @return ResponseCore + * @throws null + */ + public function putBucketStorageCapacity($bucket, $storageCapacity, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'qos'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $storageCapacityConfig = new StorageCapacityConfig($storageCapacity); + $options[self::OSS_CONTENT] = $storageCapacityConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取bucket的容量大小,单位GB + * + * @param string $bucket bucket名称 + * @param array $options + * @throws OssException + * @return int + */ + public function getBucketStorageCapacity($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'qos'; + $response = $this->auth($options); + $result = new GetStorageCapacityResult($response); + return $result->getData(); + } + + /** + * 获取bucket下的object列表 + * + * @param string $bucket + * @param array $options + * 其中options中的参数如下 + * $options = array( + * 'max-keys' => max-keys用于限定此次返回object的最大数,如果不设定,默认为100,max-keys取值不能大于1000。 + * 'prefix' => 限定返回的object key必须以prefix作为前缀。注意使用prefix查询时,返回的key中仍会包含prefix。 + * 'delimiter' => 是一个用于对Object名字进行分组的字符。所有名字包含指定的前缀且第一次出现delimiter字符之间的object作为一组元素 + * 'marker' => 用户设定结果从marker之后按字母排序的第一个开始返回。 + *) + * 其中 prefix,marker用来实现分页显示效果,参数的长度必须小于256字节。 + * @throws OssException + * @return ObjectListInfo + */ + public function listObjects($bucket, $options = NULL) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_HEADERS] = array( + self::OSS_DELIMITER => isset($options[self::OSS_DELIMITER]) ? $options[self::OSS_DELIMITER] : '/', + self::OSS_PREFIX => isset($options[self::OSS_PREFIX]) ? $options[self::OSS_PREFIX] : '', + self::OSS_MAX_KEYS => isset($options[self::OSS_MAX_KEYS]) ? $options[self::OSS_MAX_KEYS] : self::OSS_MAX_KEYS_VALUE, + self::OSS_MARKER => isset($options[self::OSS_MARKER]) ? $options[self::OSS_MARKER] : '', + ); + $query = isset($options[self::OSS_QUERY_STRING]) ? $options[self::OSS_QUERY_STRING] : array(); + $options[self::OSS_QUERY_STRING] = array_merge( + $query, + array(self::OSS_ENCODING_TYPE => self::OSS_ENCODING_TYPE_URL) + ); + + $response = $this->auth($options); + $result = new ListObjectsResult($response); + return $result->getData(); + } + + /** + * 创建虚拟目录 (本函数会在object名称后增加'/', 所以创建目录的object名称不需要'/'结尾,否则,目录名称会变成'//') + * + * 暂不开放此接口 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param array $options + * @return null + */ + public function createObjectDir($bucket, $object, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $object . '/'; + $options[self::OSS_CONTENT_LENGTH] = array(self::OSS_CONTENT_LENGTH => 0); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 上传内存中的内容 + * + * @param string $bucket bucket名称 + * @param string $object objcet名称 + * @param string $content 上传的内容 + * @param array $options + * @return null + */ + public function putObject($bucket, $object, $content, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + + $options[self::OSS_CONTENT] = $content; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $object; + + if (!isset($options[self::OSS_LENGTH])) { + $options[self::OSS_CONTENT_LENGTH] = strlen($options[self::OSS_CONTENT]); + } else { + $options[self::OSS_CONTENT_LENGTH] = $options[self::OSS_LENGTH]; + } + + $is_check_md5 = $this->isCheckMD5($options); + if ($is_check_md5) { + $content_md5 = base64_encode(md5($content, true)); + $options[self::OSS_CONTENT_MD5] = $content_md5; + } + + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object); + } + $response = $this->auth($options); + + if (isset($options[self::OSS_CALLBACK]) && !empty($options[self::OSS_CALLBACK])) { + $result = new CallbackResult($response); + } else { + $result = new PutSetDeleteResult($response); + } + + return $result->getData(); + } + + /** + * 创建symlink + * @param string $bucket bucket名称 + * @param string $symlink symlink名称 + * @param string $targetObject 目标object名称 + * @param array $options + * @return null + */ + public function putSymlink($bucket, $symlink ,$targetObject, $options = NULL) + { + $this->precheckCommon($bucket, $symlink, $options); + + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $symlink; + $options[self::OSS_SUB_RESOURCE] = self::OSS_SYMLINK; + $options[self::OSS_HEADERS][self::OSS_SYMLINK_TARGET] = rawurlencode($targetObject); + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取symlink + *@param string $bucket bucket名称 + * @param string $symlink symlink名称 + * @return null + */ + public function getSymlink($bucket, $symlink) + { + $this->precheckCommon($bucket, $symlink, $options); + + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $symlink; + $options[self::OSS_SUB_RESOURCE] = self::OSS_SYMLINK; + + $response = $this->auth($options); + $result = new SymlinkResult($response); + return $result->getData(); + } + + /** + * 上传本地文件 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $file 本地文件路径 + * @param array $options + * @return null + * @throws OssException + */ + public function uploadFile($bucket, $object, $file, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + OssUtil::throwOssExceptionWithMessageIfEmpty($file, "file path is invalid"); + $file = OssUtil::encodePath($file); + if (!file_exists($file)) { + throw new OssException($file . " file does not exist"); + } + $options[self::OSS_FILE_UPLOAD] = $file; + $file_size = filesize($options[self::OSS_FILE_UPLOAD]); + $is_check_md5 = $this->isCheckMD5($options); + if ($is_check_md5) { + $content_md5 = base64_encode(md5_file($options[self::OSS_FILE_UPLOAD], true)); + $options[self::OSS_CONTENT_MD5] = $content_md5; + } + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object, $file); + } + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_CONTENT_LENGTH] = $file_size; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 追加上传内存中的内容 + * + * @param string $bucket bucket名称 + * @param string $object objcet名称 + * @param string $content 本次追加上传的内容 + * @param array $options + * @return int next append position + * @throws OssException + */ + public function appendObject($bucket, $object, $content, $position, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + + $options[self::OSS_CONTENT] = $content; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_SUB_RESOURCE] = 'append'; + $options[self::OSS_POSITION] = strval($position); + + if (!isset($options[self::OSS_LENGTH])) { + $options[self::OSS_CONTENT_LENGTH] = strlen($options[self::OSS_CONTENT]); + } else { + $options[self::OSS_CONTENT_LENGTH] = $options[self::OSS_LENGTH]; + } + + $is_check_md5 = $this->isCheckMD5($options); + if ($is_check_md5) { + $content_md5 = base64_encode(md5($content, true)); + $options[self::OSS_CONTENT_MD5] = $content_md5; + } + + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object); + } + $response = $this->auth($options); + $result = new AppendResult($response); + return $result->getData(); + } + + /** + * 追加上传本地文件 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $file 追加上传的本地文件路径 + * @param array $options + * @return int next append position + * @throws OssException + */ + public function appendFile($bucket, $object, $file, $position, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + + OssUtil::throwOssExceptionWithMessageIfEmpty($file, "file path is invalid"); + $file = OssUtil::encodePath($file); + if (!file_exists($file)) { + throw new OssException($file . " file does not exist"); + } + $options[self::OSS_FILE_UPLOAD] = $file; + $file_size = filesize($options[self::OSS_FILE_UPLOAD]); + $is_check_md5 = $this->isCheckMD5($options); + if ($is_check_md5) { + $content_md5 = base64_encode(md5_file($options[self::OSS_FILE_UPLOAD], true)); + $options[self::OSS_CONTENT_MD5] = $content_md5; + } + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object, $file); + } + + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_CONTENT_LENGTH] = $file_size; + $options[self::OSS_SUB_RESOURCE] = 'append'; + $options[self::OSS_POSITION] = strval($position); + + $response = $this->auth($options); + $result = new AppendResult($response); + return $result->getData(); + } + + /** + * 拷贝一个在OSS上已经存在的object成另外一个object + * + * @param string $fromBucket 源bucket名称 + * @param string $fromObject 源object名称 + * @param string $toBucket 目标bucket名称 + * @param string $toObject 目标object名称 + * @param array $options + * @return null + * @throws OssException + */ + public function copyObject($fromBucket, $fromObject, $toBucket, $toObject, $options = NULL) + { + $this->precheckCommon($fromBucket, $fromObject, $options); + $this->precheckCommon($toBucket, $toObject, $options); + $options[self::OSS_BUCKET] = $toBucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $toObject; + if (isset($options[self::OSS_HEADERS])) { + $options[self::OSS_HEADERS][self::OSS_OBJECT_COPY_SOURCE] = '/' . $fromBucket . '/' . $fromObject; + } else { + $options[self::OSS_HEADERS] = array(self::OSS_OBJECT_COPY_SOURCE => '/' . $fromBucket . '/' . $fromObject); + } + $response = $this->auth($options); + $result = new CopyObjectResult($response); + return $result->getData(); + } + + /** + * 获取Object的Meta信息 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $options 具体参考SDK文档 + * @return array + */ + public function getObjectMeta($bucket, $object, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_HEAD; + $options[self::OSS_OBJECT] = $object; + $response = $this->auth($options); + $result = new HeaderResult($response); + return $result->getData(); + } + + /** + * 删除某个Object + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param array $options + * @return null + */ + public function deleteObject($bucket, $object, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = $object; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 删除同一个Bucket中的多个Object + * + * @param string $bucket bucket名称 + * @param array $objects object列表 + * @param array $options + * @return ResponseCore + * @throws null + */ + public function deleteObjects($bucket, $objects, $options = null) + { + $this->precheckCommon($bucket, NULL, $options, false); + if (!is_array($objects) || !$objects) { + throw new OssException('objects must be array'); + } + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'delete'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $quiet = 'false'; + if (isset($options['quiet'])) { + if (is_bool($options['quiet'])) { //Boolean + $quiet = $options['quiet'] ? 'true' : 'false'; + } elseif (is_string($options['quiet'])) { // string + $quiet = ($options['quiet'] === 'true') ? 'true' : 'false'; + } + } + $xmlBody = OssUtil::createDeleteObjectsXmlBody($objects, $quiet); + $options[self::OSS_CONTENT] = $xmlBody; + $response = $this->auth($options); + $result = new DeleteObjectsResult($response); + return $result->getData(); + } + + /** + * 获得Object内容 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param array $options 该参数中必须设置ALIOSS::OSS_FILE_DOWNLOAD,ALIOSS::OSS_RANGE可选,可以根据实际情况设置;如果不设置,默认会下载全部内容 + * @return string + */ + public function getObject($bucket, $object, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $object; + if (isset($options[self::OSS_LAST_MODIFIED])) { + $options[self::OSS_HEADERS][self::OSS_IF_MODIFIED_SINCE] = $options[self::OSS_LAST_MODIFIED]; + unset($options[self::OSS_LAST_MODIFIED]); + } + if (isset($options[self::OSS_ETAG])) { + $options[self::OSS_HEADERS][self::OSS_IF_NONE_MATCH] = $options[self::OSS_ETAG]; + unset($options[self::OSS_ETAG]); + } + if (isset($options[self::OSS_RANGE])) { + $range = $options[self::OSS_RANGE]; + $options[self::OSS_HEADERS][self::OSS_RANGE] = "bytes=$range"; + unset($options[self::OSS_RANGE]); + } + $response = $this->auth($options); + $result = new BodyResult($response); + return $result->getData(); + } + + /** + * 检测Object是否存在 + * 通过获取Object的Meta信息来判断Object是否存在, 用户需要自行解析ResponseCore判断object是否存在 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param array $options + * @return bool + */ + public function doesObjectExist($bucket, $object, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_HEAD; + $options[self::OSS_OBJECT] = $object; + $response = $this->auth($options); + $result = new ExistResult($response); + return $result->getData(); + } + + /** + * 针对Archive类型的Object读取 + * 需要使用Restore操作让服务端执行解冻任务 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @return null + * @throws OssException + */ + public function restoreObject($bucket, $object, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_SUB_RESOURCE] = self::OSS_RESTORE; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取分片大小,根据用户提供的part_size,重新计算一个更合理的partsize + * + * @param int $partSize + * @return int + */ + private function computePartSize($partSize) + { + $partSize = (integer)$partSize; + if ($partSize <= self::OSS_MIN_PART_SIZE) { + $partSize = self::OSS_MIN_PART_SIZE; + } elseif ($partSize > self::OSS_MAX_PART_SIZE) { + $partSize = self::OSS_MAX_PART_SIZE; + } + return $partSize; + } + + /** + * 计算文件可以分成多少个part,以及每个part的长度以及起始位置 + * 方法必须在 中调用 + * + * @param integer $file_size 文件大小 + * @param integer $partSize part大小,默认5M + * @return array An array 包含 key-value 键值对. Key 为 `seekTo` 和 `length`. + */ + public function generateMultiuploadParts($file_size, $partSize = 5242880) + { + $i = 0; + $size_count = $file_size; + $values = array(); + $partSize = $this->computePartSize($partSize); + while ($size_count > 0) { + $size_count -= $partSize; + $values[] = array( + self::OSS_SEEK_TO => ($partSize * $i), + self::OSS_LENGTH => (($size_count > 0) ? $partSize : ($size_count + $partSize)), + ); + $i++; + } + return $values; + } + + /** + * 初始化multi-part upload + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param array $options Key-Value数组 + * @throws OssException + * @return string 返回uploadid + */ + public function initiateMultipartUpload($bucket, $object, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_SUB_RESOURCE] = 'uploads'; + $options[self::OSS_CONTENT] = ''; + + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object); + } + if (!isset($options[self::OSS_HEADERS])) { + $options[self::OSS_HEADERS] = array(); + } + $response = $this->auth($options); + $result = new InitiateMultipartUploadResult($response); + return $result->getData(); + } + + /** + * 分片上传的块上传接口 + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param string $uploadId + * @param array $options Key-Value数组 + * @return string eTag + * @throws OssException + */ + public function uploadPart($bucket, $object, $uploadId, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $this->precheckParam($options, self::OSS_FILE_UPLOAD, __FUNCTION__); + $this->precheckParam($options, self::OSS_PART_NUM, __FUNCTION__); + + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + + if (isset($options[self::OSS_LENGTH])) { + $options[self::OSS_CONTENT_LENGTH] = $options[self::OSS_LENGTH]; + } + $response = $this->auth($options); + $result = new UploadPartResult($response); + return $result->getData(); + } + + /** + * 获取已成功上传的part + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param string $uploadId uploadId + * @param array $options Key-Value数组 + * @return ListPartsInfo + * @throws OssException + */ + public function listParts($bucket, $object, $uploadId, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + $options[self::OSS_QUERY_STRING] = array(); + foreach (array('max-parts', 'part-number-marker') as $param) { + if (isset($options[$param])) { + $options[self::OSS_QUERY_STRING][$param] = $options[$param]; + unset($options[$param]); + } + } + $response = $this->auth($options); + $result = new ListPartsResult($response); + return $result->getData(); + } + + /** + * 中止进行一半的分片上传操作 + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param string $uploadId uploadId + * @param array $options Key-Value数组 + * @return null + * @throws OssException + */ + public function abortMultipartUpload($bucket, $object, $uploadId, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 在将所有数据Part都上传完成后,调用此接口完成本次分块上传 + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param string $uploadId uploadId + * @param array $listParts array( array("PartNumber"=> int, "ETag"=>string)) + * @param array $options Key-Value数组 + * @throws OssException + * @return null + */ + public function completeMultipartUpload($bucket, $object, $uploadId, $listParts, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + if (!is_array($listParts)) { + throw new OssException("listParts must be array type"); + } + $options[self::OSS_CONTENT] = OssUtil::createCompleteMultipartUploadXmlBody($listParts); + $response = $this->auth($options); + if (isset($options[self::OSS_CALLBACK]) && !empty($options[self::OSS_CALLBACK])) { + $result = new CallbackResult($response); + } else { + $result = new PutSetDeleteResult($response); + } + return $result->getData(); + } + + /** + * 罗列出所有执行中的Multipart Upload事件,即已经被初始化的Multipart Upload但是未被 + * Complete或者Abort的Multipart Upload事件 + * + * @param string $bucket bucket + * @param array $options 关联数组 + * @throws OssException + * @return ListMultipartUploadInfo + */ + public function listMultipartUploads($bucket, $options = null) + { + $this->precheckCommon($bucket, NULL, $options, false); + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'uploads'; + + foreach (array('delimiter', 'key-marker', 'max-uploads', 'prefix', 'upload-id-marker') as $param) { + if (isset($options[$param])) { + $options[self::OSS_QUERY_STRING][$param] = $options[$param]; + unset($options[$param]); + } + } + $query = isset($options[self::OSS_QUERY_STRING]) ? $options[self::OSS_QUERY_STRING] : array(); + $options[self::OSS_QUERY_STRING] = array_merge( + $query, + array(self::OSS_ENCODING_TYPE => self::OSS_ENCODING_TYPE_URL) + ); + + $response = $this->auth($options); + $result = new ListMultipartUploadResult($response); + return $result->getData(); + } + + /** + * 从一个已存在的Object中拷贝数据来上传一个Part + * + * @param string $fromBucket 源bucket名称 + * @param string $fromObject 源object名称 + * @param string $toBucket 目标bucket名称 + * @param string $toObject 目标object名称 + * @param int $partNumber 分块上传的块id + * @param string $uploadId 初始化multipart upload返回的uploadid + * @param array $options Key-Value数组 + * @return null + * @throws OssException + */ + public function uploadPartCopy($fromBucket, $fromObject, $toBucket, $toObject, $partNumber, $uploadId, $options = NULL) + { + $this->precheckCommon($fromBucket, $fromObject, $options); + $this->precheckCommon($toBucket, $toObject, $options); + + //如果没有设置$options['isFullCopy'],则需要强制判断copy的起止位置 + $start_range = "0"; + if (isset($options['start'])) { + $start_range = $options['start']; + } + $end_range = ""; + if (isset($options['end'])) { + $end_range = $options['end']; + } + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_BUCKET] = $toBucket; + $options[self::OSS_OBJECT] = $toObject; + $options[self::OSS_PART_NUM] = $partNumber; + $options[self::OSS_UPLOAD_ID] = $uploadId; + + if (!isset($options[self::OSS_HEADERS])) { + $options[self::OSS_HEADERS] = array(); + } + + $options[self::OSS_HEADERS][self::OSS_OBJECT_COPY_SOURCE] = '/' . $fromBucket . '/' . $fromObject; + $options[self::OSS_HEADERS][self::OSS_OBJECT_COPY_SOURCE_RANGE] = "bytes=" . $start_range . "-" . $end_range; + $response = $this->auth($options); + $result = new UploadPartResult($response); + return $result->getData(); + } + + /** + * multipart上传统一封装,从初始化到完成multipart,以及出错后中止动作 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $file 需要上传的本地文件的路径 + * @param array $options Key-Value数组 + * @return null + * @throws OssException + */ + public function multiuploadFile($bucket, $object, $file, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + if (isset($options[self::OSS_LENGTH])) { + $options[self::OSS_CONTENT_LENGTH] = $options[self::OSS_LENGTH]; + unset($options[self::OSS_LENGTH]); + } + if (empty($file)) { + throw new OssException("parameter invalid, file is empty"); + } + $uploadFile = OssUtil::encodePath($file); + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object, $uploadFile); + } + + $upload_position = isset($options[self::OSS_SEEK_TO]) ? (integer)$options[self::OSS_SEEK_TO] : 0; + + if (isset($options[self::OSS_CONTENT_LENGTH])) { + $upload_file_size = (integer)$options[self::OSS_CONTENT_LENGTH]; + } else { + $upload_file_size = filesize($uploadFile); + if ($upload_file_size !== false) { + $upload_file_size -= $upload_position; + } + } + + if ($upload_position === false || !isset($upload_file_size) || $upload_file_size === false || $upload_file_size < 0) { + throw new OssException('The size of `fileUpload` cannot be determined in ' . __FUNCTION__ . '().'); + } + // 处理partSize + if (isset($options[self::OSS_PART_SIZE])) { + $options[self::OSS_PART_SIZE] = $this->computePartSize($options[self::OSS_PART_SIZE]); + } else { + $options[self::OSS_PART_SIZE] = self::OSS_MID_PART_SIZE; + } + + $is_check_md5 = $this->isCheckMD5($options); + // 如果上传的文件小于partSize,则直接使用普通方式上传 + if ($upload_file_size < $options[self::OSS_PART_SIZE] && !isset($options[self::OSS_UPLOAD_ID])) { + return $this->uploadFile($bucket, $object, $uploadFile, $options); + } + + // 初始化multipart + if (isset($options[self::OSS_UPLOAD_ID])) { + $uploadId = $options[self::OSS_UPLOAD_ID]; + } else { + // 初始化 + $uploadId = $this->initiateMultipartUpload($bucket, $object, $options); + } + + // 获取的分片 + $pieces = $this->generateMultiuploadParts($upload_file_size, (integer)$options[self::OSS_PART_SIZE]); + $response_upload_part = array(); + foreach ($pieces as $i => $piece) { + $from_pos = $upload_position + (integer)$piece[self::OSS_SEEK_TO]; + $to_pos = (integer)$piece[self::OSS_LENGTH] + $from_pos - 1; + $up_options = array( + self::OSS_FILE_UPLOAD => $uploadFile, + self::OSS_PART_NUM => ($i + 1), + self::OSS_SEEK_TO => $from_pos, + self::OSS_LENGTH => $to_pos - $from_pos + 1, + self::OSS_CHECK_MD5 => $is_check_md5, + ); + if ($is_check_md5) { + $content_md5 = OssUtil::getMd5SumForFile($uploadFile, $from_pos, $to_pos); + $up_options[self::OSS_CONTENT_MD5] = $content_md5; + } + $response_upload_part[] = $this->uploadPart($bucket, $object, $uploadId, $up_options); + } + + $uploadParts = array(); + foreach ($response_upload_part as $i => $etag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $etag, + ); + } + return $this->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts); + } + + /** + * 上传本地目录内的文件或者目录到指定bucket的指定prefix的object中 + * + * @param string $bucket bucket名称 + * @param string $prefix 需要上传到的object的key前缀,可以理解成bucket中的子目录,结尾不能是'/',接口中会补充'/' + * @param string $localDirectory 需要上传的本地目录 + * @param string $exclude 需要排除的目录 + * @param bool $recursive 是否递归的上传localDirectory下的子目录内容 + * @param bool $checkMd5 + * @return array 返回两个列表 array("succeededList" => array("object"), "failedList" => array("object"=>"errorMessage")) + * @throws OssException + */ + public function uploadDir($bucket, $prefix, $localDirectory, $exclude = '.|..|.svn|.git', $recursive = false, $checkMd5 = true) + { + $retArray = array("succeededList" => array(), "failedList" => array()); + if (empty($bucket)) throw new OssException("parameter error, bucket is empty"); + if (!is_string($prefix)) throw new OssException("parameter error, prefix is not string"); + if (empty($localDirectory)) throw new OssException("parameter error, localDirectory is empty"); + $directory = $localDirectory; + $directory = OssUtil::encodePath($directory); + //判断是否目录 + if (!is_dir($directory)) { + throw new OssException('parameter error: ' . $directory . ' is not a directory, please check it'); + } + //read directory + $file_list_array = OssUtil::readDir($directory, $exclude, $recursive); + if (!$file_list_array) { + throw new OssException($directory . ' is empty...'); + } + foreach ($file_list_array as $k => $item) { + if (is_dir($item['path'])) { + continue; + } + $options = array( + self::OSS_PART_SIZE => self::OSS_MIN_PART_SIZE, + self::OSS_CHECK_MD5 => $checkMd5, + ); + $realObject = (!empty($prefix) ? $prefix . '/' : '') . $item['file']; + + try { + $this->multiuploadFile($bucket, $realObject, $item['path'], $options); + $retArray["succeededList"][] = $realObject; + } catch (OssException $e) { + $retArray["failedList"][$realObject] = $e->getMessage(); + } + } + return $retArray; + } + + /** + * 支持生成get和put签名, 用户可以生成一个具有一定有效期的 + * 签名过的url + * + * @param string $bucket + * @param string $object + * @param int $timeout + * @param string $method + * @param array $options Key-Value数组 + * @return string + * @throws OssException + */ + public function signUrl($bucket, $object, $timeout = 60, $method = self::OSS_HTTP_GET, $options = NULL) + { + $this->precheckCommon($bucket, $object, $options); + //method + if (self::OSS_HTTP_GET !== $method && self::OSS_HTTP_PUT !== $method) { + throw new OssException("method is invalid"); + } + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_METHOD] = $method; + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = ''; + } + $timeout = time() + $timeout; + $options[self::OSS_PREAUTH] = $timeout; + $options[self::OSS_DATE] = $timeout; + $this->setSignStsInUrl(true); + return $this->auth($options); + } + + /** + * 检测options参数 + * + * @param array $options + * @throws OssException + */ + private function precheckOptions(&$options) + { + OssUtil::validateOptions($options); + if (!$options) { + $options = array(); + } + } + + /** + * 校验bucket参数 + * + * @param string $bucket + * @param string $errMsg + * @throws OssException + */ + private function precheckBucket($bucket, $errMsg = 'bucket is not allowed empty') + { + OssUtil::throwOssExceptionWithMessageIfEmpty($bucket, $errMsg); + } + + /** + * 校验object参数 + * + * @param string $object + * @throws OssException + */ + private function precheckObject($object) + { + OssUtil::throwOssExceptionWithMessageIfEmpty($object, "object name is empty"); + } + + /** + * 校验option restore + * + * @param string $restore + * @throws OssException + */ + private function precheckStorage($storage) + { + if (is_string($storage)) { + switch ($storage) { + case self::OSS_STORAGE_ARCHIVE: + return; + case self::OSS_STORAGE_IA: + return; + case self::OSS_STORAGE_STANDARD: + return; + default: + break; + } + } + throw new OssException('storage name is invalid'); + } + + /** + * 校验bucket,options参数 + * + * @param string $bucket + * @param string $object + * @param array $options + * @param bool $isCheckObject + */ + private function precheckCommon($bucket, $object, &$options, $isCheckObject = true) + { + if ($isCheckObject) { + $this->precheckObject($object); + } + $this->precheckOptions($options); + $this->precheckBucket($bucket); + } + + /** + * 参数校验 + * + * @param array $options + * @param string $param + * @param string $funcName + * @throws OssException + */ + private function precheckParam($options, $param, $funcName) + { + if (!isset($options[$param])) { + throw new OssException('The `' . $param . '` options is required in ' . $funcName . '().'); + } + } + + /** + * 检测md5 + * + * @param array $options + * @return bool|null + */ + private function isCheckMD5($options) + { + return $this->getValue($options, self::OSS_CHECK_MD5, false, true, true); + } + + /** + * 获取value + * + * @param array $options + * @param string $key + * @param string $default + * @param bool $isCheckEmpty + * @param bool $isCheckBool + * @return bool|null + */ + private function getValue($options, $key, $default = NULL, $isCheckEmpty = false, $isCheckBool = false) + { + $value = $default; + if (isset($options[$key])) { + if ($isCheckEmpty) { + if (!empty($options[$key])) { + $value = $options[$key]; + } + } else { + $value = $options[$key]; + } + unset($options[$key]); + } + if ($isCheckBool) { + if ($value !== true && $value !== false) { + $value = false; + } + } + return $value; + } + + /** + * 获取mimetype类型 + * + * @param string $object + * @return string + */ + private function getMimeType($object, $file = null) + { + if (!is_null($file)) { + $type = MimeTypes::getMimetype($file); + if (!is_null($type)) { + return $type; + } + } + + $type = MimeTypes::getMimetype($object); + if (!is_null($type)) { + return $type; + } + + return self::DEFAULT_CONTENT_TYPE; + } + + /** + * 验证并且执行请求,按照OSS Api协议,执行操作 + * + * @param array $options + * @return ResponseCore + * @throws OssException + * @throws RequestCore_Exception + */ + private function auth($options) + { + OssUtil::validateOptions($options); + //验证bucket,list_bucket时不需要验证 + $this->authPrecheckBucket($options); + //验证object + $this->authPrecheckObject($options); + //Object名称的编码必须是utf8 + $this->authPrecheckObjectEncoding($options); + //验证ACL + $this->authPrecheckAcl($options); + // 获得当次请求使用的协议头,是https还是http + $scheme = $this->useSSL ? 'https://' : 'http://'; + // 获得当次请求使用的hostname,如果是公共域名或者专有域名,bucket拼在前面构成三级域名 + $hostname = $this->generateHostname($options[self::OSS_BUCKET]); + $string_to_sign = ''; + $headers = $this->generateHeaders($options, $hostname); + $signable_query_string_params = $this->generateSignableQueryStringParam($options); + $signable_query_string = OssUtil::toQueryString($signable_query_string_params); + $resource_uri = $this->generateResourceUri($options); + //生成请求URL + $conjunction = '?'; + $non_signable_resource = ''; + if (isset($options[self::OSS_SUB_RESOURCE])) { + $conjunction = '&'; + } + if ($signable_query_string !== '') { + $signable_query_string = $conjunction . $signable_query_string; + $conjunction = '&'; + } + $query_string = $this->generateQueryString($options); + if ($query_string !== '') { + $non_signable_resource .= $conjunction . $query_string; + $conjunction = '&'; + } + $this->requestUrl = $scheme . $hostname . $resource_uri . $signable_query_string . $non_signable_resource; + + //创建请求 + $request = new RequestCore($this->requestUrl, $this->requestProxy); + $request->set_useragent($this->generateUserAgent()); + // Streaming uploads + if (isset($options[self::OSS_FILE_UPLOAD])) { + if (is_resource($options[self::OSS_FILE_UPLOAD])) { + $length = null; + + if (isset($options[self::OSS_CONTENT_LENGTH])) { + $length = $options[self::OSS_CONTENT_LENGTH]; + } elseif (isset($options[self::OSS_SEEK_TO])) { + $stats = fstat($options[self::OSS_FILE_UPLOAD]); + if ($stats && $stats[self::OSS_SIZE] >= 0) { + $length = $stats[self::OSS_SIZE] - (integer)$options[self::OSS_SEEK_TO]; + } + } + $request->set_read_stream($options[self::OSS_FILE_UPLOAD], $length); + } else { + $request->set_read_file($options[self::OSS_FILE_UPLOAD]); + $length = $request->read_stream_size; + if (isset($options[self::OSS_CONTENT_LENGTH])) { + $length = $options[self::OSS_CONTENT_LENGTH]; + } elseif (isset($options[self::OSS_SEEK_TO]) && isset($length)) { + $length -= (integer)$options[self::OSS_SEEK_TO]; + } + $request->set_read_stream_size($length); + } + } + if (isset($options[self::OSS_SEEK_TO])) { + $request->set_seek_position((integer)$options[self::OSS_SEEK_TO]); + } + if (isset($options[self::OSS_FILE_DOWNLOAD])) { + if (is_resource($options[self::OSS_FILE_DOWNLOAD])) { + $request->set_write_stream($options[self::OSS_FILE_DOWNLOAD]); + } else { + $request->set_write_file($options[self::OSS_FILE_DOWNLOAD]); + } + } + + if (isset($options[self::OSS_METHOD])) { + $request->set_method($options[self::OSS_METHOD]); + $string_to_sign .= $options[self::OSS_METHOD] . "\n"; + } + + if (isset($options[self::OSS_CONTENT])) { + $request->set_body($options[self::OSS_CONTENT]); + if ($headers[self::OSS_CONTENT_TYPE] === 'application/x-www-form-urlencoded') { + $headers[self::OSS_CONTENT_TYPE] = 'application/octet-stream'; + } + + $headers[self::OSS_CONTENT_LENGTH] = strlen($options[self::OSS_CONTENT]); + $headers[self::OSS_CONTENT_MD5] = base64_encode(md5($options[self::OSS_CONTENT], true)); + } + + if (isset($options[self::OSS_CALLBACK])) { + $headers[self::OSS_CALLBACK] = base64_encode($options[self::OSS_CALLBACK]); + } + if (isset($options[self::OSS_CALLBACK_VAR])) { + $headers[self::OSS_CALLBACK_VAR] = base64_encode($options[self::OSS_CALLBACK_VAR]); + } + + if (!isset($headers[self::OSS_ACCEPT_ENCODING])) { + $headers[self::OSS_ACCEPT_ENCODING] = ''; + } + + uksort($headers, 'strnatcasecmp'); + + foreach ($headers as $header_key => $header_value) { + $header_value = str_replace(array("\r", "\n"), '', $header_value); + if ($header_value !== '' || $header_key === self::OSS_ACCEPT_ENCODING) { + $request->add_header($header_key, $header_value); + } + + if ( + strtolower($header_key) === 'content-md5' || + strtolower($header_key) === 'content-type' || + strtolower($header_key) === 'date' || + (isset($options['self::OSS_PREAUTH']) && (integer)$options['self::OSS_PREAUTH'] > 0) + ) { + $string_to_sign .= $header_value . "\n"; + } elseif (substr(strtolower($header_key), 0, 6) === self::OSS_DEFAULT_PREFIX) { + $string_to_sign .= strtolower($header_key) . ':' . $header_value . "\n"; + } + } + // 生成 signable_resource + $signable_resource = $this->generateSignableResource($options); + $string_to_sign .= rawurldecode($signable_resource) . urldecode($signable_query_string); + + //对?后面的要签名的string字母序排序 + $string_to_sign_ordered = $this->stringToSignSorted($string_to_sign); + + $signature = base64_encode(hash_hmac('sha1', $string_to_sign_ordered, $this->accessKeySecret, true)); + $request->add_header('Authorization', 'OSS ' . $this->accessKeyId . ':' . $signature); + + if (isset($options[self::OSS_PREAUTH]) && (integer)$options[self::OSS_PREAUTH] > 0) { + $signed_url = $this->requestUrl . $conjunction . self::OSS_URL_ACCESS_KEY_ID . '=' . rawurlencode($this->accessKeyId) . '&' . self::OSS_URL_EXPIRES . '=' . $options[self::OSS_PREAUTH] . '&' . self::OSS_URL_SIGNATURE . '=' . rawurlencode($signature); + return $signed_url; + } elseif (isset($options[self::OSS_PREAUTH])) { + return $this->requestUrl; + } + + if ($this->timeout !== 0) { + $request->timeout = $this->timeout; + } + if ($this->connectTimeout !== 0) { + $request->connect_timeout = $this->connectTimeout; + } + + try { + $request->send_request(); + } catch (RequestCore_Exception $e) { + throw(new OssException('RequestCoreException: ' . $e->getMessage())); + } + $response_header = $request->get_response_header(); + $response_header['oss-request-url'] = $this->requestUrl; + $response_header['oss-redirects'] = $this->redirects; + $response_header['oss-stringtosign'] = $string_to_sign; + $response_header['oss-requestheaders'] = $request->request_headers; + + $data = new ResponseCore($response_header, $request->get_response_body(), $request->get_response_code()); + //retry if OSS Internal Error + if ((integer)$request->get_response_code() === 500) { + if ($this->redirects <= $this->maxRetries) { + //设置休眠 + $delay = (integer)(pow(4, $this->redirects) * 100000); + usleep($delay); + $this->redirects++; + $data = $this->auth($options); + } + } + + $this->redirects = 0; + return $data; + } + + /** + * 设置最大尝试次数 + * + * @param int $maxRetries + * @return void + */ + public function setMaxTries($maxRetries = 3) + { + $this->maxRetries = $maxRetries; + } + + /** + * 获取最大尝试次数 + * + * @return int + */ + public function getMaxRetries() + { + return $this->maxRetries; + } + + /** + * 打开sts enable标志,使用户构造函数中传入的$sts生效 + * + * @param boolean $enable + */ + public function setSignStsInUrl($enable) + { + $this->enableStsInUrl = $enable; + } + + /** + * @return boolean + */ + public function isUseSSL() + { + return $this->useSSL; + } + + /** + * @param boolean $useSSL + */ + public function setUseSSL($useSSL) + { + $this->useSSL = $useSSL; + } + + /** + * 检查bucket名称格式是否正确,如果非法抛出异常 + * + * @param $options + * @throws OssException + */ + private function authPrecheckBucket($options) + { + if (!(('/' == $options[self::OSS_OBJECT]) && ('' == $options[self::OSS_BUCKET]) && ('GET' == $options[self::OSS_METHOD])) && !OssUtil::validateBucket($options[self::OSS_BUCKET])) { + throw new OssException('"' . $options[self::OSS_BUCKET] . '"' . 'bucket name is invalid'); + } + } + + /** + * + * 检查object名称格式是否正确,如果非法抛出异常 + * + * @param $options + * @throws OssException + */ + private function authPrecheckObject($options) + { + if (isset($options[self::OSS_OBJECT]) && $options[self::OSS_OBJECT] === '/') { + return; + } + + if (isset($options[self::OSS_OBJECT]) && !OssUtil::validateObject($options[self::OSS_OBJECT])) { + throw new OssException('"' . $options[self::OSS_OBJECT] . '"' . ' object name is invalid'); + } + } + + /** + * 检查object的编码,如果是gbk或者gb2312则尝试将其转化为utf8编码 + * + * @param mixed $options 参数 + */ + private function authPrecheckObjectEncoding(&$options) + { + $tmp_object = $options[self::OSS_OBJECT]; + try { + if (OssUtil::isGb2312($options[self::OSS_OBJECT])) { + $options[self::OSS_OBJECT] = iconv('GB2312', "UTF-8//IGNORE", $options[self::OSS_OBJECT]); + } elseif (OssUtil::checkChar($options[self::OSS_OBJECT], true)) { + $options[self::OSS_OBJECT] = iconv('GBK', "UTF-8//IGNORE", $options[self::OSS_OBJECT]); + } + } catch (\Exception $e) { + try { + $tmp_object = iconv(mb_detect_encoding($tmp_object), "UTF-8", $tmp_object); + } catch (\Exception $e) { + } + } + $options[self::OSS_OBJECT] = $tmp_object; + } + + /** + * 检查ACL是否是预定义中三种之一,如果不是抛出异常 + * + * @param $options + * @throws OssException + */ + private function authPrecheckAcl($options) + { + if (isset($options[self::OSS_HEADERS][self::OSS_ACL]) && !empty($options[self::OSS_HEADERS][self::OSS_ACL])) { + if (!in_array(strtolower($options[self::OSS_HEADERS][self::OSS_ACL]), self::$OSS_ACL_TYPES)) { + throw new OssException($options[self::OSS_HEADERS][self::OSS_ACL] . ':' . 'acl is invalid(private,public-read,public-read-write)'); + } + } + } + + /** + * 获得档次请求使用的域名 + * bucket在前的三级域名,或者二级域名,如果是cname或者ip的话,则是二级域名 + * + * @param $bucket + * @return string 剥掉协议头的域名 + */ + private function generateHostname($bucket) + { + if ($this->hostType === self::OSS_HOST_TYPE_IP) { + $hostname = $this->hostname; + } elseif ($this->hostType === self::OSS_HOST_TYPE_CNAME) { + $hostname = $this->hostname; + } else { + // 专有域或者官网endpoint + $hostname = ($bucket == '') ? $this->hostname : ($bucket . '.') . $this->hostname; + } + return $hostname; + } + + /** + * 获得当次请求的资源定位字段 + * + * @param $options + * @return string 资源定位字段 + */ + private function generateResourceUri($options) + { + $resource_uri = ""; + + // resource_uri + bucket + if (isset($options[self::OSS_BUCKET]) && '' !== $options[self::OSS_BUCKET]) { + if ($this->hostType === self::OSS_HOST_TYPE_IP) { + $resource_uri = '/' . $options[self::OSS_BUCKET]; + } + } + + // resource_uri + object + if (isset($options[self::OSS_OBJECT]) && '/' !== $options[self::OSS_OBJECT]) { + $resource_uri .= '/' . str_replace(array('%2F', '%25'), array('/', '%'), rawurlencode($options[self::OSS_OBJECT])); + } + + // resource_uri + sub_resource + $conjunction = '?'; + if (isset($options[self::OSS_SUB_RESOURCE])) { + $resource_uri .= $conjunction . $options[self::OSS_SUB_RESOURCE]; + } + return $resource_uri; + } + + /** + * 生成signalbe_query_string_param, array类型 + * + * @param array $options + * @return array + */ + private function generateSignableQueryStringParam($options) + { + $signableQueryStringParams = array(); + $signableList = array( + self::OSS_PART_NUM, + 'response-content-type', + 'response-content-language', + 'response-cache-control', + 'response-content-encoding', + 'response-expires', + 'response-content-disposition', + self::OSS_UPLOAD_ID, + self::OSS_COMP, + self::OSS_LIVE_CHANNEL_STATUS, + self::OSS_LIVE_CHANNEL_START_TIME, + self::OSS_LIVE_CHANNEL_END_TIME, + self::OSS_PROCESS, + self::OSS_POSITION, + self::OSS_SYMLINK, + self::OSS_RESTORE, + ); + + foreach ($signableList as $item) { + if (isset($options[$item])) { + $signableQueryStringParams[$item] = $options[$item]; + } + } + + if ($this->enableStsInUrl && (!is_null($this->securityToken))) { + $signableQueryStringParams["security-token"] = $this->securityToken; + } + + return $signableQueryStringParams; + } + + /** + * 生成用于签名resource段 + * + * @param mixed $options + * @return string + */ + private function generateSignableResource($options) + { + $signableResource = ""; + $signableResource .= '/'; + if (isset($options[self::OSS_BUCKET]) && '' !== $options[self::OSS_BUCKET]) { + $signableResource .= $options[self::OSS_BUCKET]; + // 如果操作没有Object操作的话,这里最后是否有斜线有个trick,ip的域名下,不需要加'/', 否则需要加'/' + if ($options[self::OSS_OBJECT] == '/') { + if ($this->hostType !== self::OSS_HOST_TYPE_IP) { + $signableResource .= "/"; + } + } + } + //signable_resource + object + if (isset($options[self::OSS_OBJECT]) && '/' !== $options[self::OSS_OBJECT]) { + $signableResource .= '/' . str_replace(array('%2F', '%25'), array('/', '%'), rawurlencode($options[self::OSS_OBJECT])); + } + if (isset($options[self::OSS_SUB_RESOURCE])) { + $signableResource .= '?' . $options[self::OSS_SUB_RESOURCE]; + } + return $signableResource; + } + + /** + * 生成query_string + * + * @param mixed $options + * @return string + */ + private function generateQueryString($options) + { + //请求参数 + $queryStringParams = array(); + if (isset($options[self::OSS_QUERY_STRING])) { + $queryStringParams = array_merge($queryStringParams, $options[self::OSS_QUERY_STRING]); + } + return OssUtil::toQueryString($queryStringParams); + } + + private function stringToSignSorted($string_to_sign) + { + $queryStringSorted = ''; + $explodeResult = explode('?', $string_to_sign); + $index = count($explodeResult); + if ($index === 1) + return $string_to_sign; + + $queryStringParams = explode('&', $explodeResult[$index - 1]); + sort($queryStringParams); + + foreach($queryStringParams as $params) + { + $queryStringSorted .= $params . '&'; + } + + $queryStringSorted = substr($queryStringSorted, 0, -1); + + return $explodeResult[0] . '?' . $queryStringSorted; + } + + /** + * 初始化headers + * + * @param mixed $options + * @param string $hostname hostname + * @return array + */ + private function generateHeaders($options, $hostname) + { + $headers = array( + self::OSS_CONTENT_MD5 => '', + self::OSS_CONTENT_TYPE => isset($options[self::OSS_CONTENT_TYPE]) ? $options[self::OSS_CONTENT_TYPE] : self::DEFAULT_CONTENT_TYPE, + self::OSS_DATE => isset($options[self::OSS_DATE]) ? $options[self::OSS_DATE] : gmdate('D, d M Y H:i:s \G\M\T'), + self::OSS_HOST => $hostname, + ); + if (isset($options[self::OSS_CONTENT_MD5])) { + $headers[self::OSS_CONTENT_MD5] = $options[self::OSS_CONTENT_MD5]; + } + + //添加stsSecurityToken + if ((!is_null($this->securityToken)) && (!$this->enableStsInUrl)) { + $headers[self::OSS_SECURITY_TOKEN] = $this->securityToken; + } + //合并HTTP headers + if (isset($options[self::OSS_HEADERS])) { + $headers = array_merge($headers, $options[self::OSS_HEADERS]); + } + return $headers; + } + + /** + * 生成请求用的UserAgent + * + * @return string + */ + private function generateUserAgent() + { + return self::OSS_NAME . "/" . self::OSS_VERSION . " (" . php_uname('s') . "/" . php_uname('r') . "/" . php_uname('m') . ";" . PHP_VERSION . ")"; + } + + /** + * 检查endpoint的种类 + * 如有有协议头,剥去协议头 + * 并且根据参数 is_cname 和endpoint本身,判定域名类型,是ip,cname,还是专有域或者官网域名 + * + * @param string $endpoint + * @param boolean $isCName + * @return string 剥掉协议头的域名 + */ + private function checkEndpoint($endpoint, $isCName) + { + $ret_endpoint = null; + if (strpos($endpoint, 'http://') === 0) { + $ret_endpoint = substr($endpoint, strlen('http://')); + } elseif (strpos($endpoint, 'https://') === 0) { + $ret_endpoint = substr($endpoint, strlen('https://')); + $this->useSSL = true; + } else { + $ret_endpoint = $endpoint; + } + + if ($isCName) { + $this->hostType = self::OSS_HOST_TYPE_CNAME; + } elseif (OssUtil::isIPFormat($ret_endpoint)) { + $this->hostType = self::OSS_HOST_TYPE_IP; + } else { + $this->hostType = self::OSS_HOST_TYPE_NORMAL; + } + return $ret_endpoint; + } + + /** + * 用来检查sdk所以来的扩展是否打开 + * + * @throws OssException + */ + public static function checkEnv() + { + if (function_exists('get_loaded_extensions')) { + //检测curl扩展 + $enabled_extension = array("curl"); + $extensions = get_loaded_extensions(); + if ($extensions) { + foreach ($enabled_extension as $item) { + if (!in_array($item, $extensions)) { + throw new OssException("Extension {" . $item . "} is not installed or not enabled, please check your php env."); + } + } + } else { + throw new OssException("function get_loaded_extensions not found."); + } + } else { + throw new OssException('Function get_loaded_extensions has been disabled, please check php config.'); + } + } + + /** + //* 设置http库的请求超时时间,单位秒 + * + * @param int $timeout + */ + public function setTimeout($timeout) + { + $this->timeout = $timeout; + } + + /** + * 设置http库的连接超时时间,单位秒 + * + * @param int $connectTimeout + */ + public function setConnectTimeout($connectTimeout) + { + $this->connectTimeout = $connectTimeout; + } + + // 生命周期相关常量 + const OSS_LIFECYCLE_EXPIRATION = "Expiration"; + const OSS_LIFECYCLE_TIMING_DAYS = "Days"; + const OSS_LIFECYCLE_TIMING_DATE = "Date"; + //OSS 内部常量 + const OSS_BUCKET = 'bucket'; + const OSS_OBJECT = 'object'; + const OSS_HEADERS = OssUtil::OSS_HEADERS; + const OSS_METHOD = 'method'; + const OSS_QUERY = 'query'; + const OSS_BASENAME = 'basename'; + const OSS_MAX_KEYS = 'max-keys'; + const OSS_UPLOAD_ID = 'uploadId'; + const OSS_PART_NUM = 'partNumber'; + const OSS_COMP = 'comp'; + const OSS_LIVE_CHANNEL_STATUS = 'status'; + const OSS_LIVE_CHANNEL_START_TIME = 'startTime'; + const OSS_LIVE_CHANNEL_END_TIME = 'endTime'; + const OSS_POSITION = 'position'; + const OSS_MAX_KEYS_VALUE = 100; + const OSS_MAX_OBJECT_GROUP_VALUE = OssUtil::OSS_MAX_OBJECT_GROUP_VALUE; + const OSS_MAX_PART_SIZE = OssUtil::OSS_MAX_PART_SIZE; + const OSS_MID_PART_SIZE = OssUtil::OSS_MID_PART_SIZE; + const OSS_MIN_PART_SIZE = OssUtil::OSS_MIN_PART_SIZE; + const OSS_FILE_SLICE_SIZE = 8192; + const OSS_PREFIX = 'prefix'; + const OSS_DELIMITER = 'delimiter'; + const OSS_MARKER = 'marker'; + const OSS_ACCEPT_ENCODING = 'Accept-Encoding'; + const OSS_CONTENT_MD5 = 'Content-Md5'; + const OSS_SELF_CONTENT_MD5 = 'x-oss-meta-md5'; + const OSS_CONTENT_TYPE = 'Content-Type'; + const OSS_CONTENT_LENGTH = 'Content-Length'; + const OSS_IF_MODIFIED_SINCE = 'If-Modified-Since'; + const OSS_IF_UNMODIFIED_SINCE = 'If-Unmodified-Since'; + const OSS_IF_MATCH = 'If-Match'; + const OSS_IF_NONE_MATCH = 'If-None-Match'; + const OSS_CACHE_CONTROL = 'Cache-Control'; + const OSS_EXPIRES = 'Expires'; + const OSS_PREAUTH = 'preauth'; + const OSS_CONTENT_COING = 'Content-Coding'; + const OSS_CONTENT_DISPOSTION = 'Content-Disposition'; + const OSS_RANGE = 'range'; + const OSS_ETAG = 'etag'; + const OSS_LAST_MODIFIED = 'lastmodified'; + const OS_CONTENT_RANGE = 'Content-Range'; + const OSS_CONTENT = OssUtil::OSS_CONTENT; + const OSS_BODY = 'body'; + const OSS_LENGTH = OssUtil::OSS_LENGTH; + const OSS_HOST = 'Host'; + const OSS_DATE = 'Date'; + const OSS_AUTHORIZATION = 'Authorization'; + const OSS_FILE_DOWNLOAD = 'fileDownload'; + const OSS_FILE_UPLOAD = 'fileUpload'; + const OSS_PART_SIZE = 'partSize'; + const OSS_SEEK_TO = 'seekTo'; + const OSS_SIZE = 'size'; + const OSS_QUERY_STRING = 'query_string'; + const OSS_SUB_RESOURCE = 'sub_resource'; + const OSS_DEFAULT_PREFIX = 'x-oss-'; + const OSS_CHECK_MD5 = 'checkmd5'; + const DEFAULT_CONTENT_TYPE = 'application/octet-stream'; + const OSS_SYMLINK_TARGET = 'x-oss-symlink-target'; + const OSS_SYMLINK = 'symlink'; + const OSS_HTTP_CODE = 'http_code'; + const OSS_REQUEST_ID = 'x-oss-request-id'; + const OSS_INFO = 'info'; + const OSS_STORAGE = 'storage'; + const OSS_RESTORE = 'restore'; + const OSS_STORAGE_STANDARD = 'Standard'; + const OSS_STORAGE_IA = 'IA'; + const OSS_STORAGE_ARCHIVE = 'Archive'; + + //私有URL变量 + const OSS_URL_ACCESS_KEY_ID = 'OSSAccessKeyId'; + const OSS_URL_EXPIRES = 'Expires'; + const OSS_URL_SIGNATURE = 'Signature'; + //HTTP方法 + const OSS_HTTP_GET = 'GET'; + const OSS_HTTP_PUT = 'PUT'; + const OSS_HTTP_HEAD = 'HEAD'; + const OSS_HTTP_POST = 'POST'; + const OSS_HTTP_DELETE = 'DELETE'; + const OSS_HTTP_OPTIONS = 'OPTIONS'; + //其他常量 + const OSS_ACL = 'x-oss-acl'; + const OSS_OBJECT_ACL = 'x-oss-object-acl'; + const OSS_OBJECT_GROUP = 'x-oss-file-group'; + const OSS_MULTI_PART = 'uploads'; + const OSS_MULTI_DELETE = 'delete'; + const OSS_OBJECT_COPY_SOURCE = 'x-oss-copy-source'; + const OSS_OBJECT_COPY_SOURCE_RANGE = "x-oss-copy-source-range"; + const OSS_PROCESS = "x-oss-process"; + const OSS_CALLBACK = "x-oss-callback"; + const OSS_CALLBACK_VAR = "x-oss-callback-var"; + //支持STS SecurityToken + const OSS_SECURITY_TOKEN = "x-oss-security-token"; + const OSS_ACL_TYPE_PRIVATE = 'private'; + const OSS_ACL_TYPE_PUBLIC_READ = 'public-read'; + const OSS_ACL_TYPE_PUBLIC_READ_WRITE = 'public-read-write'; + const OSS_ENCODING_TYPE = "encoding-type"; + const OSS_ENCODING_TYPE_URL = "url"; + + // 域名类型 + const OSS_HOST_TYPE_NORMAL = "normal";//http://bucket.oss-cn-hangzhou.aliyuncs.com/object + const OSS_HOST_TYPE_IP = "ip"; //http://1.1.1.1/bucket/object + const OSS_HOST_TYPE_SPECIAL = 'special'; //http://bucket.guizhou.gov/object + const OSS_HOST_TYPE_CNAME = "cname"; //http://mydomain.com/object + //OSS ACL数组 + static $OSS_ACL_TYPES = array( + self::OSS_ACL_TYPE_PRIVATE, + self::OSS_ACL_TYPE_PUBLIC_READ, + self::OSS_ACL_TYPE_PUBLIC_READ_WRITE + ); + // OssClient版本信息 + const OSS_NAME = "aliyun-sdk-php"; + const OSS_VERSION = "2.3.0"; + const OSS_BUILD = "20180105"; + const OSS_AUTHOR = ""; + const OSS_OPTIONS_ORIGIN = 'Origin'; + const OSS_OPTIONS_REQUEST_METHOD = 'Access-Control-Request-Method'; + const OSS_OPTIONS_REQUEST_HEADERS = 'Access-Control-Request-Headers'; + + //是否使用ssl + private $useSSL = false; + private $maxRetries = 3; + private $redirects = 0; + + // 用户提供的域名类型,有四种 OSS_HOST_TYPE_NORMAL, OSS_HOST_TYPE_IP, OSS_HOST_TYPE_SPECIAL, OSS_HOST_TYPE_CNAME + private $hostType = self::OSS_HOST_TYPE_NORMAL; + private $requestUrl; + private $accessKeyId; + private $accessKeySecret; + private $hostname; + private $securityToken; + private $requestProxy = null; + private $enableStsInUrl = false; + private $timeout = 0; + private $connectTimeout = 0; +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/AclResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/AclResult.php new file mode 100644 index 0000000..6da0860 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/AclResult.php @@ -0,0 +1,32 @@ +rawResponse->body; + if (empty($content)) { + throw new OssException("body is null"); + } + $xml = simplexml_load_string($content); + if (isset($xml->AccessControlList->Grant)) { + return strval($xml->AccessControlList->Grant); + } else { + throw new OssException("xml format exception"); + } + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/AppendResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/AppendResult.php new file mode 100644 index 0000000..433c03e --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/AppendResult.php @@ -0,0 +1,27 @@ +rawResponse->header; + if (isset($header["x-oss-next-append-position"])) { + return intval($header["x-oss-next-append-position"]); + } + throw new OssException("cannot get next-append-position"); + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/BodyResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/BodyResult.php new file mode 100644 index 0000000..44ba15e --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/BodyResult.php @@ -0,0 +1,19 @@ +rawResponse->body) ? "" : $this->rawResponse->body; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/CallbackResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/CallbackResult.php new file mode 100644 index 0000000..514e985 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/CallbackResult.php @@ -0,0 +1,21 @@ +rawResponse->status; + if ((int)(intval($status) / 100) == 2 && (int)(intval($status)) !== 203) { + return true; + } + return false; + } + +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/CopyObjectResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/CopyObjectResult.php new file mode 100644 index 0000000..498723e --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/CopyObjectResult.php @@ -0,0 +1,30 @@ +rawResponse->body; + $xml = simplexml_load_string($body); + $result = array(); + + if (isset($xml->LastModified)) { + $result[] = $xml->LastModified; + } + if (isset($xml->ETag)) { + $result[] = $xml->ETag; + } + + return $result; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/DeleteObjectsResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/DeleteObjectsResult.php new file mode 100644 index 0000000..dc373b8 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/DeleteObjectsResult.php @@ -0,0 +1,27 @@ +rawResponse->body; + $xml = simplexml_load_string($body); + $objects = array(); + + if (isset($xml->Deleted)) { + foreach($xml->Deleted as $deleteKey) + $objects[] = $deleteKey->Key; + } + return $objects; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ExistResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ExistResult.php new file mode 100644 index 0000000..f7aa287 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ExistResult.php @@ -0,0 +1,35 @@ +rawResponse->status) === 200 ? true : false; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 判断是否存在的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetCnameResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetCnameResult.php new file mode 100644 index 0000000..eed01f9 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetCnameResult.php @@ -0,0 +1,19 @@ +rawResponse->body; + $config = new CnameConfig(); + $config->parseFromXml($content); + return $config; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetCorsResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetCorsResult.php new file mode 100644 index 0000000..a51afe2 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetCorsResult.php @@ -0,0 +1,35 @@ +rawResponse->body; + $config = new CorsConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLifecycleResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLifecycleResult.php new file mode 100644 index 0000000..6b440c3 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLifecycleResult.php @@ -0,0 +1,41 @@ +rawResponse->body; + $config = new LifecycleConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelHistoryResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelHistoryResult.php new file mode 100644 index 0000000..202a668 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelHistoryResult.php @@ -0,0 +1,19 @@ +rawResponse->body; + $channelList = new GetLiveChannelHistory(); + $channelList->parseFromXml($content); + return $channelList; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelInfoResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelInfoResult.php new file mode 100644 index 0000000..d5a9005 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelInfoResult.php @@ -0,0 +1,19 @@ +rawResponse->body; + $channelList = new GetLiveChannelInfo(); + $channelList->parseFromXml($content); + return $channelList; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelStatusResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelStatusResult.php new file mode 100644 index 0000000..6b8a60f --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLiveChannelStatusResult.php @@ -0,0 +1,19 @@ +rawResponse->body; + $channelList = new GetLiveChannelStatus(); + $channelList->parseFromXml($content); + return $channelList; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLocationResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLocationResult.php new file mode 100644 index 0000000..71c4c96 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLocationResult.php @@ -0,0 +1,30 @@ +rawResponse->body; + if (empty($content)) { + throw new OssException("body is null"); + } + $xml = simplexml_load_string($content); + return $xml; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLoggingResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLoggingResult.php new file mode 100644 index 0000000..72fc3ae --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetLoggingResult.php @@ -0,0 +1,41 @@ +rawResponse->body; + $config = new LoggingConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetRefererResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetRefererResult.php new file mode 100644 index 0000000..aee50d3 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetRefererResult.php @@ -0,0 +1,41 @@ +rawResponse->body; + $config = new RefererConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetStorageCapacityResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetStorageCapacityResult.php new file mode 100644 index 0000000..84e4916 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetStorageCapacityResult.php @@ -0,0 +1,34 @@ +rawResponse->body; + if (empty($content)) { + throw new OssException("body is null"); + } + $xml = simplexml_load_string($content); + if (isset($xml->StorageCapacity)) { + return intval($xml->StorageCapacity); + } else { + throw new OssException("xml format exception"); + } + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetWebsiteResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetWebsiteResult.php new file mode 100644 index 0000000..3099172 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/GetWebsiteResult.php @@ -0,0 +1,40 @@ +rawResponse->body; + $config = new WebsiteConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/HeaderResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/HeaderResult.php new file mode 100644 index 0000000..c9aae56 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/HeaderResult.php @@ -0,0 +1,23 @@ +rawResponse->header) ? array() : $this->rawResponse->header; + } + +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/InitiateMultipartUploadResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/InitiateMultipartUploadResult.php new file mode 100644 index 0000000..af985f2 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/InitiateMultipartUploadResult.php @@ -0,0 +1,29 @@ +rawResponse->body; + $xml = simplexml_load_string($content); + if (isset($xml->UploadId)) { + return strval($xml->UploadId); + } + throw new OssException("cannot get UploadId"); + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListBucketsResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListBucketsResult.php new file mode 100644 index 0000000..a58fb2d --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListBucketsResult.php @@ -0,0 +1,33 @@ +rawResponse->body; + $xml = new \SimpleXMLElement($content); + if (isset($xml->Buckets) && isset($xml->Buckets->Bucket)) { + foreach ($xml->Buckets->Bucket as $bucket) { + $bucketInfo = new BucketInfo(strval($bucket->Location), + strval($bucket->Name), + strval($bucket->CreationDate)); + $bucketList[] = $bucketInfo; + } + } + return new BucketListInfo($bucketList); + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListLiveChannelResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListLiveChannelResult.php new file mode 100644 index 0000000..1a6e2a4 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListLiveChannelResult.php @@ -0,0 +1,16 @@ +rawResponse->body; + $channelList = new LiveChannelListInfo(); + $channelList->parseFromXml($content); + return $channelList; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListMultipartUploadResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListMultipartUploadResult.php new file mode 100644 index 0000000..bcb20bf --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListMultipartUploadResult.php @@ -0,0 +1,55 @@ +rawResponse->body; + $xml = simplexml_load_string($content); + + $encodingType = isset($xml->EncodingType) ? strval($xml->EncodingType) : ""; + $bucket = isset($xml->Bucket) ? strval($xml->Bucket) : ""; + $keyMarker = isset($xml->KeyMarker) ? strval($xml->KeyMarker) : ""; + $keyMarker = OssUtil::decodeKey($keyMarker, $encodingType); + $uploadIdMarker = isset($xml->UploadIdMarker) ? strval($xml->UploadIdMarker) : ""; + $nextKeyMarker = isset($xml->NextKeyMarker) ? strval($xml->NextKeyMarker) : ""; + $nextKeyMarker = OssUtil::decodeKey($nextKeyMarker, $encodingType); + $nextUploadIdMarker = isset($xml->NextUploadIdMarker) ? strval($xml->NextUploadIdMarker) : ""; + $delimiter = isset($xml->Delimiter) ? strval($xml->Delimiter) : ""; + $delimiter = OssUtil::decodeKey($delimiter, $encodingType); + $prefix = isset($xml->Prefix) ? strval($xml->Prefix) : ""; + $prefix = OssUtil::decodeKey($prefix, $encodingType); + $maxUploads = isset($xml->MaxUploads) ? intval($xml->MaxUploads) : 0; + $isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : ""; + $listUpload = array(); + + if (isset($xml->Upload)) { + foreach ($xml->Upload as $upload) { + $key = isset($upload->Key) ? strval($upload->Key) : ""; + $key = OssUtil::decodeKey($key, $encodingType); + $uploadId = isset($upload->UploadId) ? strval($upload->UploadId) : ""; + $initiated = isset($upload->Initiated) ? strval($upload->Initiated) : ""; + $listUpload[] = new UploadInfo($key, $uploadId, $initiated); + } + } + return new ListMultipartUploadInfo($bucket, $keyMarker, $uploadIdMarker, + $nextKeyMarker, $nextUploadIdMarker, + $delimiter, $prefix, $maxUploads, $isTruncated, $listUpload); + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListObjectsResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListObjectsResult.php new file mode 100644 index 0000000..fcf493d --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListObjectsResult.php @@ -0,0 +1,71 @@ +rawResponse->body); + $encodingType = isset($xml->EncodingType) ? strval($xml->EncodingType) : ""; + $objectList = $this->parseObjectList($xml, $encodingType); + $prefixList = $this->parsePrefixList($xml, $encodingType); + $bucketName = isset($xml->Name) ? strval($xml->Name) : ""; + $prefix = isset($xml->Prefix) ? strval($xml->Prefix) : ""; + $prefix = OssUtil::decodeKey($prefix, $encodingType); + $marker = isset($xml->Marker) ? strval($xml->Marker) : ""; + $marker = OssUtil::decodeKey($marker, $encodingType); + $maxKeys = isset($xml->MaxKeys) ? intval($xml->MaxKeys) : 0; + $delimiter = isset($xml->Delimiter) ? strval($xml->Delimiter) : ""; + $delimiter = OssUtil::decodeKey($delimiter, $encodingType); + $isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : ""; + $nextMarker = isset($xml->NextMarker) ? strval($xml->NextMarker) : ""; + $nextMarker = OssUtil::decodeKey($nextMarker, $encodingType); + return new ObjectListInfo($bucketName, $prefix, $marker, $nextMarker, $maxKeys, $delimiter, $isTruncated, $objectList, $prefixList); + } + + private function parseObjectList($xml, $encodingType) + { + $retList = array(); + if (isset($xml->Contents)) { + foreach ($xml->Contents as $content) { + $key = isset($content->Key) ? strval($content->Key) : ""; + $key = OssUtil::decodeKey($key, $encodingType); + $lastModified = isset($content->LastModified) ? strval($content->LastModified) : ""; + $eTag = isset($content->ETag) ? strval($content->ETag) : ""; + $type = isset($content->Type) ? strval($content->Type) : ""; + $size = isset($content->Size) ? intval($content->Size) : 0; + $storageClass = isset($content->StorageClass) ? strval($content->StorageClass) : ""; + $retList[] = new ObjectInfo($key, $lastModified, $eTag, $type, $size, $storageClass); + } + } + return $retList; + } + + private function parsePrefixList($xml, $encodingType) + { + $retList = array(); + if (isset($xml->CommonPrefixes)) { + foreach ($xml->CommonPrefixes as $commonPrefix) { + $prefix = isset($commonPrefix->Prefix) ? strval($commonPrefix->Prefix) : ""; + $prefix = OssUtil::decodeKey($prefix, $encodingType); + $retList[] = new PrefixInfo($prefix); + } + } + return $retList; + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListPartsResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListPartsResult.php new file mode 100644 index 0000000..fd8a1b8 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/ListPartsResult.php @@ -0,0 +1,42 @@ +rawResponse->body; + $xml = simplexml_load_string($content); + $bucket = isset($xml->Bucket) ? strval($xml->Bucket) : ""; + $key = isset($xml->Key) ? strval($xml->Key) : ""; + $uploadId = isset($xml->UploadId) ? strval($xml->UploadId) : ""; + $nextPartNumberMarker = isset($xml->NextPartNumberMarker) ? intval($xml->NextPartNumberMarker) : ""; + $maxParts = isset($xml->MaxParts) ? intval($xml->MaxParts) : ""; + $isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : ""; + $partList = array(); + if (isset($xml->Part)) { + foreach ($xml->Part as $part) { + $partNumber = isset($part->PartNumber) ? intval($part->PartNumber) : ""; + $lastModified = isset($part->LastModified) ? strval($part->LastModified) : ""; + $eTag = isset($part->ETag) ? strval($part->ETag) : ""; + $size = isset($part->Size) ? intval($part->Size) : ""; + $partList[] = new PartInfo($partNumber, $lastModified, $eTag, $size); + } + } + return new ListPartsInfo($bucket, $key, $uploadId, $nextPartNumberMarker, $maxParts, $isTruncated, $partList); + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/PutLiveChannelResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/PutLiveChannelResult.php new file mode 100644 index 0000000..dcac86b --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/PutLiveChannelResult.php @@ -0,0 +1,16 @@ +rawResponse->body; + $channel = new LiveChannelInfo(); + $channel->parseFromXml($content); + return $channel; + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/PutSetDeleteResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/PutSetDeleteResult.php new file mode 100644 index 0000000..97af003 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/PutSetDeleteResult.php @@ -0,0 +1,20 @@ + $this->rawResponse->body); + return array_merge($this->rawResponse->header, $body); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/Result.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/Result.php new file mode 100644 index 0000000..491256f --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/Result.php @@ -0,0 +1,175 @@ +rawResponse = $response; + $this->parseResponse(); + } + + /** + * 获取requestId + * + * @return string + */ + public function getRequestId() + { + if (isset($this->rawResponse) && + isset($this->rawResponse->header) && + isset($this->rawResponse->header['x-oss-request-id']) + ) { + return $this->rawResponse->header['x-oss-request-id']; + } else { + return ''; + } + } + + /** + * 得到返回数据,不同的请求返回数据格式不同 + * + * $return mixed + */ + public function getData() + { + return $this->parsedData; + } + + /** + * 由子类实现,不同的请求返回数据有不同的解析逻辑,由子类实现 + * + * @return mixed + */ + abstract protected function parseDataFromResponse(); + + /** + * 操作是否成功 + * + * @return mixed + */ + public function isOK() + { + return $this->isOk; + } + + /** + * @throws OssException + */ + public function parseResponse() + { + $this->isOk = $this->isResponseOk(); + if ($this->isOk) { + $this->parsedData = $this->parseDataFromResponse(); + } else { + $httpStatus = strval($this->rawResponse->status); + $requestId = strval($this->getRequestId()); + $code = $this->retrieveErrorCode($this->rawResponse->body); + $message = $this->retrieveErrorMessage($this->rawResponse->body); + $body = $this->rawResponse->body; + + $details = array( + 'status' => $httpStatus, + 'request-id' => $requestId, + 'code' => $code, + 'message' => $message, + 'body' => $body + ); + throw new OssException($details); + } + } + + /** + * 尝试从body中获取错误Message + * + * @param $body + * @return string + */ + private function retrieveErrorMessage($body) + { + if (empty($body) || false === strpos($body, 'Message)) { + return strval($xml->Message); + } + return ''; + } + + /** + * 尝试从body中获取错误Code + * + * @param $body + * @return string + */ + private function retrieveErrorCode($body) + { + if (empty($body) || false === strpos($body, 'Code)) { + return strval($xml->Code); + } + return ''; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2) { + return true; + } + return false; + } + + /** + * 返回原始的返回数据 + * + * @return ResponseCore + */ + public function getRawResponse() + { + return $this->rawResponse; + } + + /** + * 标示请求是否成功 + */ + protected $isOk = false; + /** + * 由子类解析过的数据 + */ + protected $parsedData = null; + /** + * 存放auth函数返回的原始Response + * + * @var ResponseCore + */ + protected $rawResponse; +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/SymlinkResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/SymlinkResult.php new file mode 100644 index 0000000..9c6d861 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/SymlinkResult.php @@ -0,0 +1,24 @@ +rawResponse->header[OssClient::OSS_SYMLINK_TARGET] = rawurldecode($this->rawResponse->header[OssClient::OSS_SYMLINK_TARGET]); + return $this->rawResponse->header; + } +} + diff --git a/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/UploadPartResult.php b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/UploadPartResult.php new file mode 100644 index 0000000..c6b66d4 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/src/OSS/Result/UploadPartResult.php @@ -0,0 +1,28 @@ +rawResponse->header; + if (isset($header["etag"])) { + return $header["etag"]; + } + throw new OssException("cannot get ETag"); + + } +} \ No newline at end of file diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/AclResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/AclResultTest.php new file mode 100644 index 0000000..12f4b1a --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/AclResultTest.php @@ -0,0 +1,59 @@ + + + + 00220120222 + user_example + + + public-read + + +BBBB; + + private $invalidXml = << + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new AclResult($response); + $this->assertEquals("public-read", $result->getData()); + } + + public function testParseNullXml() + { + $response = new ResponseCore(array(), "", 200); + try { + new AclResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals('body is null', $e->getMessage()); + } + } + + public function testParseInvalidXml() + { + $response = new ResponseCore(array(), $this->invalidXml, 200); + try { + new AclResult($response); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals("xml format exception", $e->getMessage()); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BodyResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BodyResultTest.php new file mode 100644 index 0000000..af13d4d --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BodyResultTest.php @@ -0,0 +1,26 @@ +assertTrue($result->isOK()); + $this->assertEquals($result->getData(), "hi"); + } + + public function testParseInvalid404() + { + $response = new ResponseCore(array(), null, 200); + $result = new BodyResult($response); + $this->assertTrue($result->isOK()); + $this->assertEquals($result->getData(), ""); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketCnameTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketCnameTest.php new file mode 100644 index 0000000..87c9e54 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketCnameTest.php @@ -0,0 +1,77 @@ +client = Common::getOssClient(); + $this->bucketName = 'php-sdk-test-bucket-' . strval(rand(0, 10000)); + $this->client->createBucket($this->bucketName); + } + + public function tearDown() + { + $this->client->deleteBucket($this->bucketName); + } + + public function testBucketWithoutCname() + { + $cnameConfig = $this->client->getBucketCname($this->bucketName); + $this->assertEquals(0, count($cnameConfig->getCnames())); + } + + public function testAddCname() + { + $this->client->addBucketCname($this->bucketName, 'www.baidu.com'); + $this->client->addBucketCname($this->bucketName, 'www.qq.com'); + + $ret = $this->client->getBucketCname($this->bucketName); + $this->assertEquals(2, count($ret->getCnames())); + + // add another 2 cnames + $this->client->addBucketCname($this->bucketName, 'www.sina.com.cn'); + $this->client->addBucketCname($this->bucketName, 'www.iqiyi.com'); + + $ret = $this->client->getBucketCname($this->bucketName); + $cnames = $ret->getCnames(); + $cnameList = array(); + + foreach ($cnames as $c) { + $cnameList[] = $c['Domain']; + } + $should = array( + 'www.baidu.com', + 'www.qq.com', + 'www.sina.com.cn', + 'www.iqiyi.com' + ); + $this->assertEquals(4, count($cnames)); + $this->assertEquals(sort($should), sort($cnameList)); + } + + public function testDeleteCname() + { + $this->client->addBucketCname($this->bucketName, 'www.baidu.com'); + $this->client->addBucketCname($this->bucketName, 'www.qq.com'); + + $ret = $this->client->getBucketCname($this->bucketName); + $this->assertEquals(2, count($ret->getCnames())); + + // delete one cname + $this->client->deleteBucketCname($this->bucketName, 'www.baidu.com'); + + $ret = $this->client->getBucketCname($this->bucketName); + $this->assertEquals(1, count($ret->getCnames())); + $cnames = $ret->getCnames(); + $this->assertEquals('www.qq.com', $cnames[0]['Domain']); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketInfoTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketInfoTest.php new file mode 100644 index 0000000..80fa25c --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketInfoTest.php @@ -0,0 +1,21 @@ +assertNotNull($bucketInfo); + $this->assertEquals('cn-beijing', $bucketInfo->getLocation()); + $this->assertEquals('name', $bucketInfo->getName()); + $this->assertEquals('today', $bucketInfo->getCreateDate()); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketLiveChannelTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketLiveChannelTest.php new file mode 100644 index 0000000..bed68b0 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/BucketLiveChannelTest.php @@ -0,0 +1,283 @@ +client = Common::getOssClient(); + $this->bucketName = 'php-sdk-test-rtmp-bucket-name-' . strval(rand(0, 10000)); + $this->client->createBucket($this->bucketName); + Common::waitMetaSync(); + } + + public function tearDown() + { + ////to delete created bucket + //1. delele live channel + $list = $this->client->listBucketLiveChannels($this->bucketName); + if (count($list->getChannelList()) != 0) + { + foreach($list->getChannelList() as $list) + { + $this->client->deleteBucketLiveChannel($this->bucketName, $list->getName()); + } + } + //2. delete exsited object + $prefix = 'live-test/'; + $delimiter = '/'; + $nextMarker = ''; + $maxkeys = 1000; + $options = array( + 'delimiter' => $delimiter, + 'prefix' => $prefix, + 'max-keys' => $maxkeys, + 'marker' => $nextMarker, + ); + + try { + $listObjectInfo = $this->client->listObjects($this->bucketName, $options); + } catch (OssException $e) { + printf($e->getMessage() . "\n"); + return; + } + + $objectList = $listObjectInfo->getObjectList(); // 文件列表 + if (!empty($objectList)) + { + foreach($objectList as $objectInfo) + $this->client->deleteObject($this->bucketName, $objectInfo->getKey()); + } + //3. delete the bucket + $this->client->deleteBucket($this->bucketName); + } + + public function testPutLiveChannel() + { + $config = new LiveChannelConfig(array( + 'description' => 'live channel 1', + 'type' => 'HLS', + 'fragDuration' => 10, + 'fragCount' => 5, + 'playListName' => 'hello.m3u8' + )); + $info = $this->client->putBucketLiveChannel($this->bucketName, 'live-1', $config); + $this->client->deleteBucketLiveChannel($this->bucketName, 'live-1'); + + $this->assertEquals('live-1', $info->getName()); + $this->assertEquals('live channel 1', $info->getDescription()); + $this->assertEquals(1, count($info->getPublishUrls())); + $this->assertEquals(1, count($info->getPlayUrls())); + } + + public function testPutLiveChannelWithDefaultParams() + { + $config = new LiveChannelConfig(array( + 'description' => 'live channel 1', + 'type' => 'HLS', + )); + $info = $this->client->putBucketLiveChannel($this->bucketName, 'live-1', $config); + $this->client->deleteBucketLiveChannel($this->bucketName, 'live-1'); + + $this->assertEquals('live-1', $info->getName()); + $this->assertEquals('live channel 1', $info->getDescription()); + $this->assertEquals(1, count($info->getPublishUrls())); + $this->assertEquals(1, count($info->getPlayUrls())); + } + + public function testListLiveChannels() + { + $config = new LiveChannelConfig(array( + 'description' => 'live channel 1', + 'type' => 'HLS', + 'fragDuration' => 10, + 'fragCount' => 5, + 'playListName' => 'hello.m3u8' + )); + $this->client->putBucketLiveChannel($this->bucketName, 'live-1', $config); + + $config = new LiveChannelConfig(array( + 'description' => 'live channel 2', + 'type' => 'HLS', + 'fragDuration' => 10, + 'fragCount' => 5, + 'playListName' => 'hello.m3u8' + )); + $this->client->putBucketLiveChannel($this->bucketName, 'live-2', $config); + + $list = $this->client->listBucketLiveChannels($this->bucketName); + + $this->assertEquals($this->bucketName, $list->getBucketName()); + $this->assertEquals(false, $list->getIsTruncated()); + $channels = $list->getChannelList(); + $this->assertEquals(2, count($channels)); + + $chan1 = $channels[0]; + $this->assertEquals('live-1', $chan1->getName()); + $this->assertEquals('live channel 1', $chan1->getDescription()); + $this->assertEquals(1, count($chan1->getPublishUrls())); + $this->assertEquals(1, count($chan1->getPlayUrls())); + + $chan2 = $channels[1]; + $this->assertEquals('live-2', $chan2->getName()); + $this->assertEquals('live channel 2', $chan2->getDescription()); + $this->assertEquals(1, count($chan2->getPublishUrls())); + $this->assertEquals(1, count($chan2->getPlayUrls())); + + $list = $this->client->listBucketLiveChannels($this->bucketName, array( + 'prefix' => 'live-', + 'marker' => 'live-1', + 'max-keys' => 10 + )); + $channels = $list->getChannelList(); + $this->assertEquals(1, count($channels)); + $chan2 = $channels[0]; + $this->assertEquals('live-2', $chan2->getName()); + $this->assertEquals('live channel 2', $chan2->getDescription()); + $this->assertEquals(1, count($chan2->getPublishUrls())); + $this->assertEquals(1, count($chan2->getPlayUrls())); + + $this->client->deleteBucketLiveChannel($this->bucketName, 'live-1'); + $this->client->deleteBucketLiveChannel($this->bucketName, 'live-2'); + $list = $this->client->listBucketLiveChannels($this->bucketName, array( + 'prefix' => 'live-' + )); + $this->assertEquals(0, count($list->getChannelList())); + } + + public function testDeleteLiveChannel() + { + $channelName = 'live-to-delete'; + $config = new LiveChannelConfig(array( + 'description' => 'live channel to delete', + 'type' => 'HLS', + 'fragDuration' => 10, + 'fragCount' => 5, + 'playListName' => 'hello.m3u8' + )); + $this->client->putBucketLiveChannel($this->bucketName, $channelName, $config); + + $this->client->deleteBucketLiveChannel($this->bucketName, $channelName); + $list = $this->client->listBucketLiveChannels($this->bucketName, array( + 'prefix' => $channelName + )); + + $this->assertEquals(0, count($list->getChannelList())); + } + + public function testSignRtmpUrl() + { + $channelName = '90475'; + $bucket = 'douyu'; + $now = time(); + $url = $this->client->signRtmpUrl($bucket, $channelName, 900, array( + 'params' => array( + 'playlistName' => 'playlist.m3u8' + ) + )); + + $ret = parse_url($url); + $this->assertEquals('rtmp', $ret['scheme']); + parse_str($ret['query'], $query); + + $this->assertTrue(isset($query['OSSAccessKeyId'])); + $this->assertTrue(isset($query['Signature'])); + $this->assertTrue(intval($query['Expires']) - ($now + 900) < 3); + $this->assertEquals('playlist.m3u8', $query['playlistName']); + } + + public function testLiveChannelInfo() + { + $channelName = 'live-to-put-status'; + $config = new LiveChannelConfig(array( + 'description' => 'test live channel info', + 'type' => 'HLS', + 'fragDuration' => 10, + 'fragCount' => 5, + 'playListName' => 'hello.m3u8' + )); + $this->client->putBucketLiveChannel($this->bucketName, $channelName, $config); + + $info = $this->client->getLiveChannelInfo($this->bucketName, $channelName); + $this->assertEquals('test live channel info', $info->getDescription()); + $this->assertEquals('enabled', $info->getStatus()); + $this->assertEquals('HLS', $info->getType()); + $this->assertEquals(10, $info->getFragDuration()); + $this->assertEquals(5, $info->getFragCount()); + $this->assertEquals('playlist.m3u8', $info->getPlayListName()); + + $this->client->deleteBucketLiveChannel($this->bucketName, $channelName); + $list = $this->client->listBucketLiveChannels($this->bucketName, array( + 'prefix' => $channelName + )); + $this->assertEquals(0, count($list->getChannelList())); + } + + public function testPutLiveChannelStatus() + { + $channelName = 'live-to-put-status'; + $config = new LiveChannelConfig(array( + 'description' => 'test live channel info', + 'type' => 'HLS', + 'fragDuration' => 10, + 'fragCount' => 5, + 'playListName' => 'hello.m3u8' + )); + $this->client->putBucketLiveChannel($this->bucketName, $channelName, $config); + + $info = $this->client->getLiveChannelInfo($this->bucketName, $channelName); + $this->assertEquals('test live channel info', $info->getDescription()); + $this->assertEquals('enabled', $info->getStatus()); + $this->assertEquals('HLS', $info->getType()); + $this->assertEquals(10, $info->getFragDuration()); + $this->assertEquals(5, $info->getFragCount()); + $this->assertEquals('playlist.m3u8', $info->getPlayListName()); + $status = $this->client->getLiveChannelStatus($this->bucketName, $channelName); + $this->assertEquals('Idle', $status->getStatus()); + + + $resp = $this->client->putLiveChannelStatus($this->bucketName, $channelName, "disabled"); + $info = $this->client->getLiveChannelInfo($this->bucketName, $channelName); + $this->assertEquals('test live channel info', $info->getDescription()); + $this->assertEquals('disabled', $info->getStatus()); + $this->assertEquals('HLS', $info->getType()); + $this->assertEquals(10, $info->getFragDuration()); + $this->assertEquals(5, $info->getFragCount()); + $this->assertEquals('playlist.m3u8', $info->getPlayListName()); + + $status = $this->client->getLiveChannelStatus($this->bucketName, $channelName); + //getLiveChannelInfo + $this->assertEquals('Disabled', $status->getStatus()); + + $this->client->deleteBucketLiveChannel($this->bucketName, $channelName); + $list = $this->client->listBucketLiveChannels($this->bucketName, array( + 'prefix' => $channelName + )); + $this->assertEquals(0, count($list->getChannelList())); + + } + public function testLiveChannelHistory() + { + $channelName = 'live-test-history'; + $config = new LiveChannelConfig(array( + 'description' => 'test live channel info', + 'type' => 'HLS', + 'fragDuration' => 10, + 'fragCount' => 5, + 'playListName' => 'hello.m3u8' + )); + $this->client->putBucketLiveChannel($this->bucketName, $channelName, $config); + + $history = $this->client->getLiveChannelHistory($this->bucketName, $channelName); + $this->assertEquals(0, count($history->getLiveRecordList())); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CallbackTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CallbackTest.php new file mode 100644 index 0000000..a0db003 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CallbackTest.php @@ -0,0 +1,297 @@ +ossClient->putObject($this->bucket, $copiedObject, file_get_contents(__FILE__)); + + /** + * step 1. 初始化一个分块上传事件, 也就是初始化上传Multipart, 获取upload id + */ + try { + $upload_id = $this->ossClient->initiateMultipartUpload($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + /* + * step 2. uploadPartCopy + */ + $copyId = 1; + $eTag = $this->ossClient->uploadPartCopy($this->bucket, $copiedObject, $this->bucket, $object, $copyId, $upload_id); + $upload_parts[] = array( + 'PartNumber' => $copyId, + 'ETag' => $eTag, + ); + + try { + $listPartsInfo = $this->ossClient->listParts($this->bucket, $object, $upload_id); + $this->assertNotNull($listPartsInfo); + } catch (OssException $e) { + $this->assertTrue(false); + } + + /** + * step 3. + */ + + $json = + '{ + "callbackUrl":"oss-demo.aliyuncs.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"{\"mimeType\":${mimeType},\"size\":${size},\"x:var1\":${x:var1},\"x:var2\":${x:var2}}", + "callbackBodyType":"application/json" + }'; + + $var = + '{ + "x:var1":"value1", + "x:var2":"值2" + }'; + $options = array(OssClient::OSS_CALLBACK => $json, + OssClient::OSS_CALLBACK_VAR => $var + ); + + try { + $result = $this->ossClient->completeMultipartUpload($this->bucket, $object, $upload_id, $upload_parts, $options); + $this->assertEquals("200", $result['info']['http_code']); + $this->assertEquals("{\"Status\":\"OK\"}", $result['body']); + } catch (OssException $e) { + $this->assertTrue(false); + } + } + + public function testMultipartUploadCallbackFailed() + { + $object = "multipart-callback-test.txt"; + $copiedObject = "multipart-callback-test.txt.copied"; + $this->ossClient->putObject($this->bucket, $copiedObject, file_get_contents(__FILE__)); + + /** + * step 1. 初始化一个分块上传事件, 也就是初始化上传Multipart, 获取upload id + */ + try { + $upload_id = $this->ossClient->initiateMultipartUpload($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + /* + * step 2. uploadPartCopy + */ + $copyId = 1; + $eTag = $this->ossClient->uploadPartCopy($this->bucket, $copiedObject, $this->bucket, $object, $copyId, $upload_id); + $upload_parts[] = array( + 'PartNumber' => $copyId, + 'ETag' => $eTag, + ); + + try { + $listPartsInfo = $this->ossClient->listParts($this->bucket, $object, $upload_id); + $this->assertNotNull($listPartsInfo); + } catch (OssException $e) { + $this->assertTrue(false); + } + + /** + * step 3. + */ + + $json = + '{ + "callbackUrl":"www.baidu.com", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"{\"mimeType\":${mimeType},\"size\":${size},\"x:var1\":${x:var1},\"x:var2\":${x:var2}}", + "callbackBodyType":"application/json" + }'; + + $var = + '{ + "x:var1":"value1", + "x:var2":"值2" + }'; + $options = array(OssClient::OSS_CALLBACK => $json, + OssClient::OSS_CALLBACK_VAR => $var + ); + + try { + $result = $this->ossClient->completeMultipartUpload($this->bucket, $object, $upload_id, $upload_parts, $options); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertTrue(true); + $this->assertEquals("203", $e->getHTTPStatus()); + } + + } + + public function testPutObjectCallbackNormal() + { + //json + { + $json = + '{ + "callbackUrl":"oss-demo.aliyuncs.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"{\"mimeType\":${mimeType},\"size\":${size}}", + "callbackBodyType":"application/json" + }'; + $options = array(OssClient::OSS_CALLBACK => $json); + $this->putObjectCallbackOk($options, "200"); + } + //url + { + $url = + '{ + "callbackUrl":"oss-demo.aliyuncs.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&imageInfo.height=${imageInfo.height}&imageInfo.width=${imageInfo.width}&imageInfo.format=${imageInfo.format}", + "callbackBodyType":"application/x-www-form-urlencoded" + }'; + $options = array(OssClient::OSS_CALLBACK => $url); + $this->putObjectCallbackOk($options, "200"); + } + // Unspecified typre + { + $url = + '{ + "callbackUrl":"oss-demo.aliyuncs.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&imageInfo.height=${imageInfo.height}&imageInfo.width=${imageInfo.width}&imageInfo.format=${imageInfo.format}" + }'; + $options = array(OssClient::OSS_CALLBACK => $url); + $this->putObjectCallbackOk($options, "200"); + } + //json and body is chinese + { + $json = + '{ + "callbackUrl":"oss-demo.aliyuncs.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"{\" 春水碧于天,画船听雨眠。\":\"垆边人似月,皓腕凝霜雪。\"}", + "callbackBodyType":"application/json" + }'; + $options = array(OssClient::OSS_CALLBACK => $json); + $this->putObjectCallbackOk($options, "200"); + } + //url and body is chinese + { + $url = + '{ + "callbackUrl":"oss-demo.aliyuncs.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"春水碧于天,画船听雨眠。垆边人似月,皓腕凝霜雪", + "callbackBodyType":"application/x-www-form-urlencoded" + }'; + $options = array(OssClient::OSS_CALLBACK => $url); + $this->putObjectCallbackOk($options, "200"); + } + //json and add callback_var + { + $json = + '{ + "callbackUrl":"oss-demo.aliyuncs.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"{\"mimeType\":${mimeType},\"size\":${size},\"x:var1\":${x:var1},\"x:var2\":${x:var2}}", + "callbackBodyType":"application/json" + }'; + + $var = + '{ + "x:var1":"value1", + "x:var2":"aliyun.com" + }'; + $options = array(OssClient::OSS_CALLBACK => $json, + OssClient::OSS_CALLBACK_VAR => $var + ); + $this->putObjectCallbackOk($options, "200"); + } + //url and add callback_var + { + $url = + '{ + "callbackUrl":"oss-demo.aliyuncs.com:23450", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&imageInfo.height=${imageInfo.height}&imageInfo.width=${imageInfo.width}&imageInfo.format=${imageInfo.format}&my_var1=${x:var1}&my_var2=${x:var2}", + "callbackBodyType":"application/x-www-form-urlencoded" + }'; + $var = + '{ + "x:var1":"value1凌波不过横塘路,但目送,芳", + "x:var2":"值2" + }'; + $options = array(OssClient::OSS_CALLBACK => $url, + OssClient::OSS_CALLBACK_VAR => $var + ); + $this->putObjectCallbackOk($options, "200"); + } + + } + + public function testPutCallbackWithCallbackFailed() + { + { + $json = + '{ + "callbackUrl":"http://www.baidu.com", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"{\"mimeType\":${mimeType},\"size\":${size}}", + "callbackBodyType":"application/json" + }'; + $options = array(OssClient::OSS_CALLBACK => $json); + $this->putObjectCallbackFailed($options, "203"); + } + + { + $url = + '{ + "callbackUrl":"http://www.baidu.com", + "callbackHost":"oss-cn-hangzhou.aliyuncs.com", + "callbackBody":"bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&imageInfo.height=${imageInfo.height}&imageInfo.width=${imageInfo.width}&imageInfo.format=${imageInfo.format}&my_var1=${x:var1}&my_var2=${x:var2}", + "callbackBodyType":"application/x-www-form-urlencoded" + }'; + $options = array(OssClient::OSS_CALLBACK => $url); + $this->putObjectCallbackFailed($options, "203"); + } + + } + + private function putObjectCallbackOk($options, $status) + { + $object = "oss-php-sdk-callback-test.txt"; + $content = file_get_contents(__FILE__); + try { + $result = $this->ossClient->putObject($this->bucket, $object, $content, $options); + $this->assertEquals($status, $result['info']['http_code']); + $this->assertEquals("{\"Status\":\"OK\"}", $result['body']); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + private function putObjectCallbackFailed($options, $status) + { + $object = "oss-php-sdk-callback-test.txt"; + $content = file_get_contents(__FILE__); + try { + $result = $this->ossClient->putObject($this->bucket, $object, $content, $options); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals($status, $e->getHTTPStatus()); + $this->assertTrue(true); + } + } + + public function setUp() + { + parent::setUp(); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CnameConfigTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CnameConfigTest.php new file mode 100644 index 0000000..e3c1ce9 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CnameConfigTest.php @@ -0,0 +1,77 @@ + + + + www.foo.com + enabled + 20150101 + + + bar.com + disabled + 20160101 + + +BBBB; + + public function testFromXml() + { + $cnameConfig = new CnameConfig(); + $cnameConfig->parseFromXml($this->xml1); + + $cnames = $cnameConfig->getCnames(); + $this->assertEquals(2, count($cnames)); + $this->assertEquals('www.foo.com', $cnames[0]['Domain']); + $this->assertEquals('enabled', $cnames[0]['Status']); + $this->assertEquals('20150101', $cnames[0]['LastModified']); + + $this->assertEquals('bar.com', $cnames[1]['Domain']); + $this->assertEquals('disabled', $cnames[1]['Status']); + $this->assertEquals('20160101', $cnames[1]['LastModified']); + } + + public function testToXml() + { + $cnameConfig = new CnameConfig(); + $cnameConfig->addCname('www.foo.com'); + $cnameConfig->addCname('bar.com'); + + $xml = $cnameConfig->serializeToXml(); + $comp = new CnameConfig(); + $comp->parseFromXml($xml); + + $cnames1 = $cnameConfig->getCnames(); + $cnames2 = $comp->getCnames(); + + $this->assertEquals(count($cnames1), count($cnames2)); + $this->assertEquals(count($cnames1[0]), count($cnames2[0])); + $this->assertEquals(1, count($cnames1[0])); + $this->assertEquals($cnames1[0]['Domain'], $cnames2[0]['Domain']); + } + + public function testCnameNumberLimit() + { + $cnameConfig = new CnameConfig(); + for ($i = 0; $i < CnameConfig::OSS_MAX_RULES; $i += 1) { + $cnameConfig->addCname(strval($i) . '.foo.com'); + } + try { + $cnameConfig->addCname('www.foo.com'); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals( + $e->getMessage(), + "num of cname in the config exceeds self::OSS_MAX_RULES: " . strval(CnameConfig::OSS_MAX_RULES)); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/Common.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/Common.php new file mode 100644 index 0000000..9d7190c --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/Common.php @@ -0,0 +1,70 @@ +getMessage() . "\n"); + return null; + } + return $ossClient; + } + + public static function getBucketName() + { + return getenv('OSS_BUCKET'); + } + + /** + * 工具方法,创建一个bucket + */ + public static function createBucket() + { + $ossClient = self::getOssClient(); + if (is_null($ossClient)) exit(1); + $bucket = self::getBucketName(); + $acl = OssClient::OSS_ACL_TYPE_PUBLIC_READ; + try { + $ossClient->createBucket($bucket, $acl); + } catch (OssException $e) { + printf(__FUNCTION__ . ": FAILED\n"); + printf($e->getMessage() . "\n"); + return; + } + print(__FUNCTION__ . ": OK" . "\n"); + } + + /** + * Wait for bucket meta sync + */ + public static function waitMetaSync() + { + if (getenv('TRAVIS')) { + sleep(10); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ContentTypeTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ContentTypeTest.php new file mode 100644 index 0000000..606c810 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ContentTypeTest.php @@ -0,0 +1,133 @@ +/dev/null', $output, $status); + + $this->assertEquals(0, $status); + } + + private function getContentType($bucket, $object) + { + $client = Common::getOssClient(); + $headers = $client->getObjectMeta($bucket, $object); + return $headers['content-type']; + } + + public function testByFileName() + { + $client = Common::getOssClient(); + $bucket = Common::getBucketName(); + + $file = '/tmp/x.html'; + $object = 'test/x'; + $this->runCmd('touch ' . $file); + + $client->uploadFile($bucket, $object, $file); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('text/html', $type); + + $file = '/tmp/x.json'; + $object = 'test/y'; + $this->runCmd('dd if=/dev/urandom of=' . $file . ' bs=1024 count=100'); + + $client->multiuploadFile($bucket, $object, $file, array('partSize' => 100)); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('application/json', $type); + } + + public function testByObjectKey() + { + $client = Common::getOssClient(); + $bucket = Common::getBucketName(); + + $object = "test/x.txt"; + $client->putObject($bucket, $object, "hello world"); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('text/plain', $type); + + $file = '/tmp/x.html'; + $object = 'test/x.txt'; + $this->runCmd('touch ' . $file); + + $client->uploadFile($bucket, $object, $file); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('text/html', $type); + + $file = '/tmp/x.none'; + $object = 'test/x.txt'; + $this->runCmd('touch ' . $file); + + $client->uploadFile($bucket, $object, $file); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('text/plain', $type); + + $file = '/tmp/x.mp3'; + $object = 'test/y.json'; + $this->runCmd('dd if=/dev/urandom of=' . $file . ' bs=1024 count=100'); + + $client->multiuploadFile($bucket, $object, $file, array('partSize' => 100)); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('audio/mpeg', $type); + + $file = '/tmp/x.none'; + $object = 'test/y.json'; + $this->runCmd('dd if=/dev/urandom of=' . $file . ' bs=1024 count=100'); + + $client->multiuploadFile($bucket, $object, $file, array('partSize' => 100)); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('application/json', $type); + } + + public function testByUser() + { + $client = Common::getOssClient(); + $bucket = Common::getBucketName(); + + $object = "test/x.txt"; + $client->putObject($bucket, $object, "hello world", array( + 'Content-Type' => 'text/html' + )); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('text/html', $type); + + $file = '/tmp/x.html'; + $object = 'test/x'; + $this->runCmd('touch ' . $file); + + $client->uploadFile($bucket, $object, $file, array( + 'Content-Type' => 'application/json' + )); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('application/json', $type); + + $file = '/tmp/x.json'; + $object = 'test/y'; + $this->runCmd('dd if=/dev/urandom of=' . $file . ' bs=1024 count=100'); + + $client->multiuploadFile($bucket, $object, $file, array( + 'partSize' => 100, + 'Content-Type' => 'audio/mpeg' + )); + $type = $this->getContentType($bucket, $object); + + $this->assertEquals('audio/mpeg', $type); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CopyObjectResult.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CopyObjectResult.php new file mode 100644 index 0000000..171d4c8 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CopyObjectResult.php @@ -0,0 +1,52 @@ + + + Fri, 24 Feb 2012 07:18:48 GMT + "5B3C1A2E053D763E1B002CC607C5A0FE" + +BBBB; + + public function testNullResponse() + { + $response = null; + try { + new CopyObjectResult($response); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('raw response is null', $e->getMessage()); + } + } + + public function testOkResponse() + { + $header= array(); + $response = new ResponseCore($header, $this->body, 200); + $result = new CopyObjectResult($response); + $data = $result->getData(); + $this->assertTrue($result->isOK()); + $this->assertEquals("Fri, 24 Feb 2012 07:18:48 GMT", $data[0]); + $this->assertEquals("\"5B3C1A2E053D763E1B002CC607C5A0FE\"", $data[1]); + } + + public function testFailResponse() + { + $response = new ResponseCore(array(), "", 404); + try { + new CopyObjectResult($response); + $this->assertFalse(true); + } catch (OssException $e) { + + } + } + +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CorsConfigTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CorsConfigTest.php new file mode 100644 index 0000000..ddc4d3a --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/CorsConfigTest.php @@ -0,0 +1,140 @@ + + + +http://www.b.com +http://www.a.com +http://www.a.com +GET +PUT +POST +x-oss-test +x-oss-test2 +x-oss-test2 +x-oss-test3 +x-oss-test1 +x-oss-test1 +x-oss-test2 +10 + + +http://www.b.com +GET +x-oss-test +x-oss-test1 +110 + + +BBBB; + + private $validXml2 = << + + +http://www.b.com +http://www.a.com +http://www.a.com +GET +PUT +POST +x-oss-test +x-oss-test2 +x-oss-test2 +x-oss-test3 +x-oss-test1 +x-oss-test1 +x-oss-test2 +10 + + +BBBB; + + public function testParseValidXml() + { + $corsConfig = new CorsConfig(); + $corsConfig->parseFromXml($this->validXml); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($corsConfig->serializeToXml())); + $this->assertNotNull($corsConfig->getRules()); + $rules = $corsConfig->getRules(); + $this->assertNotNull($rules[0]->getAllowedHeaders()); + $this->assertNotNull($rules[0]->getAllowedMethods()); + $this->assertNotNull($rules[0]->getAllowedOrigins()); + $this->assertNotNull($rules[0]->getExposeHeaders()); + $this->assertNotNull($rules[0]->getMaxAgeSeconds()); + } + + public function testParseValidXml2() + { + $corsConfig = new CorsConfig(); + $corsConfig->parseFromXml($this->validXml2); + $this->assertEquals($this->cleanXml($this->validXml2), $this->cleanXml($corsConfig->serializeToXml())); + } + + public function testCreateCorsConfigFromMoreThan10Rules() + { + $corsConfig = new CorsConfig(); + $rule = new CorsRule(); + for ($i = 0; $i < CorsConfig::OSS_MAX_RULES; $i += 1) { + $corsConfig->addRule($rule); + } + try { + $corsConfig->addRule($rule); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals($e->getMessage(), "num of rules in the config exceeds self::OSS_MAX_RULES: " . strval(CorsConfig::OSS_MAX_RULES)); + } + } + + public function testCreateCorsConfigParamAbsent() + { + $corsConfig = new CorsConfig(); + $rule = new CorsRule(); + $corsConfig->addRule($rule); + + try { + $xml = $corsConfig->serializeToXml(); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals($e->getMessage(), "maxAgeSeconds is not set in the Rule"); + } + } + + public function testCreateCorsConfigFromScratch() + { + $corsConfig = new CorsConfig(); + $rule = new CorsRule(); + $rule->addAllowedHeader("x-oss-test"); + $rule->addAllowedHeader("x-oss-test2"); + $rule->addAllowedHeader("x-oss-test2"); + $rule->addAllowedHeader("x-oss-test3"); + $rule->addAllowedOrigin("http://www.b.com"); + $rule->addAllowedOrigin("http://www.a.com"); + $rule->addAllowedOrigin("http://www.a.com"); + $rule->addAllowedMethod("GET"); + $rule->addAllowedMethod("PUT"); + $rule->addAllowedMethod("POST"); + $rule->addExposeHeader("x-oss-test1"); + $rule->addExposeHeader("x-oss-test1"); + $rule->addExposeHeader("x-oss-test2"); + $rule->setMaxAgeSeconds(10); + $corsConfig->addRule($rule); + $this->assertEquals($this->cleanXml($this->validXml2), $this->cleanXml($corsConfig->serializeToXml())); + $this->assertEquals($this->cleanXml($this->validXml2), $this->cleanXml(strval($corsConfig))); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ExistResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ExistResultTest.php new file mode 100644 index 0000000..e1b4e81 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ExistResultTest.php @@ -0,0 +1,38 @@ +assertTrue($result->isOK()); + $this->assertEquals($result->getData(), true); + } + + public function testParseInvalid404() + { + $response = new ResponseCore(array(), "", 404); + $result = new ExistResult($response); + $this->assertTrue($result->isOK()); + $this->assertEquals($result->getData(), false); + } + + public function testInvalidResponse() + { + $response = new ResponseCore(array(), "", 300); + try { + new ExistResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetCorsResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetCorsResultTest.php new file mode 100644 index 0000000..a3281c8 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetCorsResultTest.php @@ -0,0 +1,67 @@ + + + +http://www.b.com +http://www.a.com +http://www.a.com +GET +PUT +POST +x-oss-test +x-oss-test2 +x-oss-test2 +x-oss-test3 +x-oss-test1 +x-oss-test1 +x-oss-test2 +10 + + +http://www.b.com +GET +x-oss-test +x-oss-test1 +110 + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new GetCorsResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $corsConfig = $result->getData(); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($corsConfig->serializeToXml())); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } + + public function testInvalidResponse() + { + $response = new ResponseCore(array(), $this->validXml, 300); + try { + new GetCorsResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetLifecycleResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetLifecycleResultTest.php new file mode 100644 index 0000000..92ae208 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetLifecycleResultTest.php @@ -0,0 +1,59 @@ + + + +delete obsoleted files +obsoleted/ +Enabled +3 + + +delete temporary files +temporary/ +Enabled +2022-10-12T00:00:00.000Z +2022-10-12T00:00:00.000Z + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new GetLifecycleResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $lifecycleConfig = $result->getData(); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($lifecycleConfig->serializeToXml())); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } + + public function testInvalidResponse() + { + $response = new ResponseCore(array(), $this->validXml, 300); + try { + new GetLifecycleResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + + } + } + +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetLoggingResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetLoggingResultTest.php new file mode 100644 index 0000000..6195014 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetLoggingResultTest.php @@ -0,0 +1,51 @@ + + + +TargetBucket +TargetPrefix + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new GetLoggingResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $loggingConfig = $result->getData(); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($loggingConfig->serializeToXml())); + $this->assertEquals("TargetBucket", $loggingConfig->getTargetBucket()); + $this->assertEquals("TargetPrefix", $loggingConfig->getTargetPrefix()); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } + + public function testInvalidResponse() + { + $response = new ResponseCore(array(), $this->validXml, 300); + try { + new GetLoggingResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetRefererResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetRefererResultTest.php new file mode 100644 index 0000000..072aa43 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetRefererResultTest.php @@ -0,0 +1,51 @@ + + +true + +http://www.aliyun.com +https://www.aliyun.com +http://www.*.com +https://www.?.aliyuncs.com + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new GetRefererResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $refererConfig = $result->getData(); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($refererConfig->serializeToXml())); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } + + public function testInvalidResponse() + { + $response = new ResponseCore(array(), $this->validXml, 300); + try { + new GetRefererResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetWebsiteResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetWebsiteResultTest.php new file mode 100644 index 0000000..70e1559 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/GetWebsiteResultTest.php @@ -0,0 +1,50 @@ + + + +index.html + + +errorDocument.html + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new GetWebsiteResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $websiteConfig = $result->getData(); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($websiteConfig->serializeToXml())); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } + + public function testInvalidResponse() + { + $response = new ResponseCore(array(), $this->validXml, 300); + try { + new GetWebsiteResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/HeaderResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/HeaderResultTest.php new file mode 100644 index 0000000..dae4975 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/HeaderResultTest.php @@ -0,0 +1,23 @@ + 'value'), "", 200); + $result = new HeaderResult($response); + $this->assertTrue($result->isOK()); + $this->assertTrue(is_array($result->getData())); + $data = $result->getData(); + $this->assertEquals($data['key'], 'value'); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/HttpTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/HttpTest.php new file mode 100644 index 0000000..a59dfcd --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/HttpTest.php @@ -0,0 +1,77 @@ +assertFalse($res->isOK()); + $this->assertTrue($res->isOK(500)); + } + + public function testGet() + { + $httpCore = new RequestCore("http://www.baidu.com"); + $httpResponse = $httpCore->send_request(); + $this->assertNotNull($httpResponse); + } + + public function testSetProxyAndTimeout() + { + $httpCore = new RequestCore("http://www.baidu.com"); + $httpCore->set_proxy("1.0.2.1:8888"); + $httpCore->connect_timeout = 1; + try { + $httpResponse = $httpCore->send_request(); + $this->assertTrue(false); + } catch (RequestCore_Exception $e) { + + } + } + + public function testGetParseTrue() + { + $httpCore = new RequestCore("http://www.baidu.com"); + $httpCore->curlopts = array(CURLOPT_HEADER => true); + $url = $httpCore->send_request(true); + foreach ($httpCore->get_response_header() as $key => $value) { + $this->assertEquals($httpCore->get_response_header($key), $value); + } + $this->assertNotNull($url); + } + + public function testParseResponse() + { + $httpCore = new RequestCore("http://www.baidu.com"); + $response = $httpCore->send_request(); + $parsed = $httpCore->process_response(null, $response); + $this->assertNotNull($parsed); + } + + public function testExceptionGet() + { + $httpCore = null; + $exception = false; + try { + $httpCore = new RequestCore("http://www.notexistsitexx.com"); + $httpCore->set_body(""); + $httpCore->set_method("GET"); + $httpCore->connect_timeout = 10; + $httpCore->timeout = 10; + $res = $httpCore->send_request(); + } catch (RequestCore_Exception $e) { + $exception = true; + } + $this->assertTrue($exception); + } +} + + diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/InitiateMultipartUploadResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/InitiateMultipartUploadResultTest.php new file mode 100644 index 0000000..9f6c7a5 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/InitiateMultipartUploadResultTest.php @@ -0,0 +1,47 @@ + + + multipart_upload + multipart.data + 0004B9894A22E5B1888A1E29F8236E2D + +BBBB; + + private $invalidXml = << + + multipart_upload + multipart.data + +BBBB; + + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new InitiateMultipartUploadResult($response); + $this->assertEquals("0004B9894A22E5B1888A1E29F8236E2D", $result->getData()); + } + + public function testParseInvalidXml() + { + $response = new ResponseCore(array(), $this->invalidXml, 200); + try { + $result = new InitiateMultipartUploadResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LifecycleConfigTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LifecycleConfigTest.php new file mode 100644 index 0000000..7bd0331 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LifecycleConfigTest.php @@ -0,0 +1,130 @@ + + + +delete obsoleted files +obsoleted/ +Enabled +3 + + +delete temporary files +temporary/ +Enabled +2022-10-12T00:00:00.000Z +2022-10-12T00:00:00.000Z + + +BBBB; + + private $validLifecycle2 = << + +delete temporary files +temporary/ +Enabled +2022-10-12T00:00:00.000Z +2022-10-12T00:00:00.000Z + + +BBBB; + + private $nullLifecycle = << + +BBBB; + + public function testConstructValidConfig() + { + $lifecycleConfig = new LifecycleConfig(); + $actions = array(); + $actions[] = new LifecycleAction("Expiration", "Days", 3); + $lifecycleRule = new LifecycleRule("delete obsoleted files", "obsoleted/", "Enabled", $actions); + $lifecycleConfig->addRule($lifecycleRule); + $actions = array(); + $actions[] = new LifecycleAction("Expiration", "Date", '2022-10-12T00:00:00.000Z'); + $actions[] = new LifecycleAction("Expiration2", "Date", '2022-10-12T00:00:00.000Z'); + $lifecycleRule = new LifecycleRule("delete temporary files", "temporary/", "Enabled", $actions); + $lifecycleConfig->addRule($lifecycleRule); + try { + $lifecycleConfig->addRule(null); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('lifecycleRule is null', $e->getMessage()); + } + $this->assertEquals($this->cleanXml(strval($lifecycleConfig)), $this->cleanXml($this->validLifecycle)); + } + + public function testParseValidXml() + { + $lifecycleConfig = new LifecycleConfig(); + $lifecycleConfig->parseFromXml($this->validLifecycle); + $this->assertEquals($this->cleanXml($lifecycleConfig->serializeToXml()), $this->cleanXml($this->validLifecycle)); + $this->assertEquals(2, count($lifecycleConfig->getRules())); + $rules = $lifecycleConfig->getRules(); + $this->assertEquals('delete temporary files', $rules[1]->getId()); + } + + public function testParseValidXml2() + { + $lifecycleConfig = new LifecycleConfig(); + $lifecycleConfig->parseFromXml($this->validLifecycle2); + $this->assertEquals($this->cleanXml($lifecycleConfig->serializeToXml()), $this->cleanXml($this->validLifecycle2)); + $this->assertEquals(1, count($lifecycleConfig->getRules())); + $rules = $lifecycleConfig->getRules(); + $this->assertEquals('delete temporary files', $rules[0]->getId()); + } + + public function testParseNullXml() + { + $lifecycleConfig = new LifecycleConfig(); + $lifecycleConfig->parseFromXml($this->nullLifecycle); + $this->assertEquals($this->cleanXml($lifecycleConfig->serializeToXml()), $this->cleanXml($this->nullLifecycle)); + $this->assertEquals(0, count($lifecycleConfig->getRules())); + } + + public function testLifecycleRule() + { + $lifecycleRule = new LifecycleRule("x", "x", "x", array('x')); + $lifecycleRule->setId("id"); + $lifecycleRule->setPrefix("prefix"); + $lifecycleRule->setStatus("Enabled"); + $lifecycleRule->setActions(array()); + + $this->assertEquals('id', $lifecycleRule->getId()); + $this->assertEquals('prefix', $lifecycleRule->getPrefix()); + $this->assertEquals('Enabled', $lifecycleRule->getStatus()); + $this->assertEmpty($lifecycleRule->getActions()); + } + + public function testLifecycleAction() + { + $action = new LifecycleAction('x', 'x', 'x'); + $this->assertEquals($action->getAction(), 'x'); + $this->assertEquals($action->getTimeSpec(), 'x'); + $this->assertEquals($action->getTimeValue(), 'x'); + $action->setAction('y'); + $action->setTimeSpec('y'); + $action->setTimeValue('y'); + $this->assertEquals($action->getAction(), 'y'); + $this->assertEquals($action->getTimeSpec(), 'y'); + $this->assertEquals($action->getTimeValue(), 'y'); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListBucketsResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListBucketsResultTest.php new file mode 100644 index 0000000..1abe1f5 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListBucketsResultTest.php @@ -0,0 +1,97 @@ + + + + ut_test_put_bucket + ut_test_put_bucket + + + + oss-cn-hangzhou-a + xz02tphky6fjfiuc0 + 2014-05-15T11:18:32.000Z + + + oss-cn-hangzhou-a + xz02tphky6fjfiuc1 + 2014-05-15T11:18:32.000Z + + + +BBBB; + + private $nullXml = << + + + ut_test_put_bucket + ut_test_put_bucket + + + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new ListBucketsResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $bucketListInfo = $result->getData(); + $this->assertEquals(2, count($bucketListInfo->getBucketList())); + } + + public function testParseNullXml() + { + $response = new ResponseCore(array(), $this->nullXml, 200); + $result = new ListBucketsResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $bucketListInfo = $result->getData(); + $this->assertEquals(0, count($bucketListInfo->getBucketList())); + } + + public function test403() + { + $errorHeader = array( + 'x-oss-request-id' => '1a2b-3c4d' + ); + + $errorBody = <<< BBBB + + + NoSuchBucket + The specified bucket does not exist. + 566B870D207FB3044302EB0A + hello.oss-test.aliyun-inc.com + hello + +BBBB; + $response = new ResponseCore($errorHeader, $errorBody, 403); + try { + new ListBucketsResult($response); + } catch (OssException $e) { + $this->assertEquals( + $e->getMessage(), + 'NoSuchBucket: The specified bucket does not exist. RequestId: 1a2b-3c4d'); + $this->assertEquals($e->getHTTPStatus(), '403'); + $this->assertEquals($e->getRequestId(), '1a2b-3c4d'); + $this->assertEquals($e->getErrorCode(), 'NoSuchBucket'); + $this->assertEquals($e->getErrorMessage(), 'The specified bucket does not exist.'); + $this->assertEquals($e->getDetails(), $errorBody); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListMultipartUploadResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListMultipartUploadResultTest.php new file mode 100644 index 0000000..5c757d3 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListMultipartUploadResultTest.php @@ -0,0 +1,114 @@ + + + oss-example + xx + 3 + oss.avi + 0004B99B8E707874FC2D692FA5D77D3F + x + xx + 1000 + false + + multipart.data + 0004B999EF518A1FE585B0C9360DC4C8 + 2012-02-23T04:18:23.000Z + + + multipart.data + 0004B999EF5A239BB9138C6227D69F95 + 2012-02-23T04:18:23.000Z + + + oss.avi + 0004B99B8E707874FC2D692FA5D77D3F + 2012-02-23T06:14:27.000Z + + +BBBB; + + private $validXmlWithEncodedKey = << + + oss-example + url + php%2Bkey-marker + 3 + php%2Bnext-key-marker + 0004B99B8E707874FC2D692FA5D77D3F + %2F + php%2Bprefix + 1000 + true + + php%2Bkey-1 + 0004B999EF518A1FE585B0C9360DC4C8 + 2012-02-23T04:18:23.000Z + + + php%2Bkey-2 + 0004B999EF5A239BB9138C6227D69F95 + 2012-02-23T04:18:23.000Z + + + php%2Bkey-3 + 0004B99B8E707874FC2D692FA5D77D3F + 2012-02-23T06:14:27.000Z + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new ListMultipartUploadResult($response); + $listMultipartUploadInfo = $result->getData(); + $this->assertEquals("oss-example", $listMultipartUploadInfo->getBucket()); + $this->assertEquals("xx", $listMultipartUploadInfo->getKeyMarker()); + $this->assertEquals(3, $listMultipartUploadInfo->getUploadIdMarker()); + $this->assertEquals("oss.avi", $listMultipartUploadInfo->getNextKeyMarker()); + $this->assertEquals("0004B99B8E707874FC2D692FA5D77D3F", $listMultipartUploadInfo->getNextUploadIdMarker()); + $this->assertEquals("x", $listMultipartUploadInfo->getDelimiter()); + $this->assertEquals("xx", $listMultipartUploadInfo->getPrefix()); + $this->assertEquals(1000, $listMultipartUploadInfo->getMaxUploads()); + $this->assertEquals("false", $listMultipartUploadInfo->getIsTruncated()); + $uploads = $listMultipartUploadInfo->getUploads(); + $this->assertEquals("multipart.data", $uploads[0]->getKey()); + $this->assertEquals("0004B999EF518A1FE585B0C9360DC4C8", $uploads[0]->getUploadId()); + $this->assertEquals("2012-02-23T04:18:23.000Z", $uploads[0]->getInitiated()); + } + + public function testParseValidXmlWithEncodedKey() + { + $response = new ResponseCore(array(), $this->validXmlWithEncodedKey, 200); + $result = new ListMultipartUploadResult($response); + $listMultipartUploadInfo = $result->getData(); + $this->assertEquals("oss-example", $listMultipartUploadInfo->getBucket()); + $this->assertEquals("php+key-marker", $listMultipartUploadInfo->getKeyMarker()); + $this->assertEquals("php+next-key-marker", $listMultipartUploadInfo->getNextKeyMarker()); + $this->assertEquals(3, $listMultipartUploadInfo->getUploadIdMarker()); + $this->assertEquals("0004B99B8E707874FC2D692FA5D77D3F", $listMultipartUploadInfo->getNextUploadIdMarker()); + $this->assertEquals("/", $listMultipartUploadInfo->getDelimiter()); + $this->assertEquals("php+prefix", $listMultipartUploadInfo->getPrefix()); + $this->assertEquals(1000, $listMultipartUploadInfo->getMaxUploads()); + $this->assertEquals("true", $listMultipartUploadInfo->getIsTruncated()); + $uploads = $listMultipartUploadInfo->getUploads(); + $this->assertEquals("php+key-1", $uploads[0]->getKey()); + $this->assertEquals("0004B999EF518A1FE585B0C9360DC4C8", $uploads[0]->getUploadId()); + $this->assertEquals("2012-02-23T04:18:23.000Z", $uploads[0]->getInitiated()); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListObjectsResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListObjectsResultTest.php new file mode 100644 index 0000000..85f262c --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListObjectsResultTest.php @@ -0,0 +1,151 @@ + + + testbucket-hf + + + 1000 + / + false + + oss-php-sdk-test/ + + + test/ + + +BBBB; + + private $validXml2 = << + + testbucket-hf + oss-php-sdk-test/ + xx + 1000 + / + false + + oss-php-sdk-test/upload-test-object-name.txt + 2015-11-18T03:36:00.000Z + "89B9E567E7EB8815F2F7D41851F9A2CD" + Normal + 13115 + Standard + + cname_user + cname_user + + + +BBBB; + + private $validXmlWithEncodedKey = << + + testbucket-hf + url + php%2Fprefix + php%2Fmarker + php%2Fnext-marker + 1000 + %2F + true + + php/a%2Bb + 2015-11-18T03:36:00.000Z + "89B9E567E7EB8815F2F7D41851F9A2CD" + Normal + 13115 + Standard + + cname_user + cname_user + + + +BBBB; + + public function testParseValidXml1() + { + $response = new ResponseCore(array(), $this->validXml1, 200); + $result = new ListObjectsResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $objectListInfo = $result->getData(); + $this->assertEquals(2, count($objectListInfo->getPrefixList())); + $this->assertEquals(0, count($objectListInfo->getObjectList())); + $this->assertEquals('testbucket-hf', $objectListInfo->getBucketName()); + $this->assertEquals('', $objectListInfo->getPrefix()); + $this->assertEquals('', $objectListInfo->getMarker()); + $this->assertEquals(1000, $objectListInfo->getMaxKeys()); + $this->assertEquals('/', $objectListInfo->getDelimiter()); + $this->assertEquals('false', $objectListInfo->getIsTruncated()); + $prefixes = $objectListInfo->getPrefixList(); + $this->assertEquals('oss-php-sdk-test/', $prefixes[0]->getPrefix()); + $this->assertEquals('test/', $prefixes[1]->getPrefix()); + } + + public function testParseValidXml2() + { + $response = new ResponseCore(array(), $this->validXml2, 200); + $result = new ListObjectsResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $objectListInfo = $result->getData(); + $this->assertEquals(0, count($objectListInfo->getPrefixList())); + $this->assertEquals(1, count($objectListInfo->getObjectList())); + $this->assertEquals('testbucket-hf', $objectListInfo->getBucketName()); + $this->assertEquals('oss-php-sdk-test/', $objectListInfo->getPrefix()); + $this->assertEquals('xx', $objectListInfo->getMarker()); + $this->assertEquals(1000, $objectListInfo->getMaxKeys()); + $this->assertEquals('/', $objectListInfo->getDelimiter()); + $this->assertEquals('false', $objectListInfo->getIsTruncated()); + $objects = $objectListInfo->getObjectList(); + $this->assertEquals('oss-php-sdk-test/upload-test-object-name.txt', $objects[0]->getKey()); + $this->assertEquals('2015-11-18T03:36:00.000Z', $objects[0]->getLastModified()); + $this->assertEquals('"89B9E567E7EB8815F2F7D41851F9A2CD"', $objects[0]->getETag()); + $this->assertEquals('Normal', $objects[0]->getType()); + $this->assertEquals(13115, $objects[0]->getSize()); + $this->assertEquals('Standard', $objects[0]->getStorageClass()); + } + + public function testParseValidXmlWithEncodedKey() + { + $response = new ResponseCore(array(), $this->validXmlWithEncodedKey, 200); + $result = new ListObjectsResult($response); + $this->assertTrue($result->isOK()); + $this->assertNotNull($result->getData()); + $this->assertNotNull($result->getRawResponse()); + $objectListInfo = $result->getData(); + $this->assertEquals(0, count($objectListInfo->getPrefixList())); + $this->assertEquals(1, count($objectListInfo->getObjectList())); + $this->assertEquals('testbucket-hf', $objectListInfo->getBucketName()); + $this->assertEquals('php/prefix', $objectListInfo->getPrefix()); + $this->assertEquals('php/marker', $objectListInfo->getMarker()); + $this->assertEquals('php/next-marker', $objectListInfo->getNextMarker()); + $this->assertEquals(1000, $objectListInfo->getMaxKeys()); + $this->assertEquals('/', $objectListInfo->getDelimiter()); + $this->assertEquals('true', $objectListInfo->getIsTruncated()); + $objects = $objectListInfo->getObjectList(); + $this->assertEquals('php/a+b', $objects[0]->getKey()); + $this->assertEquals('2015-11-18T03:36:00.000Z', $objects[0]->getLastModified()); + $this->assertEquals('"89B9E567E7EB8815F2F7D41851F9A2CD"', $objects[0]->getETag()); + $this->assertEquals('Normal', $objects[0]->getType()); + $this->assertEquals(13115, $objects[0]->getSize()); + $this->assertEquals('Standard', $objects[0]->getStorageClass()); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListPartsResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListPartsResultTest.php new file mode 100644 index 0000000..c446714 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ListPartsResultTest.php @@ -0,0 +1,62 @@ + + + multipart_upload + multipart.data + 0004B999EF5A239BB9138C6227D69F95 + 5 + 1000 + false + + 1 + 2012-02-23T07:01:34.000Z + "3349DC700140D7F86A078484278075A9" + 6291456 + + + 2 + 2012-02-23T07:01:12.000Z + "3349DC700140D7F86A078484278075A9" + 6291456 + + + 5 + 2012-02-23T07:02:03.000Z + "7265F4D211B56873A381D321F586E4A9" + 1024 + + +BBBB; + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new ListPartsResult($response); + $listPartsInfo = $result->getData(); + $this->assertEquals("multipart_upload", $listPartsInfo->getBucket()); + $this->assertEquals("multipart.data", $listPartsInfo->getKey()); + $this->assertEquals("0004B999EF5A239BB9138C6227D69F95", $listPartsInfo->getUploadId()); + $this->assertEquals(5, $listPartsInfo->getNextPartNumberMarker()); + $this->assertEquals(1000, $listPartsInfo->getMaxParts()); + $this->assertEquals("false", $listPartsInfo->getIsTruncated()); + $this->assertEquals(3, count($listPartsInfo->getListPart())); + $parts = $listPartsInfo->getListPart(); + $this->assertEquals(1, $parts[0]->getPartNumber()); + $this->assertEquals('2012-02-23T07:01:34.000Z', $parts[0]->getLastModified()); + $this->assertEquals('"3349DC700140D7F86A078484278075A9"', $parts[0]->getETag()); + $this->assertEquals(6291456, $parts[0]->getSize()); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LiveChannelXmlTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LiveChannelXmlTest.php new file mode 100644 index 0000000..cc3e219 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LiveChannelXmlTest.php @@ -0,0 +1,249 @@ + + + xxx + enabled + + hls + 1000 + 5 + hello.m3u8 + + +BBBB; + + private $info = << + + live-1 + xxx + + rtmp://bucket.oss-cn-hangzhou.aliyuncs.com/live/213443245345 + + + http://bucket.oss-cn-hangzhou.aliyuncs.com/213443245345/播放列表.m3u8 + + enabled + 2015-11-24T14:25:31.000Z + +BBBB; + + private $list = << + +xxx + yyy + 100 + false + 121312132 + + 12123214323431 + xxx + + rtmp://bucket.oss-cn-hangzhou.aliyuncs.com/live/1 + + + http://bucket.oss-cn-hangzhou.aliyuncs.com/1/播放列表.m3u8 + + enabled + 2015-11-24T14:25:31.000Z + + + 432423432423 + yyy + + rtmp://bucket.oss-cn-hangzhou.aliyuncs.com/live/2 + + + http://bucket.oss-cn-hangzhou.aliyuncs.com/2/播放列表.m3u8 + + enabled + 2016-11-24T14:25:31.000Z + + +BBBB; + + private $status = << + + Live + 2016-10-20T14:25:31.000Z + 10.1.2.4:47745 + + + +BBBB; + + private $history = << + + + 2013-11-24T14:25:31.000Z + 2013-11-24T15:25:31.000Z + 10.101.194.148:56861 + + + 2014-11-24T14:25:31.000Z + 2014-11-24T15:25:31.000Z + 10.101.194.148:56862 + + + 2015-11-24T14:25:31.000Z + 2015-11-24T15:25:31.000Z + 10.101.194.148:56863 + + +BBBB; + + public function testLiveChannelStatus() + { + $stat = new GetLiveChannelStatus(); + $stat->parseFromXml($this->status); + + $this->assertEquals('Live', $stat->getStatus()); + $this->assertEquals('2016-10-20T14:25:31.000Z', $stat->getConnectedTime()); + $this->assertEquals('10.1.2.4:47745', $stat->getRemoteAddr()); + + $this->assertEquals(1280, $stat->getVideoWidth()); + $this->assertEquals(536, $stat->getVideoHeight()); + $this->assertEquals(24, $stat->getVideoFrameRate()); + $this->assertEquals(72513, $stat->getVideoBandwidth()); + $this->assertEquals('H264', $stat->getVideoCodec()); + $this->assertEquals(6519, $stat->getAudioBandwidth()); + $this->assertEquals(44100, $stat->getAudioSampleRate()); + $this->assertEquals('AAC', $stat->getAudioCodec()); + + } + + public function testLiveChannelHistory() + { + $history = new GetLiveChannelHistory(); + $history->parseFromXml($this->history); + + $recordList = $history->getLiveRecordList(); + $this->assertEquals(3, count($recordList)); + + $list0 = $recordList[0]; + $this->assertEquals('2013-11-24T14:25:31.000Z', $list0->getStartTime()); + $this->assertEquals('2013-11-24T15:25:31.000Z', $list0->getEndTime()); + $this->assertEquals('10.101.194.148:56861', $list0->getRemoteAddr()); + + $list1 = $recordList[1]; + $this->assertEquals('2014-11-24T14:25:31.000Z', $list1->getStartTime()); + $this->assertEquals('2014-11-24T15:25:31.000Z', $list1->getEndTime()); + $this->assertEquals('10.101.194.148:56862', $list1->getRemoteAddr()); + + $list2 = $recordList[2]; + $this->assertEquals('2015-11-24T14:25:31.000Z', $list2->getStartTime()); + $this->assertEquals('2015-11-24T15:25:31.000Z', $list2->getEndTime()); + $this->assertEquals('10.101.194.148:56863', $list2->getRemoteAddr()); + + } + + public function testLiveChannelConfig() + { + $config = new LiveChannelConfig(array('name' => 'live-1')); + $config->parseFromXml($this->config); + + $this->assertEquals('xxx', $config->getDescription()); + $this->assertEquals('enabled', $config->getStatus()); + $this->assertEquals('hls', $config->getType()); + $this->assertEquals(1000, $config->getFragDuration()); + $this->assertEquals(5, $config->getFragCount()); + $this->assertEquals('hello.m3u8', $config->getPlayListName()); + + $xml = $config->serializeToXml(); + $config2 = new LiveChannelConfig(array('name' => 'live-2')); + $config2->parseFromXml($xml); + $this->assertEquals('xxx', $config2->getDescription()); + $this->assertEquals('enabled', $config2->getStatus()); + $this->assertEquals('hls', $config2->getType()); + $this->assertEquals(1000, $config2->getFragDuration()); + $this->assertEquals(5, $config2->getFragCount()); + $this->assertEquals('hello.m3u8', $config2->getPlayListName()); + } + + public function testLiveChannelInfo() + { + $info = new LiveChannelInfo(array('name' => 'live-1')); + $info->parseFromXml($this->info); + + $this->assertEquals('live-1', $info->getName()); + $this->assertEquals('xxx', $info->getDescription()); + $this->assertEquals('enabled', $info->getStatus()); + $this->assertEquals('2015-11-24T14:25:31.000Z', $info->getLastModified()); + $pubs = $info->getPublishUrls(); + $this->assertEquals(1, count($pubs)); + $this->assertEquals('rtmp://bucket.oss-cn-hangzhou.aliyuncs.com/live/213443245345', $pubs[0]); + + $plays = $info->getPlayUrls(); + $this->assertEquals(1, count($plays)); + $this->assertEquals('http://bucket.oss-cn-hangzhou.aliyuncs.com/213443245345/播放列表.m3u8', $plays[0]); + } + + public function testLiveChannelList() + { + $list = new LiveChannelListInfo(); + $list->parseFromXml($this->list); + + $this->assertEquals('xxx', $list->getPrefix()); + $this->assertEquals('yyy', $list->getMarker()); + $this->assertEquals(100, $list->getMaxKeys()); + $this->assertEquals(false, $list->getIsTruncated()); + $this->assertEquals('121312132', $list->getNextMarker()); + + $channels = $list->getChannelList(); + $this->assertEquals(2, count($channels)); + + $chan1 = $channels[0]; + $this->assertEquals('12123214323431', $chan1->getName()); + $this->assertEquals('xxx', $chan1->getDescription()); + $this->assertEquals('enabled', $chan1->getStatus()); + $this->assertEquals('2015-11-24T14:25:31.000Z', $chan1->getLastModified()); + $pubs = $chan1->getPublishUrls(); + $this->assertEquals(1, count($pubs)); + $this->assertEquals('rtmp://bucket.oss-cn-hangzhou.aliyuncs.com/live/1', $pubs[0]); + + $plays = $chan1->getPlayUrls(); + $this->assertEquals(1, count($plays)); + $this->assertEquals('http://bucket.oss-cn-hangzhou.aliyuncs.com/1/播放列表.m3u8', $plays[0]); + + $chan2 = $channels[1]; + $this->assertEquals('432423432423', $chan2->getName()); + $this->assertEquals('yyy', $chan2->getDescription()); + $this->assertEquals('enabled', $chan2->getStatus()); + $this->assertEquals('2016-11-24T14:25:31.000Z', $chan2->getLastModified()); + $pubs = $chan2->getPublishUrls(); + $this->assertEquals(1, count($pubs)); + $this->assertEquals('rtmp://bucket.oss-cn-hangzhou.aliyuncs.com/live/2', $pubs[0]); + + $plays = $chan2->getPlayUrls(); + $this->assertEquals(1, count($plays)); + $this->assertEquals('http://bucket.oss-cn-hangzhou.aliyuncs.com/2/播放列表.m3u8', $plays[0]); + } + +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LoggingConfigTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LoggingConfigTest.php new file mode 100644 index 0000000..01496bb --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/LoggingConfigTest.php @@ -0,0 +1,47 @@ + + + +TargetBucket +TargetPrefix + + +BBBB; + + private $nullXml = << + +BBBB; + + public function testParseValidXml() + { + $loggingConfig = new LoggingConfig(); + $loggingConfig->parseFromXml($this->validXml); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml(strval($loggingConfig))); + } + + public function testConstruct() + { + $loggingConfig = new LoggingConfig('TargetBucket', 'TargetPrefix'); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($loggingConfig->serializeToXml())); + } + + public function testFailedConstruct() + { + $loggingConfig = new LoggingConfig('TargetBucket', null); + $this->assertEquals($this->cleanXml($this->nullXml), $this->cleanXml($loggingConfig->serializeToXml())); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/MimeTypesTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/MimeTypesTest.php new file mode 100644 index 0000000..0697409 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/MimeTypesTest.php @@ -0,0 +1,13 @@ +assertEquals('application/xml', MimeTypes::getMimetype('file.xml')); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ObjectAclTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ObjectAclTest.php new file mode 100644 index 0000000..d397288 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/ObjectAclTest.php @@ -0,0 +1,28 @@ +deleteObject($bucket, $object); + $client->putObject($bucket, $object, "hello world"); + + $acl = $client->getObjectAcl($bucket, $object); + $this->assertEquals('default', $acl); + + $client->putObjectAcl($bucket, $object, 'public-read'); + $acl = $client->getObjectAcl($bucket, $object); + $this->assertEquals('public-read', $acl); + + $content = $client->getObject($bucket, $object); + $this->assertEquals('hello world', $content); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketCorsTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketCorsTest.php new file mode 100644 index 0000000..a32154b --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketCorsTest.php @@ -0,0 +1,84 @@ +addAllowedHeader("x-oss-test"); + $rule->addAllowedHeader("x-oss-test2"); + $rule->addAllowedHeader("x-oss-test2"); + $rule->addAllowedHeader("x-oss-test3"); + $rule->addAllowedOrigin("http://www.b.com"); + $rule->addAllowedOrigin("http://www.a.com"); + $rule->addAllowedOrigin("http://www.a.com"); + $rule->addAllowedMethod("GET"); + $rule->addAllowedMethod("PUT"); + $rule->addAllowedMethod("POST"); + $rule->addExposeHeader("x-oss-test1"); + $rule->addExposeHeader("x-oss-test1"); + $rule->addExposeHeader("x-oss-test2"); + $rule->setMaxAgeSeconds(10); + $corsConfig->addRule($rule); + $rule = new CorsRule(); + $rule->addAllowedHeader("x-oss-test"); + $rule->addAllowedMethod("GET"); + $rule->addAllowedOrigin("http://www.b.com"); + $rule->addExposeHeader("x-oss-test1"); + $rule->setMaxAgeSeconds(110); + $corsConfig->addRule($rule); + + try { + $this->ossClient->putBucketCors($this->bucket, $corsConfig); + } catch (OssException $e) { + $this->assertFalse(True); + } + + try { + Common::waitMetaSync(); + $object = "cors/test.txt"; + $this->ossClient->putObject($this->bucket, $object, file_get_contents(__FILE__)); + $headers = $this->ossClient->optionsObject($this->bucket, $object, "http://www.a.com", "GET", "", null); + $this->assertNotEmpty($headers); + } catch (OssException $e) { + var_dump($e->getMessage()); + } + + try { + Common::waitMetaSync(); + $corsConfig2 = $this->ossClient->getBucketCors($this->bucket); + $this->assertNotNull($corsConfig2); + $this->assertEquals($corsConfig->serializeToXml(), $corsConfig2->serializeToXml()); + } catch (OssException $e) { + $this->assertFalse(True); + } + + try { + Common::waitMetaSync(); + $this->ossClient->deleteBucketCors($this->bucket); + } catch (OssException $e) { + $this->assertFalse(True); + } + + try { + Common::waitMetaSync(); + $corsConfig3 = $this->ossClient->getBucketCors($this->bucket); + $this->assertNotNull($corsConfig3); + $this->assertNotEquals($corsConfig->serializeToXml(), $corsConfig3->serializeToXml()); + } catch (OssException $e) { + $this->assertFalse(True); + } + + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketLifecycleTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketLifecycleTest.php new file mode 100644 index 0000000..46da1f0 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketLifecycleTest.php @@ -0,0 +1,57 @@ +addRule($lifecycleRule); + $actions = array(); + $actions[] = new LifecycleAction("Expiration", "Date", '2022-10-12T00:00:00.000Z'); + $lifecycleRule = new LifecycleRule("delete temporary files", "temporary/", "Enabled", $actions); + $lifecycleConfig->addRule($lifecycleRule); + + try { + $this->ossClient->putBucketLifecycle($this->bucket, $lifecycleConfig); + } catch (OssException $e) { + $this->assertTrue(false); + } + + try { + Common::waitMetaSync(); + $lifecycleConfig2 = $this->ossClient->getBucketLifecycle($this->bucket); + $this->assertEquals($lifecycleConfig->serializeToXml(), $lifecycleConfig2->serializeToXml()); + } catch (OssException $e) { + $this->assertTrue(false); + } + + try { + Common::waitMetaSync(); + $this->ossClient->deleteBucketLifecycle($this->bucket); + } catch (OssException $e) { + $this->assertTrue(false); + } + + try { + Common::waitMetaSync(); + $lifecycleConfig3 = $this->ossClient->getBucketLifecycle($this->bucket); + $this->assertNotEquals($lifecycleConfig->serializeToXml(), $lifecycleConfig3->serializeToXml()); + } catch (OssException $e) { + $this->assertTrue(false); + } + + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketLoggingTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketLoggingTest.php new file mode 100644 index 0000000..16a10eb --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketLoggingTest.php @@ -0,0 +1,43 @@ +bucket, 'prefix'); + try { + $this->ossClient->putBucketLogging($this->bucket, $this->bucket, 'prefix'); + } catch (OssException $e) { + var_dump($e->getMessage()); + $this->assertTrue(false); + } + try { + Common::waitMetaSync(); + $loggingConfig2 = $this->ossClient->getBucketLogging($this->bucket); + $this->assertEquals($loggingConfig->serializeToXml(), $loggingConfig2->serializeToXml()); + } catch (OssException $e) { + $this->assertTrue(false); + } + try { + Common::waitMetaSync(); + $this->ossClient->deleteBucketLogging($this->bucket); + } catch (OssException $e) { + $this->assertTrue(false); + } + try { + Common::waitMetaSync(); + $loggingConfig3 = $this->ossClient->getBucketLogging($this->bucket); + $this->assertNotEquals($loggingConfig->serializeToXml(), $loggingConfig3->serializeToXml()); + } catch (OssException $e) { + $this->assertTrue(false); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketRefererTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketRefererTest.php new file mode 100644 index 0000000..ba7d14f --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketRefererTest.php @@ -0,0 +1,48 @@ +addReferer('http://www.aliyun.com'); + + try { + $this->ossClient->putBucketReferer($this->bucket, $refererConfig); + } catch (OssException $e) { + var_dump($e->getMessage()); + $this->assertTrue(false); + } + try { + Common::waitMetaSync(); + $refererConfig2 = $this->ossClient->getBucketReferer($this->bucket); + $this->assertEquals($refererConfig->serializeToXml(), $refererConfig2->serializeToXml()); + } catch (OssException $e) { + $this->assertTrue(false); + } + try { + Common::waitMetaSync(); + $nullRefererConfig = new RefererConfig(); + $nullRefererConfig->setAllowEmptyReferer(false); + $this->ossClient->putBucketReferer($this->bucket, $nullRefererConfig); + } catch (OssException $e) { + $this->assertTrue(false); + } + try { + Common::waitMetaSync(); + $refererConfig3 = $this->ossClient->getBucketLogging($this->bucket); + $this->assertNotEquals($refererConfig->serializeToXml(), $refererConfig3->serializeToXml()); + } catch (OssException $e) { + $this->assertTrue(false); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketStorageCapacityTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketStorageCapacityTest.php new file mode 100644 index 0000000..87548f9 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketStorageCapacityTest.php @@ -0,0 +1,56 @@ +ossClient->getBucketStorageCapacity($this->bucket); + $this->assertEquals($storageCapacity, -1); + } catch (OssException $e) { + $this->assertTrue(false); + } + + try { + $this->ossClient->putBucketStorageCapacity($this->bucket, 1000); + } catch (OssException $e) { + $this->assertTrue(false); + } + + try { + Common::waitMetaSync(); + $storageCapacity = $this->ossClient->getBucketStorageCapacity($this->bucket); + $this->assertEquals($storageCapacity, 1000); + } catch (OssException $e) { + $this->assertTrue(false); + } + + try { + $this->ossClient->putBucketStorageCapacity($this->bucket, 0); + + Common::waitMetaSync(); + + $storageCapacity = $this->ossClient->getBucketStorageCapacity($this->bucket); + $this->assertEquals($storageCapacity, 0); + + $this->ossClient->putObject($this->bucket, 'test-storage-capacity','test-content'); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals('Bucket storage exceed max storage capacity.',$e->getErrorMessage()); + } + + try { + $this->ossClient->putBucketStorageCapacity($this->bucket, -2); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals(400, $e->getHTTPStatus()); + $this->assertEquals('InvalidArgument', $e->getErrorCode()); + } + } + +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketTest.php new file mode 100644 index 0000000..f207ca1 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketTest.php @@ -0,0 +1,113 @@ +ossClient->createBucket("s"); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('"s"bucket name is invalid', $e->getMessage()); + } + } + + public function testBucketWithInvalidACL() + { + try { + $this->ossClient->createBucket($this->bucket, "invalid"); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('invalid:acl is invalid(private,public-read,public-read-write)', $e->getMessage()); + } + } + + public function testBucket() + { + $this->ossClient->createBucket($this->bucket, OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE); + + $bucketListInfo = $this->ossClient->listBuckets(); + $this->assertNotNull($bucketListInfo); + + $bucketList = $bucketListInfo->getBucketList(); + $this->assertTrue(is_array($bucketList)); + $this->assertGreaterThan(0, count($bucketList)); + + $this->ossClient->putBucketAcl($this->bucket, OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE); + Common::waitMetaSync(); + $this->assertEquals($this->ossClient->getBucketAcl($this->bucket), OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE); + + $this->assertTrue($this->ossClient->doesBucketExist($this->bucket)); + $this->assertFalse($this->ossClient->doesBucketExist($this->bucket . '-notexist')); + + $this->assertEquals($this->ossClient->getBucketLocation($this->bucket), 'oss-us-west-1'); + + $res = $this->ossClient->getBucketMeta($this->bucket); + $this->assertEquals('200', $res['info']['http_code']); + $this->assertEquals('oss-us-west-1', $res['x-oss-bucket-region']); + } + + public function testCreateBucketWithStorageType() + { + $object = 'storage-object'; + + $this->ossClient->putObject($this->archiveBucket, $object,'testcontent'); + try { + $this->ossClient->getObject($this->archiveBucket, $object); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals('403', $e->getHTTPStatus()); + $this->assertEquals('InvalidObjectState', $e->getErrorCode()); + } + + $this->ossClient->putObject($this->iaBucket, $object,'testcontent'); + $result = $this->ossClient->getObject($this->iaBucket, $object); + $this->assertEquals($result, 'testcontent'); + + $this->ossClient->putObject($this->bucket, $object,'testcontent'); + $result = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($result, 'testcontent'); + } + + public function setUp() + { + parent::setUp(); + + $this->iaBucket = 'ia-' . $this->bucket; + $this->archiveBucket = 'archive-' . $this->bucket; + $options = array( + OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_IA + ); + + $this->ossClient->createBucket($this->iaBucket, OssClient::OSS_ACL_TYPE_PRIVATE, $options); + + $options = array( + OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_ARCHIVE + ); + + $this->ossClient->createBucket($this->archiveBucket, OssClient::OSS_ACL_TYPE_PRIVATE, $options); + } + + public function tearDown() + { + parent::tearDown(); + + $object = 'storage-object'; + + $this->ossClient->deleteObject($this->iaBucket, $object); + $this->ossClient->deleteObject($this->archiveBucket, $object); + $this->ossClient->deleteBucket($this->iaBucket); + $this->ossClient->deleteBucket($this->archiveBucket); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketWebsiteTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketWebsiteTest.php new file mode 100644 index 0000000..dfa9cc1 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientBucketWebsiteTest.php @@ -0,0 +1,46 @@ +ossClient->putBucketWebsite($this->bucket, $websiteConfig); + } catch (OssException $e) { + var_dump($e->getMessage()); + $this->assertTrue(false); + } + + try { + Common::waitMetaSync(); + $websiteConfig2 = $this->ossClient->getBucketWebsite($this->bucket); + $this->assertEquals($websiteConfig->serializeToXml(), $websiteConfig2->serializeToXml()); + } catch (OssException $e) { + $this->assertTrue(false); + } + try { + Common::waitMetaSync(); + $this->ossClient->deleteBucketWebsite($this->bucket); + } catch (OssException $e) { + $this->assertTrue(false); + } + try { + Common::waitMetaSync(); + $websiteConfig3 = $this->ossClient->getBucketLogging($this->bucket); + $this->assertNotEquals($websiteConfig->serializeToXml(), $websiteConfig3->serializeToXml()); + } catch (OssException $e) { + $this->assertTrue(false); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientImageTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientImageTest.php new file mode 100644 index 0000000..df8bd6c --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientImageTest.php @@ -0,0 +1,100 @@ +client = Common::getOssClient(); + $this->bucketName = 'php-sdk-test-bucket-image-' . strval(rand(0, 10000)); + $this->client->createBucket($this->bucketName); + Common::waitMetaSync(); + $this->local_file = "example.jpg"; + $this->object = "oss-example.jpg"; + $this->download_file = "image.jpg"; + + $this->client->uploadFile($this->bucketName, $this->object, $this->local_file); + } + + public function tearDown() + { + $this->client->deleteObject($this->bucketName, $this->object); + $this->client->deleteBucket($this->bucketName); + } + + public function testImageResize() + { + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $this->download_file, + OssClient::OSS_PROCESS => "image/resize,m_fixed,h_100,w_100", ); + $this->check($options, 100, 100, 3267, 'jpg'); + } + + public function testImageCrop() + { + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $this->download_file, + OssClient::OSS_PROCESS => "image/crop,w_100,h_100,x_100,y_100,r_1", ); + $this->check($options, 100, 100, 1969, 'jpg'); + } + + public function testImageRotate() + { + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $this->download_file, + OssClient::OSS_PROCESS => "image/rotate,90", ); + $this->check($options, 267, 400, 20998, 'jpg'); + } + + public function testImageSharpen() + { + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $this->download_file, + OssClient::OSS_PROCESS => "image/sharpen,100", ); + $this->check($options, 400, 267, 23015, 'jpg'); + } + + public function testImageWatermark() + { + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $this->download_file, + OssClient::OSS_PROCESS => "image/watermark,text_SGVsbG8g5Zu-54mH5pyN5YqhIQ", ); + $this->check($options, 400, 267, 26369, 'jpg'); + } + + public function testImageFormat() + { + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $this->download_file, + OssClient::OSS_PROCESS => "image/format,png", ); + $this->check($options, 400, 267, 160733, 'png'); + } + + public function testImageTofile() + { + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $this->download_file, + OssClient::OSS_PROCESS => "image/resize,m_fixed,w_100,h_100", ); + $this->check($options, 100, 100, 3267, 'jpg'); + } + + private function check($options, $width, $height, $size, $type) + { + $this->client->getObject($this->bucketName, $this->object, $options); + $array = getimagesize($this->download_file); + $this->assertEquals($width, $array[0]); + $this->assertEquals($height, $array[1]); + $this->assertEquals($type === 'jpg' ? 2 : 3, $array[2]);//2 <=> jpg + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientMultipartUploadTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientMultipartUploadTest.php new file mode 100644 index 0000000..a95f412 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientMultipartUploadTest.php @@ -0,0 +1,313 @@ +ossClient->uploadDir($this->bucket, "", "abc/ds/s/s/notexitst"); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals("parameter error: abc/ds/s/s/notexitst is not a directory, please check it", $e->getMessage()); + } + + } + + public function testMultipartUploadBigFile() + { + $bigFileName = __DIR__ . DIRECTORY_SEPARATOR . "/bigfile.tmp"; + $localFilename = __DIR__ . DIRECTORY_SEPARATOR . "/localfile.tmp"; + OssUtil::generateFile($bigFileName, 6 * 1024 * 1024); + $object = 'mpu/multipart-bigfile-test.tmp'; + try { + $this->ossClient->multiuploadFile($this->bucket, $object, $bigFileName, array(OssClient::OSS_PART_SIZE => 1)); + $options = array(OssClient::OSS_FILE_DOWNLOAD => $localFilename); + $this->ossClient->getObject($this->bucket, $object, $options); + $this->assertEquals(md5_file($bigFileName), md5_file($localFilename)); + } catch (OssException $e) { + var_dump($e->getMessage()); + $this->assertFalse(true); + } + unlink($bigFileName); + unlink($localFilename); + } + + public function testMultipartUploadBigFileWithMD5Check() + { + $bigFileName = __DIR__ . DIRECTORY_SEPARATOR . "/bigfile.tmp"; + $localFilename = __DIR__ . DIRECTORY_SEPARATOR . "/localfile.tmp"; + OssUtil::generateFile($bigFileName, 6 * 1024 * 1024); + $object = 'mpu/multipart-bigfile-test.tmp'; + $options = array( + OssClient::OSS_CHECK_MD5 => true, + OssClient::OSS_PART_SIZE => 1, + ); + try { + $this->ossClient->multiuploadFile($this->bucket, $object, $bigFileName, $options); + $options = array(OssClient::OSS_FILE_DOWNLOAD => $localFilename); + $this->ossClient->getObject($this->bucket, $object, $options); + $this->assertEquals(md5_file($bigFileName), md5_file($localFilename)); + } catch (OssException $e) { + var_dump($e->getMessage()); + $this->assertFalse(true); + } + unlink($bigFileName); + unlink($localFilename); + } + + public function testCopyPart() + { + $object = "mpu/multipart-test.txt"; + $copiedObject = "mpu/multipart-test.txt.copied"; + $this->ossClient->putObject($this->bucket, $copiedObject, file_get_contents(__FILE__)); + /** + * step 1. 初始化一个分块上传事件, 也就是初始化上传Multipart, 获取upload id + */ + try { + $upload_id = $this->ossClient->initiateMultipartUpload($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + /* + * step 2. uploadPartCopy + */ + $copyId = 1; + $eTag = $this->ossClient->uploadPartCopy($this->bucket, $copiedObject, $this->bucket, $object, $copyId, $upload_id); + $upload_parts[] = array( + 'PartNumber' => $copyId, + 'ETag' => $eTag, + ); + + try { + $listPartsInfo = $this->ossClient->listParts($this->bucket, $object, $upload_id); + $this->assertNotNull($listPartsInfo); + } catch (OssException $e) { + $this->assertTrue(false); + } + + /** + * step 3. + */ + try { + $this->ossClient->completeMultipartUpload($this->bucket, $object, $upload_id, $upload_parts); + } catch (OssException $e) { + var_dump($e->getMessage()); + $this->assertTrue(false); + } + + $this->assertEquals($this->ossClient->getObject($this->bucket, $object), file_get_contents(__FILE__)); + $this->assertEquals($this->ossClient->getObject($this->bucket, $copiedObject), file_get_contents(__FILE__)); + } + + public function testAbortMultipartUpload() + { + $object = "mpu/multipart-test.txt"; + /** + * step 1. 初始化一个分块上传事件, 也就是初始化上传Multipart, 获取upload id + */ + try { + $upload_id = $this->ossClient->initiateMultipartUpload($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + /* + * step 2. 上传分片 + */ + $part_size = 10 * 1024 * 1024; + $upload_file = __FILE__; + $upload_filesize = filesize($upload_file); + $pieces = $this->ossClient->generateMultiuploadParts($upload_filesize, $part_size); + $response_upload_part = array(); + $upload_position = 0; + $is_check_md5 = true; + foreach ($pieces as $i => $piece) { + $from_pos = $upload_position + (integer)$piece[OssClient::OSS_SEEK_TO]; + $to_pos = (integer)$piece[OssClient::OSS_LENGTH] + $from_pos - 1; + $up_options = array( + OssClient::OSS_FILE_UPLOAD => $upload_file, + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_SEEK_TO => $from_pos, + OssClient::OSS_LENGTH => $to_pos - $from_pos + 1, + OssClient::OSS_CHECK_MD5 => $is_check_md5, + ); + if ($is_check_md5) { + $content_md5 = OssUtil::getMd5SumForFile($upload_file, $from_pos, $to_pos); + $up_options[OssClient::OSS_CONTENT_MD5] = $content_md5; + } + //2. 将每一分片上传到OSS + try { + $response_upload_part[] = $this->ossClient->uploadPart($this->bucket, $object, $upload_id, $up_options); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + $upload_parts = array(); + foreach ($response_upload_part as $i => $eTag) { + $upload_parts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + + try { + $listPartsInfo = $this->ossClient->listParts($this->bucket, $object, $upload_id); + $this->assertNotNull($listPartsInfo); + } catch (OssException $e) { + $this->assertTrue(false); + } + $this->assertEquals(1, count($listPartsInfo->getListPart())); + + $numOfMultipartUpload1 = 0; + $options = null; + try { + $listMultipartUploadInfo = $listMultipartUploadInfo = $this->ossClient->listMultipartUploads($this->bucket, $options); + $this->assertNotNull($listMultipartUploadInfo); + $numOfMultipartUpload1 = count($listMultipartUploadInfo->getUploads()); + } catch (OssException $e) { + $this->assertFalse(true); + } + + try { + $this->ossClient->abortMultipartUpload($this->bucket, $object, $upload_id); + } catch (OssException $e) { + $this->assertTrue(false); + } + + $numOfMultipartUpload2 = 0; + try { + $listMultipartUploadInfo = $listMultipartUploadInfo = $this->ossClient->listMultipartUploads($this->bucket, $options); + $this->assertNotNull($listMultipartUploadInfo); + $numOfMultipartUpload2 = count($listMultipartUploadInfo->getUploads()); + } catch (OssException $e) { + $this->assertFalse(true); + } + $this->assertEquals($numOfMultipartUpload1 - 1, $numOfMultipartUpload2); + } + + public function testPutObjectByRawApis() + { + $object = "mpu/multipart-test.txt"; + /** + * step 1. 初始化一个分块上传事件, 也就是初始化上传Multipart, 获取upload id + */ + try { + $upload_id = $this->ossClient->initiateMultipartUpload($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + /* + * step 2. 上传分片 + */ + $part_size = 10 * 1024 * 1024; + $upload_file = __FILE__; + $upload_filesize = filesize($upload_file); + $pieces = $this->ossClient->generateMultiuploadParts($upload_filesize, $part_size); + $response_upload_part = array(); + $upload_position = 0; + $is_check_md5 = true; + foreach ($pieces as $i => $piece) { + $from_pos = $upload_position + (integer)$piece[OssClient::OSS_SEEK_TO]; + $to_pos = (integer)$piece[OssClient::OSS_LENGTH] + $from_pos - 1; + $up_options = array( + OssClient::OSS_FILE_UPLOAD => $upload_file, + OssClient::OSS_PART_NUM => ($i + 1), + OssClient::OSS_SEEK_TO => $from_pos, + OssClient::OSS_LENGTH => $to_pos - $from_pos + 1, + OssClient::OSS_CHECK_MD5 => $is_check_md5, + ); + if ($is_check_md5) { + $content_md5 = OssUtil::getMd5SumForFile($upload_file, $from_pos, $to_pos); + $up_options[OssClient::OSS_CONTENT_MD5] = $content_md5; + } + //2. 将每一分片上传到OSS + try { + $response_upload_part[] = $this->ossClient->uploadPart($this->bucket, $object, $upload_id, $up_options); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + $upload_parts = array(); + foreach ($response_upload_part as $i => $eTag) { + $upload_parts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $eTag, + ); + } + + try { + $listPartsInfo = $this->ossClient->listParts($this->bucket, $object, $upload_id); + $this->assertNotNull($listPartsInfo); + } catch (OssException $e) { + $this->assertTrue(false); + } + + /** + * step 3. + */ + try { + $this->ossClient->completeMultipartUpload($this->bucket, $object, $upload_id, $upload_parts); + } catch (OssException $e) { + $this->assertTrue(false); + } + } + + function testPutObjectsByDir() + { + $localDirectory = dirname(__FILE__); + $prefix = "samples/codes"; + try { + $this->ossClient->uploadDir($this->bucket, $prefix, $localDirectory); + } catch (OssException $e) { + var_dump($e->getMessage()); + $this->assertFalse(true); + + } + $this->assertTrue($this->ossClient->doesObjectExist($this->bucket, 'samples/codes/' . "OssClientMultipartUploadTest.php")); + } + + public function testPutObjectByMultipartUpload() + { + $object = "mpu/multipart-test.txt"; + $file = __FILE__; + $options = array(); + + try { + $this->ossClient->multiuploadFile($this->bucket, $object, $file, $options); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testPutObjectByMultipartUploadWithMD5Check() + { + $object = "mpu/multipart-test.txt"; + $file = __FILE__; + $options = array(OssClient::OSS_CHECK_MD5 => true); + + try { + $this->ossClient->multiuploadFile($this->bucket, $object, $file, $options); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testListMultipartUploads() + { + $options = null; + try { + $listMultipartUploadInfo = $this->ossClient->listMultipartUploads($this->bucket, $options); + $this->assertNotNull($listMultipartUploadInfo); + } catch (OssException $e) { + $this->assertFalse(true); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientObjectTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientObjectTest.php new file mode 100644 index 0000000..34e3ded --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientObjectTest.php @@ -0,0 +1,588 @@ +ossClient->getObjectMeta($this->bucket, $object); + $this->assertEquals('200', $res['info']['http_code']); + $this->assertEquals('text/plain', $res['content-type']); + $this->assertEquals('Accept-Encoding', $res['vary']); + $this->assertTrue(isset($res['content-length'])); + $this->assertFalse(isset($res['content-encoding'])); + } catch (OssException $e) { + $this->assertTrue(false); + } + + $options = array(OssClient::OSS_HEADERS => array(OssClient::OSS_ACCEPT_ENCODING => 'deflate, gzip')); + + try { + $res = $this->ossClient->getObjectMeta($this->bucket, $object, $options); + $this->assertEquals('200', $res['info']['http_code']); + $this->assertEquals('text/plain', $res['content-type']); + $this->assertEquals('Accept-Encoding', $res['vary']); + $this->assertFalse(isset($res['content-length'])); + $this->assertEquals('gzip', $res['content-encoding']); + } catch (OssException $e) { + $this->assertTrue(false); + } + } + + public function testGetObjectWithAcceptEncoding() + { + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $options = array(OssClient::OSS_HEADERS => array(OssClient::OSS_ACCEPT_ENCODING => 'deflate, gzip')); + + try { + $res = $this->ossClient->getObject($this->bucket, $object, $options); + $this->assertEquals(file_get_contents(__FILE__), $res); + } catch (OssException $e) { + $this->assertTrue(false); + } + } + + public function testGetObjectWithHeader() + { + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + try { + $res = $this->ossClient->getObject($this->bucket, $object, array(OssClient::OSS_LAST_MODIFIED => "xx")); + $this->assertEquals(file_get_contents(__FILE__), $res); + } catch (OssException $e) { + $this->assertEquals('"/ilegal.txt" object name is invalid', $e->getMessage()); + } + } + + public function testGetObjectWithIleggalEtag() + { + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + try { + $res = $this->ossClient->getObject($this->bucket, $object, array(OssClient::OSS_ETAG => "xx")); + $this->assertEquals(file_get_contents(__FILE__), $res); + } catch (OssException $e) { + $this->assertEquals('"/ilegal.txt" object name is invalid', $e->getMessage()); + } + } + + public function testObject() + { + /** + * 上传本地变量到bucket + */ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $content = file_get_contents(__FILE__); + $options = array( + OssClient::OSS_LENGTH => strlen($content), + OssClient::OSS_HEADERS => array( + 'Expires' => 'Fri, 28 Feb 2020 05:38:42 GMT', + 'Cache-Control' => 'no-cache', + 'Content-Disposition' => 'attachment;filename=oss_download.log', + 'Content-Encoding' => 'utf-8', + 'Content-Language' => 'zh-CN', + 'x-oss-server-side-encryption' => 'AES256', + 'x-oss-meta-self-define-title' => 'user define meta info', + ), + ); + + try { + $this->ossClient->putObject($this->bucket, $object, $content, $options); + } catch (OssException $e) { + $this->assertFalse(true); + } + + try { + $this->ossClient->putObject($this->bucket, $object, $content, $options); + } catch (OssException $e) { + $this->assertFalse(true); + } + + try { + $result = $this->ossClient->deleteObjects($this->bucket, "stringtype", $options); + $this->assertEquals('stringtype', $result[0]); + } catch (OssException $e) { + $this->assertEquals('objects must be array', $e->getMessage()); + } + + try { + $result = $this->ossClient->deleteObjects($this->bucket, "stringtype", $options); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('objects must be array', $e->getMessage()); + } + + try { + $this->ossClient->uploadFile($this->bucket, $object, "notexist.txt", $options); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('notexist.txt file does not exist', $e->getMessage()); + } + + /** + * getObject到本地变量,检查是否match + */ + try { + $content = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($content, file_get_contents(__FILE__)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * getObject的前五个字节 + */ + try { + $options = array(OssClient::OSS_RANGE => '0-4'); + $content = $this->ossClient->getObject($this->bucket, $object, $options); + $this->assertEquals($content, 'assertFalse(true); + } + + + /** + * 上传本地文件到object + */ + try { + $this->ossClient->uploadFile($this->bucket, $object, __FILE__); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 下载文件到本地变量,检查是否match + */ + try { + $content = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($content, file_get_contents(__FILE__)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 下载文件到本地文件 + */ + $localfile = "upload-test-object-name.txt"; + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $localfile, + ); + + try { + $this->ossClient->getObject($this->bucket, $object, $options); + } catch (OssException $e) { + $this->assertFalse(true); + } + $this->assertTrue(file_get_contents($localfile) === file_get_contents(__FILE__)); + if (file_exists($localfile)) { + unlink($localfile); + } + + /** + * 下载文件到本地文件 no such key + */ + $localfile = "upload-test-object-name-no-such-key.txt"; + $options = array( + OssClient::OSS_FILE_DOWNLOAD => $localfile, + ); + + try { + $this->ossClient->getObject($this->bucket, $object . "no-such-key", $options); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertTrue(true); + $this->assertFalse(file_exists($localfile)); + if (strpos($e, "The specified key does not exist") == false) + { + $this->assertTrue(true); + } + } + + /** + * 下载文件到内容 no such key + */ + try { + $result = $this->ossClient->getObject($this->bucket, $object . "no-such-key"); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertTrue(true); + if (strpos($e, "The specified key does not exist") == false) + { + $this->assertTrue(true); + } + } + + /** + * 复制object + */ + $to_bucket = $this->bucket; + $to_object = $object . '.copy'; + $options = array(); + try { + $result = $this->ossClient->copyObject($this->bucket, $object, $to_bucket, $to_object, $options); + $this->assertFalse(empty($result)); + $this->assertEquals(strlen("2016-11-21T03:46:58.000Z"), strlen($result[0])); + $this->assertEquals(strlen("\"5B3C1A2E053D763E1B002CC607C5A0FE\""), strlen($result[1])); + } catch (OssException $e) { + $this->assertFalse(true); + var_dump($e->getMessage()); + + } + + /** + * 检查复制的是否相同 + */ + try { + $content = $this->ossClient->getObject($this->bucket, $to_object); + $this->assertEquals($content, file_get_contents(__FILE__)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 列出bucket内的文件列表 + */ + $prefix = ''; + $delimiter = '/'; + $next_marker = ''; + $maxkeys = 1000; + $options = array( + 'delimiter' => $delimiter, + 'prefix' => $prefix, + 'max-keys' => $maxkeys, + 'marker' => $next_marker, + ); + + try { + $listObjectInfo = $this->ossClient->listObjects($this->bucket, $options); + $objectList = $listObjectInfo->getObjectList(); + $prefixList = $listObjectInfo->getPrefixList(); + $this->assertNotNull($objectList); + $this->assertNotNull($prefixList); + $this->assertTrue(is_array($objectList)); + $this->assertTrue(is_array($prefixList)); + + } catch (OssException $e) { + $this->assertTrue(false); + } + + /** + * 设置文件的meta信息 + */ + $from_bucket = $this->bucket; + $from_object = "oss-php-sdk-test/upload-test-object-name.txt"; + $to_bucket = $from_bucket; + $to_object = $from_object; + $copy_options = array( + OssClient::OSS_HEADERS => array( + 'Expires' => '2012-10-01 08:00:00', + 'Content-Disposition' => 'attachment; filename="xxxxxx"', + ), + ); + try { + $this->ossClient->copyObject($from_bucket, $from_object, $to_bucket, $to_object, $copy_options); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 获取文件的meta信息 + */ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + try { + $objectMeta = $this->ossClient->getObjectMeta($this->bucket, $object); + $this->assertEquals('attachment; filename="xxxxxx"', $objectMeta[strtolower('Content-Disposition')]); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 删除单个文件 + */ + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + + try { + $this->assertTrue($this->ossClient->doesObjectExist($this->bucket, $object)); + $this->ossClient->deleteObject($this->bucket, $object); + $this->assertFalse($this->ossClient->doesObjectExist($this->bucket, $object)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 删除多个个文件 + */ + $object1 = "oss-php-sdk-test/upload-test-object-name.txt"; + $object2 = "oss-php-sdk-test/upload-test-object-name.txt.copy"; + $list = array($object1, $object2); + try { + $this->assertTrue($this->ossClient->doesObjectExist($this->bucket, $object2)); + + $result = $this->ossClient->deleteObjects($this->bucket, $list); + $this->assertEquals($list[1], $result[0]); + $this->assertEquals($list[0], $result[1]); + + $result = $this->ossClient->deleteObjects($this->bucket, $list, array('quiet' => 'true')); + $this->assertEquals(array(), $result); + $this->assertFalse($this->ossClient->doesObjectExist($this->bucket, $object2)); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testAppendObject() + { + $object = "oss-php-sdk-test/append-test-object-name.txt"; + $content_array = array('Hello OSS', 'Hi OSS', 'OSS OK'); + + /** + * 追加上传字符串 + */ + try { + $position = $this->ossClient->appendObject($this->bucket, $object, $content_array[0], 0); + $this->assertEquals($position, strlen($content_array[0])); + $position = $this->ossClient->appendObject($this->bucket, $object, $content_array[1], $position); + $this->assertEquals($position, strlen($content_array[0]) + strlen($content_array[1])); + $position = $this->ossClient->appendObject($this->bucket, $object, $content_array[2], $position); + $this->assertEquals($position, strlen($content_array[0]) + strlen($content_array[1]) + strlen($content_array[1])); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 检查内容的是否相同 + */ + try { + $content = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($content, implode($content_array)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + + /** + * 删除测试object + */ + try { + $this->ossClient->deleteObject($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 追加上传本地文件 + */ + try { + $position = $this->ossClient->appendFile($this->bucket, $object, __FILE__, 0); + $this->assertEquals($position, filesize(__FILE__)); + $position = $this->ossClient->appendFile($this->bucket, $object, __FILE__, $position); + $this->assertEquals($position, filesize(__FILE__) * 2); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 检查复制的是否相同 + */ + try { + $content = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($content, file_get_contents(__FILE__) . file_get_contents(__FILE__)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 删除测试object + */ + try { + $this->ossClient->deleteObject($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + + + $options = array( + OssClient::OSS_HEADERS => array( + 'Expires' => '2012-10-01 08:00:00', + 'Content-Disposition' => 'attachment; filename="xxxxxx"', + ), + ); + + /** + * 带option的追加上传 + */ + try { + $position = $this->ossClient->appendObject($this->bucket, $object, "Hello OSS, ", 0, $options); + $position = $this->ossClient->appendObject($this->bucket, $object, "Hi OSS.", $position); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 获取文件的meta信息 + */ + try { + $objectMeta = $this->ossClient->getObjectMeta($this->bucket, $object); + $this->assertEquals('attachment; filename="xxxxxx"', $objectMeta[strtolower('Content-Disposition')]); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 删除测试object + */ + try { + $this->ossClient->deleteObject($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testPutIllelObject() + { + $object = "/ilegal.txt"; + try { + $this->ossClient->putObject($this->bucket, $object, "hi", null); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('"/ilegal.txt" object name is invalid', $e->getMessage()); + } + } + + public function testCheckMD5() + { + $object = "oss-php-sdk-test/upload-test-object-name.txt"; + $content = file_get_contents(__FILE__); + $options = array(OssClient::OSS_CHECK_MD5 => true); + + /** + * 上传数据开启MD5 + */ + try { + $this->ossClient->putObject($this->bucket, $object, $content, $options); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 检查复制的是否相同 + */ + try { + $content = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($content, file_get_contents(__FILE__)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 上传文件开启MD5 + */ + try { + $this->ossClient->uploadFile($this->bucket, $object, __FILE__, $options); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 检查复制的是否相同 + */ + try { + $content = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($content, file_get_contents(__FILE__)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 删除测试object + */ + try { + $this->ossClient->deleteObject($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + + $object = "oss-php-sdk-test/append-test-object-name.txt"; + $content_array = array('Hello OSS', 'Hi OSS', 'OSS OK'); + $options = array(OssClient::OSS_CHECK_MD5 => true); + + /** + * 追加上传字符串 + */ + try { + $position = $this->ossClient->appendObject($this->bucket, $object, $content_array[0], 0, $options); + $this->assertEquals($position, strlen($content_array[0])); + $position = $this->ossClient->appendObject($this->bucket, $object, $content_array[1], $position, $options); + $this->assertEquals($position, strlen($content_array[0]) + strlen($content_array[1])); + $position = $this->ossClient->appendObject($this->bucket, $object, $content_array[2], $position, $options); + $this->assertEquals($position, strlen($content_array[0]) + strlen($content_array[1]) + strlen($content_array[1])); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 检查内容的是否相同 + */ + try { + $content = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($content, implode($content_array)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 删除测试object + */ + try { + $this->ossClient->deleteObject($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 追加上传本地文件 + */ + try { + $position = $this->ossClient->appendFile($this->bucket, $object, __FILE__, 0, $options); + $this->assertEquals($position, filesize(__FILE__)); + $position = $this->ossClient->appendFile($this->bucket, $object, __FILE__, $position, $options); + $this->assertEquals($position, filesize(__FILE__) * 2); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 检查复制的是否相同 + */ + try { + $content = $this->ossClient->getObject($this->bucket, $object); + $this->assertEquals($content, file_get_contents(__FILE__) . file_get_contents(__FILE__)); + } catch (OssException $e) { + $this->assertFalse(true); + } + + /** + * 删除测试object + */ + try { + $this->ossClient->deleteObject($this->bucket, $object); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function setUp() + { + parent::setUp(); + $this->ossClient->putObject($this->bucket, 'oss-php-sdk-test/upload-test-object-name.txt', file_get_contents(__FILE__)); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientRestoreObjectTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientRestoreObjectTest.php new file mode 100644 index 0000000..cc1412f --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientRestoreObjectTest.php @@ -0,0 +1,96 @@ +ossClient->putObject($this->iaBucket, $object,'testcontent'); + try{ + $this->ossClient->restoreObject($this->iaBucket, $object); + $this->assertTrue(false); + }catch (OssException $e){ + $this->assertEquals('400', $e->getHTTPStatus()); + $this->assertEquals('OperationNotSupported', $e->getErrorCode()); + } + } + + public function testNullObjectRestoreObject() + { + $object = 'null-object'; + + try{ + $this->ossClient->restoreObject($this->bucket, $object); + $this->assertTrue(false); + }catch (OssException $e){ + $this->assertEquals('404', $e->getHTTPStatus()); + } + } + + public function testArchiveRestoreObject() + { + $object = 'storage-object'; + + $this->ossClient->putObject($this->archiveBucket, $object,'testcontent'); + try{ + $this->ossClient->getObject($this->archiveBucket, $object); + $this->assertTrue(false); + }catch (OssException $e){ + $this->assertEquals('403', $e->getHTTPStatus()); + $this->assertEquals('InvalidObjectState', $e->getErrorCode()); + } + $result = $this->ossClient->restoreObject($this->archiveBucket, $object); + common::waitMetaSync(); + $this->assertEquals('202', $result['info']['http_code']); + + try{ + $this->ossClient->restoreObject($this->archiveBucket, $object); + }catch(OssException $e){ + $this->assertEquals('409', $e->getHTTPStatus()); + $this->assertEquals('RestoreAlreadyInProgress', $e->getErrorCode()); + } + } + + public function setUp() + { + parent::setUp(); + + $this->iaBucket = 'ia-' . $this->bucket; + $this->archiveBucket = 'archive-' . $this->bucket; + $options = array( + OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_IA + ); + + $this->ossClient->createBucket($this->iaBucket, OssClient::OSS_ACL_TYPE_PRIVATE, $options); + + $options = array( + OssClient::OSS_STORAGE => OssClient::OSS_STORAGE_ARCHIVE + ); + + $this->ossClient->createBucket($this->archiveBucket, OssClient::OSS_ACL_TYPE_PRIVATE, $options); + } + + public function tearDown() + { + parent::tearDown(); + + $object = 'storage-object'; + + $this->ossClient->deleteObject($this->iaBucket, $object); + $this->ossClient->deleteObject($this->archiveBucket, $object); + $this->ossClient->deleteBucket($this->iaBucket); + $this->ossClient->deleteBucket($this->archiveBucket); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientSignatureTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientSignatureTest.php new file mode 100644 index 0000000..109121d --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientSignatureTest.php @@ -0,0 +1,111 @@ +ossClient->putObject($this->bucket, $object, file_get_contents(__FILE__)); + $timeout = 3600; + try { + $signedUrl = $this->ossClient->signUrl($this->bucket, $object, $timeout); + } catch (OssException $e) { + $this->assertFalse(true); + } + + $request = new RequestCore($signedUrl); + $request->set_method('GET'); + $request->add_header('Content-Type', ''); + $request->send_request(); + $res = new ResponseCore($request->get_response_header(), $request->get_response_body(), $request->get_response_code()); + $this->assertEquals(file_get_contents(__FILE__), $res->body); + } + + public function testGetSignedUrlForPuttingObject() + { + $object = "a.file"; + $timeout = 3600; + try { + $signedUrl = $this->ossClient->signUrl($this->bucket, $object, $timeout, "PUT"); + $content = file_get_contents(__FILE__); + $request = new RequestCore($signedUrl); + $request->set_method('PUT'); + $request->add_header('Content-Type', ''); + $request->add_header('Content-Length', strlen($content)); + $request->set_body($content); + $request->send_request(); + $res = new ResponseCore($request->get_response_header(), + $request->get_response_body(), $request->get_response_code()); + $this->assertTrue($res->isOK()); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testGetSignedUrlForPuttingObjectFromFile() + { + $file = __FILE__; + $object = "a.file"; + $timeout = 3600; + $options = array('Content-Type' => 'txt'); + try { + $signedUrl = $this->ossClient->signUrl($this->bucket, $object, $timeout, "PUT", $options); + $request = new RequestCore($signedUrl); + $request->set_method('PUT'); + $request->add_header('Content-Type', 'txt'); + $request->set_read_file($file); + $request->set_read_stream_size(filesize($file)); + $request->send_request(); + $res = new ResponseCore($request->get_response_header(), + $request->get_response_body(), $request->get_response_code()); + $this->assertTrue($res->isOK()); + } catch (OssException $e) { + $this->assertFalse(true); + } + + } + + public function tearDown() + { + $this->ossClient->deleteObject($this->bucket, "a.file"); + parent::tearDown(); + } + + public function setUp() + { + parent::setUp(); + /** + * 上传本地变量到bucket + */ + $object = "a.file"; + $content = file_get_contents(__FILE__); + $options = array( + OssClient::OSS_LENGTH => strlen($content), + OssClient::OSS_HEADERS => array( + 'Expires' => 'Fri, 28 Feb 2020 05:38:42 GMT', + 'Cache-Control' => 'no-cache', + 'Content-Disposition' => 'attachment;filename=oss_download.log', + 'Content-Encoding' => 'utf-8', + 'Content-Language' => 'zh-CN', + 'x-oss-server-side-encryption' => 'AES256', + 'x-oss-meta-self-define-title' => 'user define meta info', + ), + ); + + try { + $this->ossClient->putObject($this->bucket, $object, $content, $options); + } catch (OssException $e) { + $this->assertFalse(true); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientTest.php new file mode 100644 index 0000000..f92b346 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssClientTest.php @@ -0,0 +1,216 @@ +assertFalse($ossClient->isUseSSL()); + $ossClient->setUseSSL(true); + $this->assertTrue($ossClient->isUseSSL()); + $this->assertTrue(true); + $this->assertEquals(3, $ossClient->getMaxRetries()); + $ossClient->setMaxTries(4); + $this->assertEquals(4, $ossClient->getMaxRetries()); + $ossClient->setTimeout(10); + $ossClient->setConnectTimeout(20); + } catch (OssException $e) { + assertFalse(true); + } + } + + public function testConstrunct2() + { + try { + $ossClient = new OssClient('id', "", 'http://oss-cn-hangzhou.aliyuncs.com'); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals("access key secret is empty", $e->getMessage()); + } + } + + public function testConstrunct3() + { + try { + $ossClient = new OssClient("", 'key', 'http://oss-cn-hangzhou.aliyuncs.com'); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals("access key id is empty", $e->getMessage()); + } + } + + public function testConstrunct4() + { + try { + $ossClient = new OssClient('id', 'key', ""); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('endpoint is empty', $e->getMessage()); + } + } + + public function testConstrunct5() + { + try { + $ossClient = new OssClient('id', 'key', "123.123.123.1"); + } catch (OssException $e) { + $this->assertTrue(false); + } + } + + public function testConstrunct6() + { + try { + $ossClient = new OssClient('id', 'key', "https://123.123.123.1"); + $this->assertTrue($ossClient->isUseSSL()); + } catch (OssException $e) { + $this->assertTrue(false); + } + } + + public function testConstrunct7() + { + try { + $ossClient = new OssClient('id', 'key', "http://123.123.123.1"); + $this->assertFalse($ossClient->isUseSSL()); + } catch (OssException $e) { + $this->assertTrue(false); + } + } + + public function testConstrunct8() + { + try { + $ossClient = new OssClient('id', 'key', "http://123.123.123.1", true); + $ossClient->listBuckets(); + $this->assertFalse(true); + } catch (OssException $e) { + + } + } + + public function testConstrunct9() + { + try { + $accessKeyId = ' ' . getenv('OSS_ACCESS_KEY_ID') . ' '; + $accessKeySecret = ' ' . getenv('OSS_ACCESS_KEY_SECRET') . ' '; + $endpoint = ' ' . getenv('OSS_ENDPOINT') . '/ '; + $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint, false); + $ossClient->listBuckets(); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testSupportPutEmptyObject() + { + try { + $accessKeyId = ' ' . getenv('OSS_ACCESS_KEY_ID') . ' '; + $accessKeySecret = ' ' . getenv('OSS_ACCESS_KEY_SECRET') . ' '; + $endpoint = ' ' . getenv('OSS_ENDPOINT') . '/ '; + $bucket = getenv('OSS_BUCKET'); + $ossClient = new OssClient($accessKeyId, $accessKeySecret , $endpoint, false); + $ossClient->putObject($bucket,'test_emptybody',''); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testCreateObjectDir() + { + try { + $accessKeyId = ' ' . getenv('OSS_ACCESS_KEY_ID') . ' '; + $accessKeySecret = ' ' . getenv('OSS_ACCESS_KEY_SECRET') . ' '; + $endpoint = ' ' . getenv('OSS_ENDPOINT') . '/ '; + $bucket = getenv('OSS_BUCKET'); + $object='test-dir'; + $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint, false); + $ossClient->createObjectDir($bucket,$object); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testGetBucketCors() + { + try { + $accessKeyId = ' ' . getenv('OSS_ACCESS_KEY_ID') . ' '; + $accessKeySecret = ' ' . getenv('OSS_ACCESS_KEY_SECRET') . ' '; + $endpoint = ' ' . getenv('OSS_ENDPOINT') . '/ '; + $bucket = getenv('OSS_BUCKET'); + $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint, false); + $ossClient->getBucketCors($bucket); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testGetBucketCname() + { + try { + $accessKeyId = ' ' . getenv('OSS_ACCESS_KEY_ID') . ' '; + $accessKeySecret = ' ' . getenv('OSS_ACCESS_KEY_SECRET') . ' '; + $endpoint = ' ' . getenv('OSS_ENDPOINT') . '/ '; + $bucket = getenv('OSS_BUCKET'); + $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint, false); + $ossClient->getBucketCname($bucket); + } catch (OssException $e) { + $this->assertFalse(true); + } + } + + public function testProxySupport() + { + $accessKeyId = ' ' . getenv('OSS_ACCESS_KEY_ID') . ' '; + $accessKeySecret = ' ' . getenv('OSS_ACCESS_KEY_SECRET') . ' '; + $endpoint = ' ' . getenv('OSS_ENDPOINT') . '/ '; + $bucket = getenv('OSS_BUCKET') . '-proxy'; + $requestProxy = getenv('OSS_PROXY'); + $key = 'test-proxy-srv-object'; + $content = 'test-content'; + $proxys = parse_url($requestProxy); + + $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint, false, null, $requestProxy); + + $result = $ossClient->createBucket($bucket); + $this->checkProxy($result, $proxys); + + $result = $ossClient->putObject($bucket, $key, $content); + $this->checkProxy($result, $proxys); + $result = $ossClient->getObject($bucket, $key); + $this->assertEquals($content, $result); + + // list object + $objectListInfo = $ossClient->listObjects($bucket); + $objectList = $objectListInfo->getObjectList(); + $this->assertNotNull($objectList); + $this->assertTrue(is_array($objectList)); + $objects = array(); + foreach ($objectList as $value) { + $objects[] = $value->getKey(); + } + $this->assertEquals(1, count($objects)); + $this->assertTrue(in_array($key, $objects)); + + $result = $ossClient->deleteObject($bucket, $key); + $this->checkProxy($result,$proxys); + + $result = $ossClient->deleteBucket($bucket); + $this->checkProxy($result, $proxys); + } + + private function checkProxy($result, $proxys) + { + $this->assertEquals($result['info']['primary_ip'], $proxys['host']); + $this->assertEquals($result['info']['primary_port'], $proxys['port']); + $this->assertTrue(array_key_exists('via', $result)); + } + +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssExceptionTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssExceptionTest.php new file mode 100644 index 0000000..4a418d5 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssExceptionTest.php @@ -0,0 +1,19 @@ +assertTrue(false); + } catch (OssException $e) { + $this->assertNotNull($e); + $this->assertEquals($e->getMessage(), "ERR"); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssUtilTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssUtilTest.php new file mode 100644 index 0000000..adf6457 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/OssUtilTest.php @@ -0,0 +1,225 @@ +assertEquals(OssUtil::chkChinese("hello,world"), 0); + $str = '你好,这里是卖咖啡!'; + $strGBK = OssUtil::encodePath($str); + $this->assertEquals(OssUtil::chkChinese($str), 1); + $this->assertEquals(OssUtil::chkChinese($strGBK), 1); + } + + public function testIsGB2312() + { + $str = '你好,这里是卖咖啡!'; + $this->assertFalse(OssUtil::isGb2312($str)); + } + + public function testCheckChar() + { + $str = '你好,这里是卖咖啡!'; + $this->assertFalse(OssUtil::checkChar($str)); + $this->assertTrue(OssUtil::checkChar(iconv("UTF-8", "GB2312//IGNORE", $str))); + } + + public function testIsIpFormat() + { + $this->assertTrue(OssUtil::isIPFormat("10.101.160.147")); + $this->assertTrue(OssUtil::isIPFormat("12.12.12.34")); + $this->assertTrue(OssUtil::isIPFormat("12.12.12.12")); + $this->assertTrue(OssUtil::isIPFormat("255.255.255.255")); + $this->assertTrue(OssUtil::isIPFormat("0.1.1.1")); + $this->assertFalse(OssUtil::isIPFormat("0.1.1.x")); + $this->assertFalse(OssUtil::isIPFormat("0.1.1.256")); + $this->assertFalse(OssUtil::isIPFormat("256.1.1.1")); + $this->assertFalse(OssUtil::isIPFormat("0.1.1.0.1")); + $this->assertTrue(OssUtil::isIPFormat("10.10.10.10:123")); + } + + public function testToQueryString() + { + $option = array("a" => "b"); + $this->assertEquals('a=b', OssUtil::toQueryString($option)); + } + + public function testSReplace() + { + $str = "<>&'\""; + $this->assertEquals("&lt;&gt;&'"", OssUtil::sReplace($str)); + } + + public function testCheckChinese() + { + $str = '你好,这里是卖咖啡!'; + $this->assertEquals(OssUtil::chkChinese($str), 1); + if (OssUtil::isWin()) { + $strGB = OssUtil::encodePath($str); + $this->assertEquals($str, iconv("GB2312", "UTF-8", $strGB)); + } + } + + public function testValidateOption() + { + $option = 'string'; + + try { + OssUtil::validateOptions($option); + $this->assertFalse(true); + } catch (OssException $e) { + $this->assertEquals("string:option must be array", $e->getMessage()); + } + + $option = null; + + try { + OssUtil::validateOptions($option); + $this->assertTrue(true); + } catch (OssException $e) { + $this->assertFalse(true); + } + + } + + public function testCreateDeleteObjectsXmlBody() + { + $xml = <<trueobj1 +BBBB; + $a = array('obj1'); + $this->assertEquals($xml, $this->cleanXml(OssUtil::createDeleteObjectsXmlBody($a, 'true'))); + } + + public function testCreateCompleteMultipartUploadXmlBody() + { + $xml = <<2xx +BBBB; + $a = array(array("PartNumber" => 2, "ETag" => "xx")); + $this->assertEquals($this->cleanXml(OssUtil::createCompleteMultipartUploadXmlBody($a)), $xml); + } + + public function testCreateBucketXmlBody() + { + $xml = <<Standard +BBBB; + $storageClass ="Standard"; + $this->assertEquals($this->cleanXml(OssUtil::createBucketXmlBody($storageClass)), $xml); + } + + public function testValidateBucket() + { + $this->assertTrue(OssUtil::validateBucket("xxx")); + $this->assertFalse(OssUtil::validateBucket("XXXqwe123")); + $this->assertFalse(OssUtil::validateBucket("XX")); + $this->assertFalse(OssUtil::validateBucket("/X")); + $this->assertFalse(OssUtil::validateBucket("")); + } + + public function testValidateObject() + { + $this->assertTrue(OssUtil::validateObject("xxx")); + $this->assertTrue(OssUtil::validateObject("xxx23")); + $this->assertTrue(OssUtil::validateObject("12321-xxx")); + $this->assertTrue(OssUtil::validateObject("x")); + $this->assertFalse(OssUtil::validateObject("/aa")); + $this->assertFalse(OssUtil::validateObject("\\aa")); + $this->assertFalse(OssUtil::validateObject("")); + } + + public function testStartWith() + { + $this->assertTrue(OssUtil::startsWith("xxab", "xx"), true); + } + + public function testReadDir() + { + $list = OssUtil::readDir("./src", ".|..|.svn|.git", true); + $this->assertNotNull($list); + } + + public function testIsWin() + { + //$this->assertTrue(OssUtil::isWin()); + } + + public function testGetMd5SumForFile() + { + $this->assertEquals(OssUtil::getMd5SumForFile(__FILE__, 0, filesize(__FILE__) - 1), base64_encode(md5(file_get_contents(__FILE__), true))); + } + + public function testGenerateFile() + { + $path = __DIR__ . DIRECTORY_SEPARATOR . "generatedFile.txt"; + OssUtil::generateFile($path, 1024 * 1024); + $this->assertEquals(filesize($path), 1024 * 1024); + unlink($path); + } + + public function testThrowOssExceptionWithMessageIfEmpty() + { + $null = null; + try { + OssUtil::throwOssExceptionWithMessageIfEmpty($null, "xx"); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals('xx', $e->getMessage()); + } + } + + public function testThrowOssExceptionWithMessageIfEmpty2() + { + $null = ""; + try { + OssUtil::throwOssExceptionWithMessageIfEmpty($null, "xx"); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals('xx', $e->getMessage()); + } + } + + public function testValidContent() + { + $null = ""; + try { + OssUtil::validateContent($null); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals('http body content is invalid', $e->getMessage()); + } + + $notnull = "x"; + try { + OssUtil::validateContent($notnull); + $this->assertTrue(true); + } catch (OssException $e) { + $this->assertEquals('http body content is invalid', $e->getMessage()); + } + } + + public function testThrowOssExceptionWithMessageIfEmpty3() + { + $null = "xx"; + try { + OssUtil::throwOssExceptionWithMessageIfEmpty($null, "xx"); + $this->assertTrue(True); + } catch (OssException $e) { + $this->assertTrue(false); + } + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } + +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/PutSetDeleteResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/PutSetDeleteResultTest.php new file mode 100644 index 0000000..b298e44 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/PutSetDeleteResultTest.php @@ -0,0 +1,66 @@ +assertFalse(true); + } catch (OssException $e) { + $this->assertEquals('raw response is null', $e->getMessage()); + } + } + + public function testOkResponse() + { + $header= array( + 'x-oss-request-id' => '582AA51E004C4550BD27E0E4', + 'etag' => '595FA1EA77945233921DF12427F9C7CE', + 'content-md5' => 'WV+h6neUUjOSHfEkJ/nHzg==', + 'info' => array( + 'http_code' => '200', + 'method' => 'PUT' + ), + ); + $response = new ResponseCore($header, "this is a mock body, just for test", 200); + $result = new PutSetDeleteResult($response); + $data = $result->getData(); + $this->assertTrue($result->isOK()); + $this->assertEquals("this is a mock body, just for test", $data['body']); + $this->assertEquals('582AA51E004C4550BD27E0E4', $data['x-oss-request-id']); + $this->assertEquals('595FA1EA77945233921DF12427F9C7CE', $data['etag']); + $this->assertEquals('WV+h6neUUjOSHfEkJ/nHzg==', $data['content-md5']); + $this->assertEquals('200', $data['info']['http_code']); + $this->assertEquals('PUT', $data['info']['method']); + } + + public function testFailResponse() + { + $response = new ResponseCore(array(), "", 301); + try { + new PutSetDeleteResult($response); + $this->assertFalse(true); + } catch (OssException $e) { + + } + } + + public function setUp() + { + + } + + public function tearDown() + { + + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/RefererConfigTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/RefererConfigTest.php new file mode 100644 index 0000000..8360a24 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/RefererConfigTest.php @@ -0,0 +1,54 @@ + + +true + +http://www.aliyun.com +https://www.aliyun.com +http://www.*.com +https://www.?.aliyuncs.com + + +BBBB; + + private $validXml2 = << + +true + +http://www.aliyun.com + + +BBBB; + + public function testParseValidXml() + { + $refererConfig = new RefererConfig(); + $refererConfig->parseFromXml($this->validXml); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($refererConfig->serializeToXml())); + } + + public function testParseValidXml2() + { + $refererConfig = new RefererConfig(); + $refererConfig->parseFromXml($this->validXml2); + $this->assertEquals(true, $refererConfig->isAllowEmptyReferer()); + $this->assertEquals(1, count($refererConfig->getRefererList())); + $this->assertEquals($this->cleanXml($this->validXml2), $this->cleanXml(strval($refererConfig))); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/StorageCapacityTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/StorageCapacityTest.php new file mode 100644 index 0000000..4562da7 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/StorageCapacityTest.php @@ -0,0 +1,59 @@ + + + 1 + +BBBB; + + private $validXml = << + + 1 + +BBBB; + + public function testParseInValidXml() + { + $response = new ResponseCore(array(), $this->inValidXml, 300); + try { + new GetStorageCapacityResult($response); + $this->assertTrue(false); + } catch (OssException $e) {} + } + + public function testParseEmptyXml() + { + $response = new ResponseCore(array(), "", 300); + try { + new GetStorageCapacityResult($response); + $this->assertTrue(false); + } catch (OssException $e) {} + } + + public function testParseValidXml() + { + $response = new ResponseCore(array(), $this->validXml, 200); + $result = new GetStorageCapacityResult($response); + $this->assertEquals($result->getData(), 1); + } + + public function testSerializeToXml() + { + $xml = "\n1\n"; + + $storageCapacityConfig = new StorageCapacityConfig(1); + $content = $storageCapacityConfig->serializeToXml(); + $this->assertEquals($content, $xml); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/SymlinkTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/SymlinkTest.php new file mode 100644 index 0000000..d257c94 --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/SymlinkTest.php @@ -0,0 +1,74 @@ +ossClient ->putObject($bucket, $object, 'test_content'); + $this->ossClient->putSymlink($bucket, $symlink, $object); + $result = $this->ossClient->getObject($bucket, $symlink); + $this->assertEquals('test_content', $result); + + $this->ossClient ->putObject($bucket, $special_object, 'test_content'); + $this->ossClient->putSymlink($bucket, $symlink, $special_object); + $result = $this->ossClient->getObject($bucket, $symlink); + $this->assertEquals('test_content', $result); + } + + public function testGetSymlink() + { + $bucket = getenv('OSS_BUCKET'); + $symlink = 'test-link'; + $object = 'exist_object^$#!~'; + + $result = $this->ossClient->getSymlink($bucket, $symlink); + $this->assertEquals($result[OssClient::OSS_SYMLINK_TARGET], $object); + $this->assertEquals('200', $result[OssClient::OSS_INFO][OssClient::OSS_HTTP_CODE]); + $this->assertTrue(isset($result[OssClient::OSS_ETAG])); + $this->assertTrue(isset($result[OssClient::OSS_REQUEST_ID])); + } + + public function testPutNullSymlink() + { + $bucket = getenv('OSS_BUCKET'); + $symlink = 'null-link'; + $object_not_exist = 'not_exist_object+$#!b不'; + $this->ossClient->putSymlink($bucket, $symlink, $object_not_exist); + + try{ + $this->ossClient->getObject($bucket, $symlink); + $this->assertTrue(false); + }catch (OssException $e){ + $this->assertEquals('The symlink target object does not exist', $e->getErrorMessage()); + } + } + + public function testGetNullSymlink() + { + $bucket = getenv('OSS_BUCKET'); + $symlink = 'null-link-new'; + + try{ + $result = $this->ossClient->getSymlink($bucket, $symlink); + $this->assertTrue(false); + }catch (OssException $e){ + $this->assertEquals('The specified key does not exist.', $e->getErrorMessage()); + } + } +} + + diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/TestOssClientBase.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/TestOssClientBase.php new file mode 100644 index 0000000..4abd31f --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/TestOssClientBase.php @@ -0,0 +1,51 @@ +bucket = Common::getBucketName() . rand(100000, 999999); + $this->ossClient = Common::getOssClient(); + $this->ossClient->createBucket($this->bucket); + Common::waitMetaSync(); + } + + public function tearDown() + { + if (!$this->ossClient->doesBucketExist($this->bucket)) { + return; + } + + $objects = $this->ossClient->listObjects( + $this->bucket, array('max-keys' => 1000, 'delimiter' => ''))->getObjectList(); + $keys = array(); + foreach ($objects as $obj) { + $keys[] = $obj->getKey(); + } + if (count($keys) > 0) { + $this->ossClient->deleteObjects($this->bucket, $keys); + } + $uploads = $this->ossClient->listMultipartUploads($this->bucket)->getUploads(); + foreach ($uploads as $up) { + $this->ossClient->abortMultipartUpload($this->bucket, $up->getKey(), $up->getUploadId()); + } + + $this->ossClient->deleteBucket($this->bucket); + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/UploadPartResultTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/UploadPartResultTest.php new file mode 100644 index 0000000..e4789ef --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/UploadPartResultTest.php @@ -0,0 +1,33 @@ + '7265F4D211B56873A381D321F586E4A9'); + private $invalidHeader = array(); + + public function testParseValidHeader() + { + $response = new ResponseCore($this->validHeader, "", 200); + $result = new UploadPartResult($response); + $eTag = $result->getData(); + $this->assertEquals('7265F4D211B56873A381D321F586E4A9', $eTag); + } + + public function testParseInvalidHeader() + { + $response = new ResponseCore($this->invalidHeader, "", 200); + try { + new UploadPartResult($response); + $this->assertTrue(false); + } catch (OssException $e) { + $this->assertEquals('cannot get ETag', $e->getMessage()); + } + } +} diff --git a/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/WebsiteConfigTest.php b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/WebsiteConfigTest.php new file mode 100644 index 0000000..2ec0fcb --- /dev/null +++ b/source/vendor/aliyuncs/oss-sdk-php/tests/OSS/Tests/WebsiteConfigTest.php @@ -0,0 +1,56 @@ + + + +index.html + + +errorDocument.html + + +BBBB; + + private $nullXml = << +BBBB; + private $nullXml2 = << +BBBB; + + public function testParseValidXml() + { + $websiteConfig = new WebsiteConfig("index"); + $websiteConfig->parseFromXml($this->validXml); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($websiteConfig->serializeToXml())); + } + + public function testParsenullXml() + { + $websiteConfig = new WebsiteConfig(); + $websiteConfig->parseFromXml($this->nullXml); + $this->assertTrue($this->cleanXml($this->nullXml) === $this->cleanXml($websiteConfig->serializeToXml()) || + $this->cleanXml($this->nullXml2) === $this->cleanXml($websiteConfig->serializeToXml())); + } + + public function testWebsiteConstruct() + { + $websiteConfig = new WebsiteConfig("index.html", "errorDocument.html"); + $this->assertEquals('index.html', $websiteConfig->getIndexDocument()); + $this->assertEquals('errorDocument.html', $websiteConfig->getErrorDocument()); + $this->assertEquals($this->cleanXml($this->validXml), $this->cleanXml($websiteConfig->serializeToXml())); + } + + private function cleanXml($xml) + { + return str_replace("\n", "", str_replace("\r", "", $xml)); + } +} diff --git a/source/vendor/autoload.php b/source/vendor/autoload.php new file mode 100644 index 0000000..845e1f4 --- /dev/null +++ b/source/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/source/vendor/composer/LICENSE b/source/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/source/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/source/vendor/composer/autoload_classmap.php b/source/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/source/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/qiniu/php-sdk/src/Qiniu/functions.php', +); diff --git a/source/vendor/composer/autoload_namespaces.php b/source/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..3fde43e --- /dev/null +++ b/source/vendor/composer/autoload_namespaces.php @@ -0,0 +1,12 @@ + array($vendorDir . '/qcloud/cos-sdk-v5/src'), + 'Guzzle\\Tests' => array($vendorDir . '/guzzle/guzzle/tests'), + 'Guzzle' => array($vendorDir . '/guzzle/guzzle/src'), +); diff --git a/source/vendor/composer/autoload_psr4.php b/source/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..9d3e5b2 --- /dev/null +++ b/source/vendor/composer/autoload_psr4.php @@ -0,0 +1,18 @@ + array($vendorDir . '/topthink/think-installer/src'), + 'think\\' => array($baseDir . '/thinkphp/library/think'), + 'app\\' => array($baseDir . '/application'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Qiniu\\' => array($vendorDir . '/qiniu/php-sdk/src/Qiniu'), + 'OSS\\' => array($vendorDir . '/aliyuncs/oss-sdk-php/src/OSS'), + 'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'), + 'Lvht\\' => array($vendorDir . '/lvht/geohash/src'), + 'Grafika\\' => array($vendorDir . '/kosinix/grafika/src/Grafika'), +); diff --git a/source/vendor/composer/autoload_real.php b/source/vendor/composer/autoload_real.php new file mode 100644 index 0000000..155a29d --- /dev/null +++ b/source/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit34a41e2841af1a67f3ddef099fc7b348::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit34a41e2841af1a67f3ddef099fc7b348::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire34a41e2841af1a67f3ddef099fc7b348($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire34a41e2841af1a67f3ddef099fc7b348($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/source/vendor/composer/autoload_static.php b/source/vendor/composer/autoload_static.php new file mode 100644 index 0000000..5483b0d --- /dev/null +++ b/source/vendor/composer/autoload_static.php @@ -0,0 +1,118 @@ + __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu/functions.php', + ); + + public static $prefixLengthsPsr4 = array ( + 't' => + array ( + 'think\\composer\\' => 15, + 'think\\' => 6, + ), + 'a' => + array ( + 'app\\' => 4, + ), + 'S' => + array ( + 'Symfony\\Component\\EventDispatcher\\' => 34, + ), + 'Q' => + array ( + 'Qiniu\\' => 6, + ), + 'O' => + array ( + 'OSS\\' => 4, + ), + 'M' => + array ( + 'MyCLabs\\Enum\\' => 13, + ), + 'L' => + array ( + 'Lvht\\' => 5, + ), + 'G' => + array ( + 'Grafika\\' => 8, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'think\\composer\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-installer/src', + ), + 'think\\' => + array ( + 0 => __DIR__ . '/../..' . '/thinkphp/library/think', + ), + 'app\\' => + array ( + 0 => __DIR__ . '/../..' . '/application', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Qiniu\\' => + array ( + 0 => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu', + ), + 'OSS\\' => + array ( + 0 => __DIR__ . '/..' . '/aliyuncs/oss-sdk-php/src/OSS', + ), + 'MyCLabs\\Enum\\' => + array ( + 0 => __DIR__ . '/..' . '/myclabs/php-enum/src', + ), + 'Lvht\\' => + array ( + 0 => __DIR__ . '/..' . '/lvht/geohash/src', + ), + 'Grafika\\' => + array ( + 0 => __DIR__ . '/..' . '/kosinix/grafika/src/Grafika', + ), + ); + + public static $prefixesPsr0 = array ( + 'Q' => + array ( + 'Qcloud\\Cos\\' => + array ( + 0 => __DIR__ . '/..' . '/qcloud/cos-sdk-v5/src', + ), + ), + 'G' => + array ( + 'Guzzle\\Tests' => + array ( + 0 => __DIR__ . '/..' . '/guzzle/guzzle/tests', + ), + 'Guzzle' => + array ( + 0 => __DIR__ . '/..' . '/guzzle/guzzle/src', + ), + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit34a41e2841af1a67f3ddef099fc7b348::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit34a41e2841af1a67f3ddef099fc7b348::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit34a41e2841af1a67f3ddef099fc7b348::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} diff --git a/source/vendor/composer/installed.json b/source/vendor/composer/installed.json new file mode 100644 index 0000000..fd68367 --- /dev/null +++ b/source/vendor/composer/installed.json @@ -0,0 +1,534 @@ +[ + { + "name": "aliyuncs/oss-sdk-php", + "version": "v2.3.0", + "version_normalized": "2.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/aliyun/aliyun-oss-php-sdk.git", + "reference": "e69f57916678458642ac9d2fd341ae78a56996c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/e69f57916678458642ac9d2fd341ae78a56996c8", + "reference": "e69f57916678458642ac9d2fd341ae78a56996c8", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "~1.0" + }, + "time": "2018-01-08T06:59:35+00:00", + "type": "library", + "installation-source": "dist", + "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/" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "version_normalized": "3.9.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "time": "2015-03-18T18:23:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle" + }, + { + "name": "kosinix/grafika", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/kosinix/grafika.git", + "reference": "211f61fc334b8b36616b23e8af7c5727971d96ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kosinix/grafika/zipball/211f61fc334b8b36616b23e8af7c5727971d96ee", + "reference": "211f61fc334b8b36616b23e8af7c5727971d96ee", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "time": "2017-06-20T03:13:49+00:00", + "type": "library", + "installation-source": "source", + "autoload": { + "psr-4": { + "Grafika\\": "src/Grafika" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "GPL-2.0+" + ], + "authors": [ + { + "name": "Nico Amarilla", + "homepage": "https://www.kosinix.com" + } + ], + "description": "An image manipulation library for PHP.", + "homepage": "http://kosinix.github.io/grafika", + "keywords": [ + "grafika" + ] + }, + { + "name": "lvht/geohash", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/lvht/geohash.git", + "reference": "bbba3e1b487f0ec2e5e666c1bc9d1d4277990a29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lvht/geohash/zipball/bbba3e1b487f0ec2e5e666c1bc9d1d4277990a29", + "reference": "bbba3e1b487f0ec2e5e666c1bc9d1d4277990a29", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "time": "2017-08-24T11:05:30+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Lvht\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "吕海涛", + "email": "git@lvht.net", + "homepage": "https://github.com/lvht" + } + ], + "description": "geohash like python-geohash", + "homepage": "http://github.com/lvht/geohash", + "keywords": [ + "geohash" + ] + }, + { + "name": "myclabs/php-enum", + "version": "1.6.4", + "version_normalized": "1.6.4.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "550d2334d77f91b0816a5cbd6965272fe20146b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/550d2334d77f91b0816a5cbd6965272fe20146b8", + "reference": "550d2334d77f91b0816a5cbd6965272fe20146b8", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "squizlabs/php_codesniffer": "1.*" + }, + "time": "2018-10-30T14:36:18+00:00", + "type": "library", + "installation-source": "dist", + "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" + ] + }, + { + "name": "qcloud/cos-sdk-v5", + "version": "v1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/tencentyun/cos-php-sdk-v5.git", + "reference": "989c087a5aaf9b5df020b1d2633644e7c9f694e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tencentyun/cos-php-sdk-v5/zipball/989c087a5aaf9b5df020b1d2633644e7c9f694e0", + "reference": "989c087a5aaf9b5df020b1d2633644e7c9f694e0", + "shasum": "" + }, + "require": { + "guzzle/guzzle": "~3.7", + "php": ">=5.3.0" + }, + "time": "2018-11-27T13:31:54+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Qcloud\\Cos\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yaozongyou", + "email": "yaozongyou@vip.qq.com" + }, + { + "name": "lewzylu", + "email": "327874225@qq.com" + } + ], + "description": "PHP SDK for QCloud COS", + "keywords": [ + "cos", + "php", + "qcloud" + ] + }, + { + "name": "qiniu/php-sdk", + "version": "v7.2.7", + "version_normalized": "7.2.7.0", + "source": { + "type": "git", + "url": "https://github.com/qiniu/php-sdk.git", + "reference": "88d11a5857ebc6871204e9be6ceec54bf5f381e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/qiniu/php-sdk/zipball/88d11a5857ebc6871204e9be6ceec54bf5f381e6", + "reference": "88d11a5857ebc6871204e9be6ceec54bf5f381e6", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.3" + }, + "time": "2018-11-06T13:34:32+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Qiniu\\": "src/Qiniu" + }, + "files": [ + "src/Qiniu/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Qiniu", + "email": "sdk@qiniu.com", + "homepage": "http://www.qiniu.com" + } + ], + "description": "Qiniu Resource (Cloud) Storage SDK for PHP", + "homepage": "http://developer.qiniu.com/", + "keywords": [ + "cloud", + "qiniu", + "sdk", + "storage" + ] + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2018-11-21T14:20:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "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": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com" + }, + { + "name": "topthink/framework", + "version": "v5.0.24", + "version_normalized": "5.0.24.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be", + "reference": "c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phpdocumentor/reflection-docblock": "^2.0", + "phploc/phploc": "2.*", + "phpunit/phpunit": "4.8.*", + "sebastian/phpcpd": "2.*" + }, + "time": "2019-01-11T08:04:58+00:00", + "type": "think-framework", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "library/think" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the new thinkphp framework", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ] + }, + { + "name": "topthink/think-installer", + "version": "v1.0.12", + "version_normalized": "1.0.12.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-installer.git", + "reference": "1be326e68f63de4e95977ed50f46ae75f017556d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-installer/zipball/1be326e68f63de4e95977ed50f46ae75f017556d", + "reference": "1be326e68f63de4e95977ed50f46ae75f017556d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "require-dev": { + "composer/composer": "1.0.*@dev" + }, + "time": "2017-05-27T06:58:09+00:00", + "type": "composer-plugin", + "extra": { + "class": "think\\composer\\Plugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\composer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ] + } +] diff --git a/source/vendor/guzzle/guzzle/.gitignore b/source/vendor/guzzle/guzzle/.gitignore new file mode 100644 index 0000000..893035d --- /dev/null +++ b/source/vendor/guzzle/guzzle/.gitignore @@ -0,0 +1,27 @@ +# Ingore common cruft +.DS_STORE +coverage +.idea + +# Ignore binary files +guzzle.phar +guzzle-min.phar + +# Ignore potentially sensitive phpunit file +phpunit.xml + +# Ignore composer generated files +composer.phar +composer.lock +composer-test.lock +vendor/ + +# Ignore build files +build/ +phing/build.properties + +# Ignore subsplit working directory +.subsplit + +docs/_build +docs/*.pyc diff --git a/source/vendor/guzzle/guzzle/.travis.yml b/source/vendor/guzzle/guzzle/.travis.yml new file mode 100644 index 0000000..209e05c --- /dev/null +++ b/source/vendor/guzzle/guzzle/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm + +before_script: + - curl --version + - pecl install uri_template-beta || echo "pecl uri_template not available" + - composer self-update + - composer install --no-interaction --prefer-source --dev + - ~/.nvm/nvm.sh install v0.6.14 + +script: composer test diff --git a/source/vendor/guzzle/guzzle/CHANGELOG.md b/source/vendor/guzzle/guzzle/CHANGELOG.md new file mode 100644 index 0000000..f0dc544 --- /dev/null +++ b/source/vendor/guzzle/guzzle/CHANGELOG.md @@ -0,0 +1,751 @@ +# CHANGELOG + +## 3.9.3 - 2015-03-18 + +* Ensuring Content-Length is not stripped from a request when it is `0`. +* Added more information to stream wrapper exceptions. +* Message parser will no longer throw warnings for malformed messages. +* Giving a valid cache TTL when max-age is 0. + +## 3.9.2 - 2014-09-10 + +* Retrying "Connection died, retrying a fresh connect" curl errors. +* Automatically extracting the cacert from the phar in client constructor. +* Added EntityBody support for OPTIONS requests. + +## 3.9.1 - 2014-05-07 + +* Added a fix to ReadLimitEntityBody to ensure it doesn't infinitely loop. +* Added a fix to the stream checksum function so that when the first read + returns a falsey value, it still continues to consume the stream until EOF. + +## 3.9.0 - 2014-04-23 + +* `null`, `false`, and `"_guzzle_blank_"` all now serialize as an empty value + with no trailing "=". See dc1d824277. +* No longer performing an MD5 check on the cacert each time the phar is used, + but rather copying the cacert to the temp directory. +* `"0"` can now be added as a URL path +* Deleting cookies that are set to empty +* If-Modified-Since is no longer unnecessarily added to the CachePlugin +* Cookie path matching now follows RFC 6265 s5.1.4 +* Updated service descriptions are now added to a service client's composite + factory. +* MockPlugin now throws an exception if the queue is empty. +* Properly parsing URLs that start with "http" but are not absolute +* Added the ability to configure the curl_multi_select timeout setting +* OAuth parameters are now sorted using lexicographical byte value ordering +* Fixing invalid usage of an out of range PHP feature in the ErrorResponsePlugin + +## 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 visting 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 accross 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'sdeprecated). 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 http://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/source/vendor/guzzle/guzzle/LICENSE b/source/vendor/guzzle/guzzle/LICENSE new file mode 100644 index 0000000..d51aa69 --- /dev/null +++ b/source/vendor/guzzle/guzzle/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 Michael Dowling, https://github.com/mtdowling + +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/source/vendor/guzzle/guzzle/README.md b/source/vendor/guzzle/guzzle/README.md new file mode 100644 index 0000000..6be06bf --- /dev/null +++ b/source/vendor/guzzle/guzzle/README.md @@ -0,0 +1,57 @@ +Guzzle, PHP HTTP client and webservice framework +================================================ + +# This is an old version of Guzzle + +This repository is for Guzzle 3.x. Guzzle 5.x, the new version of Guzzle, has +been released and is available at +[https://github.com/guzzle/guzzle](https://github.com/guzzle/guzzle). The +documentation for Guzzle version 5+ can be found at +[http://guzzlephp.org](http://guzzlephp.org). + +Guzzle 3 is only maintained for bug and security fixes. Guzzle 3 will be EOL +at some point in late 2015. + +### About Guzzle 3 + +[![Composer Downloads](https://poser.pugx.org/guzzle/guzzle/d/total.png)](https://packagist.org/packages/guzzle/guzzle) + [![Build Status](https://secure.travis-ci.org/guzzle/guzzle3.png?branch=master)](http://travis-ci.org/guzzle/guzzle3) + +- Extremely powerful API provides all the power of cURL with a simple interface. +- Truly take advantage of HTTP/1.1 with persistent connections, connection pooling, and parallel requests. +- Service description DSL allows you build awesome web service clients faster. +- Symfony2 event-based plugin system allows you to completely modify the behavior of a request. + +Get answers with: [Documentation](http://guzzle3.readthedocs.org/en/latest/), [Forums](https://groups.google.com/forum/?hl=en#!forum/guzzle), IRC ([#guzzlephp](irc://irc.freenode.net/#guzzlephp) @ irc.freenode.net) + +### Installing via Composer + +The recommended way to install Guzzle is through [Composer](http://getcomposer.org). + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php + +# Add Guzzle as a dependency +php composer.phar require guzzle/guzzle:~3.9 +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` +## Known Issues + +1. Problem following a specific redirect: https://github.com/guzzle/guzzle/issues/385. + This has been fixed in Guzzle 4/5. +2. Root XML attributes not serialized in a service description: https://github.com/guzzle/guzzle3/issues/5. + This has been fixed in Guzzle 4/5. +3. Accept-Encoding not preserved when following redirect: https://github.com/guzzle/guzzle3/issues/9 + Fixed in Guzzle 4/5. +4. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10 + Fixed in Guzzle 4/5. +5. Recursive model references with array items: https://github.com/guzzle/guzzle3/issues/13 + Fixed in Guzzle 4/5 +6. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10 + Fixed in Guzzle 4/5. diff --git a/source/vendor/guzzle/guzzle/UPGRADING.md b/source/vendor/guzzle/guzzle/UPGRADING.md new file mode 100644 index 0000000..f58bf11 --- /dev/null +++ b/source/vendor/guzzle/guzzle/UPGRADING.md @@ -0,0 +1,537 @@ +Guzzle Upgrade Guide +==================== + +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 http://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::getSteamType()` 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/source/vendor/guzzle/guzzle/build.xml b/source/vendor/guzzle/guzzle/build.xml new file mode 100644 index 0000000..2aa62ba --- /dev/null +++ b/source/vendor/guzzle/guzzle/build.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/vendor/guzzle/guzzle/composer.json b/source/vendor/guzzle/guzzle/composer.json new file mode 100644 index 0000000..59424b3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/composer.json @@ -0,0 +1,82 @@ +{ + "name": "guzzle/guzzle", + "type": "library", + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + + "require": { + "php": ">=5.3.3", + "ext-curl": "*", + "symfony/event-dispatcher": "~2.1" + }, + + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + + "scripts": { + "test": "phpunit" + }, + + "require-dev": { + "doctrine/cache": "~1.3", + "symfony/class-loader": "~2.1", + "monolog/monolog": "~1.0", + "psr/log": "~1.0", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3", + "phpunit/phpunit": "3.7.*" + }, + + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/docs/Makefile b/source/vendor/guzzle/guzzle/docs/Makefile new file mode 100644 index 0000000..d92e03f --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Guzzle.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Guzzle.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Guzzle" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Guzzle" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/source/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json b/source/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json new file mode 100644 index 0000000..8168302 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/_downloads/guzzle-schema-1.0.json @@ -0,0 +1,176 @@ +{ + "additionalProperties": true, + "name": { + "type": "string", + "description": "Name of the web service" + }, + "apiVersion": { + "type": ["string", "number"], + "description": "Version identifier that the service description is compatible with" + }, + "baseUrl": { + "type": "string", + "description": "Base URL of the web service. Any relative URI specified in an operation will be merged with the baseUrl using the process defined in RFC 2396" + }, + "basePath": { + "type": "string", + "description": "Alias of baseUrl" + }, + "_description": { + "type": "string", + "description": "Short summary of the web service. This is actually called 'description' but this JSON schema wont validate using just description." + }, + "operations": { + "description": "Operations of the web service", + "type": "object", + "properties": { + "extends": { + "type": "string", + "description": "Extend from another operation by name. The parent operation must be defined before the child." + }, + "httpMethod": { + "type": "string", + "description": "HTTP method used with the operation (e.g. GET, POST, PUT, DELETE, PATCH, etc)" + }, + "uri": { + "type": "string", + "description": "URI of the operation. The uri attribute can contain URI templates. The variables of the URI template are parameters of the operation with a location value of uri" + }, + "summary": { + "type": "string", + "description": "Short summary of what the operation does" + }, + "class": { + "type": "string", + "description": "Custom class to instantiate instead of the default Guzzle\\Service\\Command\\OperationCommand" + }, + "responseClass": { + "type": "string", + "description": "This is what is returned from the method. Can be a primitive, class name, or model name." + }, + "responseNotes": { + "type": "string", + "description": "A description of the response returned by the operation" + }, + "responseType": { + "type": "string", + "description": "The type of response that the operation creates. If not specified, this value will be automatically inferred based on whether or not there is a model matching the name, if a matching class name is found, or set to 'primitive' by default.", + "enum": [ "primitive", "class", "model", "documentation" ] + }, + "deprecated": { + "type": "boolean", + "description": "Whether or not the operation is deprecated" + }, + "errorResponses": { + "description": "Errors that could occur while executing the operation", + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "number", + "description": "HTTP response status code of the error" + }, + "reason": { + "type": "string", + "description": "Response reason phrase or description of the error" + }, + "class": { + "type": "string", + "description": "A custom exception class that would be thrown if the error is encountered" + } + } + } + }, + "data": { + "type": "object", + "additionalProperties": "true" + }, + "parameters": { + "$ref": "parameters", + "description": "Parameters of the operation. Parameters are used to define how input data is serialized into a HTTP request." + }, + "additionalParameters": { + "$ref": "parameters", + "description": "Validation and serialization rules for any parameter supplied to the operation that was not explicitly defined." + } + } + }, + "models": { + "description": "Schema models that can be referenced throughout the service description. Models can be used to define how an HTTP response is parsed into a Guzzle\\Service\\Resource\\Model object.", + "type": "object", + "properties": { + "$ref": "parameters", + "description": "Parameters of the model. When a model is referenced in a responseClass attribute of an operation, parameters define how a HTTP response message is parsed into a Guzzle\\Service\\Resource\\Model." + } + }, + "includes": { + "description": "Service description files to include and extend from (can be a .json, .js, or .php file)", + "type": "array", + "items": { + "type": "string", + "pattern": ".+\\.(js|json|php)$" + } + }, + "definitions": { + "parameters": { + "extends": "http://json-schema.org/schema", + "id": "parameters", + "name": { + "type": "string", + "description": "Unique name of the parameter" + }, + "type": { + "type": ["string", "array"], + "description": "Type of variable (string, number, integer, boolean, object, array, numeric, null, any). Types are using for validation and determining the structure of a parameter. You can use a union type by providing an array of simple types. If one of the union types matches the provided value, then the value is valid." + }, + "instanceOf": { + "type": "string", + "description": "When the type is an object, you can specify the class that the object must implement" + }, + "required": { + "type": "boolean", + "description": "Whether or not the parameter is required" + }, + "default": { + "description": "Default value to use if no value is supplied" + }, + "static": { + "type": "bool", + "description": "Set to true to specify that the parameter value cannot be changed from the default setting" + }, + "description": { + "type": "string", + "description": "Documentation of the parameter" + }, + "location": { + "type": "string", + "description": "The location of a request used to apply a parameter. Custom locations can be registered with a command, but the defaults are uri, query, statusCode, reasonPhrase, header, body, json, xml, postField, postFile, responseBody" + }, + "sentAs": { + "type": "string", + "description": "Specifies how the data being modeled is sent over the wire. For example, you may wish to include certain headers in a response model that have a normalized casing of FooBar, but the actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar." + }, + "filters": { + "type": "array", + "description": "Array of static method names to to run a parameter value through. Each value in the array must be a string containing the full class path to a static method or an array of complex filter information. You can specify static methods of classes using the full namespace class name followed by ‘::’ (e.g. FooBar::baz()). Some filters require arguments in order to properly filter a value. For complex filters, use a hash containing a ‘method’ key pointing to a static method, and an ‘args’ key containing an array of positional arguments to pass to the method. Arguments can contain keywords that are replaced when filtering a value: '@value‘ is replaced with the value being validated, '@api‘ is replaced with the Parameter object.", + "items": { + "type": ["string", { + "object": { + "properties": { + "method": { + "type": "string", + "description": "PHP function to call", + "required": true + }, + "args": { + "type": "array" + } + } + } + }] + } + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png b/source/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png new file mode 100644 index 0000000..f1017f7 Binary files /dev/null and b/source/vendor/guzzle/guzzle/docs/_static/guzzle-icon.png differ diff --git a/source/vendor/guzzle/guzzle/docs/_static/homepage.css b/source/vendor/guzzle/guzzle/docs/_static/homepage.css new file mode 100644 index 0000000..70c46d8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/_static/homepage.css @@ -0,0 +1,122 @@ +/* Hero unit on homepage */ + +.hero-unit h1 { + font-size: 49px; + margin-bottom: 12px; +} + +.hero-unit { + padding: 40px; +} + +.hero-unit p { + font-size: 17px; +} + +.masthead img { + float: left; + margin-right: 17px; +} + +.hero-unit ul li { + margin-left: 220px; +} + +.hero-unit .buttons { + text-align: center; +} + +.jumbotron { + position: relative; + padding: 40px 0; + color: #fff; + text-shadow: 0 1px 3px rgba(0,0,0,.4), 0 0 30px rgba(0,0,0,.075); + background: #00312F; + background: -moz-linear-gradient(45deg, #002F31 0%, #335A6D 100%); + background: -webkit-gradient(linear, left bottom, right top, color-stop(0%,#00312D), color-stop(100%,#33566D)); + background: -webkit-linear-gradient(45deg, #020031 0%,#334F6D 100%); + background: -o-linear-gradient(45deg, #002D31 0%,#334D6D 100%); + background: -ms-linear-gradient(45deg, #002F31 0%,#33516D 100%); + background: linear-gradient(45deg, #020031 0%,#33516D 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#020031', endColorstr='#6d3353',GradientType=1 ); + -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, .2), inset 0 -3px 7px rgba(0, 0, 0, .2); + -moz-box-shadow: inset 0 3px 7px rgba(0,0,0,.2), inset 0 -3px 7px rgba(0,0,0,.2); + box-shadow: inset 0 3px 7px rgba(0, 0, 0, .2), inset 0 -3px 7px rgba(0, 0, 0, .2); +} + +.jumbotron h1 { + font-size: 80px; + font-weight: bold; + letter-spacing: -1px; + line-height: 1; +} + +.jumbotron p { + font-size: 24px; + font-weight: 300; + line-height: 1.25; + margin-bottom: 30px; +} + +.masthead { + padding: 40px 0 30px; + margin-bottom: 0; + color: #fff; + margin-top: -19px; +} + +.masthead h1 { + display: none; +} + +.masthead p { + font-size: 40px; + font-weight: 200; + line-height: 1.25; + margin: 12px 0 0 0; +} + +.masthead .btn { + padding: 19px 24px; + font-size: 24px; + font-weight: 200; + border: 0; +} + +/* Social bar on homepage */ + +.social { + padding: 2px 0; + text-align: center; + background-color: #f5f5f5; + border-top: 1px solid #fff; + border-bottom: 1px solid #ddd; + margin: 0 0 20px 0; +} + +.social ul { + margin-top: 0; +} + +.social-buttons { + margin-left: 0; + margin-bottom: 0; + padding-left: 0; + list-style: none; +} + +.social-buttons li { + display: inline-block; + padding: 5px 8px; + line-height: 1; + *display: inline; + *zoom: 1; +} + +.center-announcement { + padding: 10px; + background-color: rgb(238, 243, 255); + border-radius: 8px; + text-align: center; + margin: 24px 0; +} diff --git a/source/vendor/guzzle/guzzle/docs/_static/logo.png b/source/vendor/guzzle/guzzle/docs/_static/logo.png new file mode 100644 index 0000000..965a4ef Binary files /dev/null and b/source/vendor/guzzle/guzzle/docs/_static/logo.png differ diff --git a/source/vendor/guzzle/guzzle/docs/_static/prettify.css b/source/vendor/guzzle/guzzle/docs/_static/prettify.css new file mode 100644 index 0000000..4d410b1 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/_static/prettify.css @@ -0,0 +1,41 @@ +.com { + color: #93A1A1; +} +.lit { + color: #195F91; +} +.pun, .opn, .clo { + color: #93A1A1; +} +.fun { + color: #DC322F; +} +.str, .atv { + color: #DD1144; +} +.kwd, .linenums .tag { + color: #1E347B; +} +.typ, .atn, .dec, .var { + color: teal; +} +.pln { + color: #48484C; +} +.prettyprint { + background-color: #F7F7F9; + border: 1px solid #E1E1E8; + padding: 8px; +} +.prettyprint.linenums { + box-shadow: 40px 0 0 #FBFBFC inset, 41px 0 0 #ECECF0 inset; +} +ol.linenums { + margin: 0 0 0 33px; +} +ol.linenums li { + color: #BEBEC5; + line-height: 18px; + padding-left: 12px; + text-shadow: 0 1px 0 #FFFFFF; +} diff --git a/source/vendor/guzzle/guzzle/docs/_static/prettify.js b/source/vendor/guzzle/guzzle/docs/_static/prettify.js new file mode 100644 index 0000000..eef5ad7 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/_static/prettify.js @@ -0,0 +1,28 @@ +var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= +[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), +l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, +q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, +q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, +"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), +a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} +for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], +H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], +J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ +I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p + + + +
    +
    + guzzle +

    Guzzle

    +

    Guzzle is a PHP HTTP client
    & framework for building RESTful web service clients.

    +

    + View Guzzle on GitHub + Read the docs +

    +
    +
    + + + +
    + +

    Introducing Guzzle

    + +

    Guzzle takes the pain out of sending HTTP requests and the redundancy out of creating web service clients. It's + a framework that includes the tools needed to create a robust web service client, including: + Service descriptions for defining the inputs and outputs of an API, resource iterators for traversing + paginated resources, batching for sending a large number of requests as efficiently as possible.

    + +
      +
    • All the power of cURL with a simple interface.
    • +
    • Persistent connections and parallel requests.
    • +
    • Streams request and response bodies
    • +
    • Service descriptions for quickly building clients.
    • +
    • Powered by the Symfony2 EventDispatcher.
    • +
    • Use all of the code or only specific components.
    • +
    • Plugins for caching, logging, OAuth, mocks, and more
    • +
    • Includes a custom node.js webserver to test your clients.
    • +
    + +
    + Guzzle is now part of Drupal 8 core and powers the official AWS SDK for PHP +
    + +

    GitHub Example

    + +
    <?php
    +require_once 'vendor/autoload.php';
    +use Guzzle\Http\Client;
    +
    +// Create a client and provide a base URL
    +$client = new Client('https://api.github.com');
    +// Create a request with basic Auth
    +$request = $client->get('/user')->setAuth('user', 'pass');
    +// Send the request and get the response
    +$response = $request->send();
    +echo $response->getBody();
    +// >>> {"type":"User", ...
    +echo $response->getHeader('Content-Length');
    +// >>> 792
    +
    + +

    Twitter Example

    +
    <?php
    +// Create a client to work with the Twitter API
    +$client = new Client('https://api.twitter.com/{version}', array(
    +    'version' => '1.1'
    +));
    +
    +// Sign all requests with the OauthPlugin
    +$client->addSubscriber(new Guzzle\Plugin\Oauth\OauthPlugin(array(
    +    'consumer_key'  => '***',
    +    'consumer_secret' => '***',
    +    'token'       => '***',
    +    'token_secret'  => '***'
    +)));
    +
    +echo $client->get('statuses/user_timeline.json')->send()->getBody();
    +// >>> {"public_gists":6,"type":"User" ...
    +
    +// Create a tweet using POST
    +$request = $client->post('statuses/update.json', null, array(
    +    'status' => 'Tweeted with Guzzle, http://guzzlephp.org'
    +));
    +
    +// Send the request and parse the JSON response into an array
    +$data = $request->send()->json();
    +echo $data['text'];
    +// >>> Tweeted with Guzzle, http://t.co/kngJMfRk
    +
    +
    + + diff --git a/source/vendor/guzzle/guzzle/docs/_templates/leftbar.html b/source/vendor/guzzle/guzzle/docs/_templates/leftbar.html new file mode 100644 index 0000000..e69de29 diff --git a/source/vendor/guzzle/guzzle/docs/_templates/nav_links.html b/source/vendor/guzzle/guzzle/docs/_templates/nav_links.html new file mode 100644 index 0000000..d4f2165 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/_templates/nav_links.html @@ -0,0 +1,5 @@ +
  • Docs
  • +
  • API
  • +
  • GitHub
  • +
  • Forum
  • +
  • IRC
  • diff --git a/source/vendor/guzzle/guzzle/docs/batching/batching.rst b/source/vendor/guzzle/guzzle/docs/batching/batching.rst new file mode 100644 index 0000000..57f04d8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/batching/batching.rst @@ -0,0 +1,183 @@ +======== +Batching +======== + +Guzzle provides a fairly generic and very customizable batching framework that allows developers to efficiently +transfer requests in parallel. + +Sending requests and commands in parallel +----------------------------------------- + +You can send HTTP requests in parallel by passing an array of ``Guzzle\Http\Message\RequestInterface`` objects to +``Guzzle\Http\Client::send()``: + +.. code-block:: php + + $responses = $client->send(array( + $client->get('http://www.example.com/foo'), + $client->get('http://www.example.com/baz') + $client->get('http://www.example.com/bar') + )); + +You can send commands in parallel by passing an array of ``Guzzle\Service\Command\CommandInterface`` objects +``Guzzle\Service\Client::execute()``: + +.. code-block:: php + + $commands = $client->execute(array( + $client->getCommand('foo'), + $client->getCommand('baz'), + $client->getCommand('bar') + )); + +These approaches work well for most use-cases. When you need more control over the requests that are sent in +parallel or you need to send a large number of requests, you need to use the functionality provided in the +``Guzzle\Batch`` namespace. + +Batching overview +----------------- + +The batch object, ``Guzzle\Batch\Batch``, is a queue. You add requests to the queue until you are ready to transfer +all of the requests. In order to efficiently transfer the items in the queue, the batch object delegates the +responsibility of dividing the queue into manageable parts to a divisor (``Guzzle\Batch\BatchDivisorInterface``). +The batch object then iterates over each array of items created by the divisor and sends them to the batch object's +``Guzzle\Batch\BatchTransferInterface``. + +.. code-block:: php + + use Guzzle\Batch\Batch; + use Guzzle\Http\BatchRequestTransfer; + + // BatchRequestTransfer acts as both the divisor and transfer strategy + $transferStrategy = new BatchRequestTransfer(10); + $divisorStrategy = $transferStrategy; + + $batch = new Batch($transferStrategy, $divisorStrategy); + + // Add some requests to the batch queue + $batch->add($request1) + ->add($request2) + ->add($request3); + + // Flush the queue and retrieve the flushed items + $arrayOfTransferredRequests = $batch->flush(); + +.. note:: + + You might find that your transfer strategy will need to act as both the divisor and transfer strategy. + +Using the BatchBuilder +---------------------- + +The ``Guzzle\Batch\BatchBuilder`` makes it easier to create batch objects. The batch builder also provides an easier +way to add additional behaviors to your batch object. + +Transferring requests +~~~~~~~~~~~~~~~~~~~~~ + +The ``Guzzle\Http\BatchRequestTransfer`` class efficiently transfers HTTP requests in parallel by grouping batches of +requests by the curl_multi handle that is used to transfer the requests. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferRequests(10) + ->build(); + +Transferring commands +~~~~~~~~~~~~~~~~~~~~~ + +The ``Guzzle\Service\Command\BatchCommandTransfer`` class efficiently transfers service commands by grouping commands +by the client that is used to transfer them. You can add commands to a batch object that are transferred by different +clients, and the batch will handle the rest. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferCommands(10) + ->build(); + + $batch->add($client->getCommand('foo')) + ->add($client->getCommand('baz')) + ->add($client->getCommand('bar')); + + $commands = $batch->flush(); + +Batch behaviors +--------------- + +You can add various behaviors to your batch that allow for more customizable transfers. + +Automatically flushing a queue +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``Guzzle\Batch\FlushingBatch`` decorator when you want to pump a large number of items into a batch queue and +have the queue automatically flush when the size of the queue reaches a certain threshold. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferRequests(10) + ->autoFlushAt(10) + ->build(); + +Batch builder method: ``autoFlushAt($threshold)`` + +Notifying on flush +~~~~~~~~~~~~~~~~~~ + +Use the ``Guzzle\Batch\NotifyingBatch`` decorator if you want a function to be notified each time the batch queue is +flushed. This is useful when paired with the flushing batch decorator. Pass a callable to the ``notify()`` method of +a batch builder to use this decorator with the builder. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferRequests(10) + ->autoFlushAt(10) + ->notify(function (array $transferredItems) { + echo 'Transferred ' . count($transferredItems) . "items\n"; + }) + ->build(); + +Batch builder method:: ``notify(callable $callback)`` + +Keeping a history +~~~~~~~~~~~~~~~~~ + +Use the ``Guzzle\Batch\HistoryBatch`` decorator if you want to maintain a history of all the items transferred with +the batch queue. + +.. code-block:: php + + use Guzzle\Batch\BatchBuilder; + + $batch = BatchBuilder::factory() + ->transferRequests(10) + ->keepHistory() + ->build(); + +After transferring items, you can use the ``getHistory()`` of a batch to retrieve an array of transferred items. Be +sure to periodically clear the history using ``clearHistory()``. + +Batch builder method: ``keepHistory()`` + +Exception buffering +~~~~~~~~~~~~~~~~~~~ + +Use the ``Guzzle\Batch\ExceptionBufferingBatch`` decorator to buffer exceptions during a transfer so that you can +transfer as many items as possible then deal with the errored batches after the transfer completes. After transfer, +use the ``getExceptions()`` method of a batch to retrieve an array of +``Guzzle\Batch\Exception\BatchTransferException`` objects. You can use these exceptions to attempt to retry the +failed batches. Be sure to clear the buffered exceptions when you are done with them by using the +``clearExceptions()`` method. + +Batch builder method: ``bufferExceptions()`` diff --git a/source/vendor/guzzle/guzzle/docs/conf.py b/source/vendor/guzzle/guzzle/docs/conf.py new file mode 100644 index 0000000..92bc46b --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/conf.py @@ -0,0 +1,94 @@ +import sys, os +from sphinx.highlighting import lexers +from pygments.lexers.web import PhpLexer + +lexers['php'] = PhpLexer(startinline=True, linenos=1) +lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1) +primary_domain = 'php' + +# -- General configuration ----------------------------------------------------- + +extensions = [] +templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' + +project = u'Guzzle' +copyright = u'2012, Michael Dowling' +version = '3.0.0' +release = '3.0.0' + +exclude_patterns = ['_build'] + +# -- Options for HTML output --------------------------------------------------- + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = "Guzzle documentation" +html_short_title = "Guzzle" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + '**': ['localtoc.html', 'leftbar.html', 'searchbox.html'] +} + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Guzzledoc' + +# -- Guzzle Sphinx theme setup ------------------------------------------------ + +sys.path.insert(0, '/Users/dowling/projects/guzzle_sphinx_theme') + +import guzzle_sphinx_theme +html_translator_class = 'guzzle_sphinx_theme.HTMLTranslator' +html_theme_path = guzzle_sphinx_theme.html_theme_path() +html_theme = 'guzzle_sphinx_theme' + +# Guzzle theme options (see theme.conf for more information) +html_theme_options = { + "index_template": "index.html", + "project_nav_name": "Guzzle", + "github_user": "guzzle", + "github_repo": "guzzle", + "disqus_comments_shortname": "guzzle", + "google_analytics_account": "UA-22752917-1" +} + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = {} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Guzzle.tex', u'Guzzle Documentation', + u'Michael Dowling', 'manual'), +] + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'guzzle', u'Guzzle Documentation', + [u'Michael Dowling'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Guzzle', u'Guzzle Documentation', + u'Michael Dowling', 'Guzzle', 'One line description of project.', + 'Miscellaneous'), +] diff --git a/source/vendor/guzzle/guzzle/docs/docs.rst b/source/vendor/guzzle/guzzle/docs/docs.rst new file mode 100644 index 0000000..cf87908 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/docs.rst @@ -0,0 +1,73 @@ +.. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services + +==================== +Guzzle Documentation +==================== + +Getting started +--------------- + +.. toctree:: + :maxdepth: 1 + + getting-started/overview + getting-started/installation + getting-started/faq + +The HTTP client +--------------- + +.. toctree:: + :maxdepth: 2 + + http-client/client + http-client/request + http-client/response + http-client/entity-bodies + http-client/http-redirects + http-client/uri-templates + +Plugins +------- + +.. toctree:: + :maxdepth: 1 + + plugins/plugins-overview + plugins/creating-plugins + plugins/async-plugin + plugins/backoff-plugin + plugins/cache-plugin + plugins/cookie-plugin + plugins/curl-auth-plugin + plugins/history-plugin + plugins/log-plugin + plugins/md5-validator-plugin + plugins/mock-plugin + plugins/oauth-plugin + +The web service client +---------------------- + +.. toctree:: + :maxdepth: 1 + + webservice-client/webservice-client + webservice-client/using-the-service-builder + webservice-client/guzzle-service-descriptions + batching/batching + iterators/resource-iterators + iterators/guzzle-iterators + +Testing +------- + +.. toctree:: + :maxdepth: 2 + + testing/unit-testing + +API Docs +-------- + +`Read the API docs `_ diff --git a/source/vendor/guzzle/guzzle/docs/getting-started/faq.rst b/source/vendor/guzzle/guzzle/docs/getting-started/faq.rst new file mode 100644 index 0000000..a0a3fdb --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/getting-started/faq.rst @@ -0,0 +1,29 @@ +=== +FAQ +=== + +What should I do if I get this error: Fatal error: Maximum function nesting level of '100' reached, aborting! +------------------------------------------------------------------------------------------------------------- + +You could run into this error if you have the XDebug extension installed and you execute a lot of requests in +callbacks. This error message comes specifically from the XDebug extension. PHP itself does not have a function +nesting limit. Change this setting in your php.ini to increase the limit:: + + xdebug.max_nesting_level = 1000 + +[`source `_] + +How can I speed up my client? +----------------------------- + +There are several things you can do to speed up your client: + +1. Utilize a C based HTTP message parser (e.g. ``Guzzle\Parser\Message\PeclHttpMessageParser``) +2. Disable operation validation by setting the ``command.disable_validation`` option to true on a command + +Why am I getting a 417 error response? +-------------------------------------- + +This can occur for a number of reasons, but if you are sending PUT, POST, or PATCH requests with an +``Expect: 100-Continue`` header, a server that does not support this header will return a 417 response. You can work +around this by calling ``$request->removeHeader('Expect');`` after setting the entity body of a request. diff --git a/source/vendor/guzzle/guzzle/docs/getting-started/installation.rst b/source/vendor/guzzle/guzzle/docs/getting-started/installation.rst new file mode 100644 index 0000000..77d4001 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/getting-started/installation.rst @@ -0,0 +1,154 @@ +============ +Installation +============ + +Requirements +------------ + +#. PHP 5.3.3+ compiled with the cURL extension +#. A recent version of cURL 7.16.2+ compiled with OpenSSL and zlib + +Installing Guzzle +----------------- + +Composer +~~~~~~~~ + +The recommended way to install Guzzle is with `Composer `_. Composer is a dependency +management tool for PHP that allows you to declare the dependencies your project needs and installs them into your +project. + +.. code-block:: bash + + # Install Composer + curl -sS https://getcomposer.org/installer | php + + # Add Guzzle as a dependency + php composer.phar require guzzle/guzzle:~3.9 + +After installing, you need to require Composer's autoloader: + +.. code-block:: php + + require 'vendor/autoload.php'; + +You can find out more on how to install Composer, configure autoloading, and other best-practices for defining +dependencies at `getcomposer.org `_. + +Using only specific parts of Guzzle +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While you can always just rely on ``guzzle/guzzle``, Guzzle provides several smaller parts of Guzzle as individual +packages available through Composer. + ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| Package name | Description | ++===============================================================================================+==========================================+ +| `guzzle/common `_ | Provides ``Guzzle\Common`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/http `_ | Provides ``Guzzle\Http`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/parser `_ | Provides ``Guzzle\Parser`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/batch `_ | Provides ``Guzzle\Batch`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/cache `_ | Provides ``Guzzle\Cache`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/inflection `_ | Provides ``Guzzle\Inflection`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/iterator `_ | Provides ``Guzzle\Iterator`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/log `_ | Provides ``Guzzle\Log`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin `_ | Provides ``Guzzle\Plugin`` (all plugins) | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-async `_ | Provides ``Guzzle\Plugin\Async`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-backoff `_ | Provides ``Guzzle\Plugin\BackoffPlugin`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-cache `_ | Provides ``Guzzle\Plugin\Cache`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-cookie `_ | Provides ``Guzzle\Plugin\Cookie`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-error-response `_ | Provides ``Guzzle\Plugin\ErrorResponse`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-history `_ | Provides ``Guzzle\Plugin\History`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-log `_ | Provides ``Guzzle\Plugin\Log`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-md5 `_ | Provides ``Guzzle\Plugin\Md5`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-mock `_ | Provides ``Guzzle\Plugin\Mock`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/plugin-oauth `_ | Provides ``Guzzle\Plugin\Oauth`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/service `_ | Provides ``Guzzle\Service`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ +| `guzzle/stream `_ | Provides ``Guzzle\Stream`` | ++-----------------------------------------------------------------------------------------------+------------------------------------------+ + +Bleeding edge +^^^^^^^^^^^^^ + +During your development, you can keep up with the latest changes on the master branch by setting the version +requirement for Guzzle to ``dev-master``. + +.. code-block:: js + + { + "require": { + "guzzle/guzzle": "dev-master" + } + } + +PEAR +~~~~ + +Guzzle can be installed through PEAR: + +.. code-block:: bash + + pear channel-discover guzzlephp.org/pear + pear install guzzle/guzzle + +You can install a specific version of Guzzle by providing a version number suffix: + +.. code-block:: bash + + pear install guzzle/guzzle-3.9.0 + +Contributing to Guzzle +---------------------- + +In order to contribute, you'll need to checkout the source from GitHub and install Guzzle's dependencies using +Composer: + +.. code-block:: bash + + git clone https://github.com/guzzle/guzzle.git + cd guzzle && curl -s http://getcomposer.org/installer | php && ./composer.phar install --dev + +Guzzle is unit tested with PHPUnit. You will need to create your own phpunit.xml file in order to run the unit tests +(or just copy phpunit.xml.dist to phpunit.xml). Run the tests using the vendored PHPUnit binary: + +.. code-block:: bash + + vendor/bin/phpunit + +You'll need to install node.js v0.5.0 or newer in order to test the cURL implementation. + +Framework integrations +---------------------- + +Using Guzzle with Symfony +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Bundles are available on GitHub: + +- `DdeboerGuzzleBundle `_ for Guzzle 2 +- `MisdGuzzleBundle `_ for Guzzle 3 + +Using Guzzle with Silex +~~~~~~~~~~~~~~~~~~~~~~~ + +A `Guzzle Silex service provider `_ is available on GitHub. diff --git a/source/vendor/guzzle/guzzle/docs/getting-started/overview.rst b/source/vendor/guzzle/guzzle/docs/getting-started/overview.rst new file mode 100644 index 0000000..505b409 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/getting-started/overview.rst @@ -0,0 +1,85 @@ +================= +Welcome to Guzzle +================= + +What is Guzzle? +~~~~~~~~~~~~~~~ + +Guzzle is a PHP HTTP client and framework for building web service clients. Guzzle takes the pain out of sending HTTP +requests and the redundancy out of creating web service clients. + +Features at a glance +-------------------- + +- All the power of cURL with a simple interface. +- Persistent connections and parallel requests. +- Streams request and response bodies +- Service descriptions for quickly building clients. +- Powered by the Symfony2 EventDispatcher. +- Use all of the code or only specific components. +- Plugins for caching, logging, OAuth, mocks, and more +- Includes a custom node.js webserver to test your clients. +- Service descriptions for defining the inputs and outputs of an API +- Resource iterators for traversing paginated resources +- Batching for sending a large number of requests as efficiently as possible + +.. code-block:: php + + // Really simple using a static facade + Guzzle\Http\StaticClient::mount(); + $response = Guzzle::get('http://guzzlephp.org'); + + // More control using a client class + $client = new \Guzzle\Http\Client('http://guzzlephp.org'); + $request = $client->get('/'); + $response = $request->send(); + +License +------- + +Licensed using the `MIT license `_. + + Copyright (c) 2013 Michael Dowling + + 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. + +Contributing +------------ + +Guidelines +~~~~~~~~~~ + +This is still a work in progress, but there are only a few rules: + +1. Guzzle follows PSR-0, PSR-1, and PSR-2 +2. All pull requests must include unit tests to ensure the change works as expected and to prevent future regressions + +Reporting a security vulnerability +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We want to ensure that Guzzle is a secure HTTP client library for everyone. If you've discovered a security +vulnerability in Guzzle, we appreciate your help in disclosing it to us in a +`responsible manner `_. + +Publicly disclosing a vulnerability can put the entire community at risk. If you've discovered a security concern, +please email us at security@guzzlephp.org. We'll work with you to make sure that we understand the scope of the issue, +and that we fully address your concern. We consider correspondence sent to security@guzzlephp.org our highest priority, +and work to address any issues that arise as quickly as possible. + +After a security vulnerability has been corrected, a security hotfix release will be deployed as soon as possible. diff --git a/source/vendor/guzzle/guzzle/docs/http-client/client.rst b/source/vendor/guzzle/guzzle/docs/http-client/client.rst new file mode 100644 index 0000000..723d729 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/http-client/client.rst @@ -0,0 +1,569 @@ +====================== +The Guzzle HTTP client +====================== + +Guzzle gives PHP developers complete control over HTTP requests while utilizing HTTP/1.1 best practices. Guzzle's HTTP +functionality is a robust framework built on top of the `PHP libcurl bindings `_. + +The three main parts of the Guzzle HTTP client are: + ++--------------+-------------------------------------------------------------------------------------------------------+ +| Clients | ``Guzzle\Http\Client`` (creates and sends requests, associates a response with a request) | ++--------------+-------------------------------------------------------------------------------------------------------+ +| Requests | ``Guzzle\Http\Message\Request`` (requests with no body), | +| | ``Guzzle\Http\Message\EntityEnclosingRequest`` (requests with a body) | ++--------------+-------------------------------------------------------------------------------------------------------+ +| Responses | ``Guzzle\Http\Message\Response`` | ++--------------+-------------------------------------------------------------------------------------------------------+ + +Creating a Client +----------------- + +Clients create requests, send requests, and set responses on a request object. When instantiating a client object, +you can pass an optional "base URL" and optional array of configuration options. A base URL is a +:doc:`URI template ` that contains the URL of a remote server. When creating requests with a relative +URL, the base URL of a client will be merged into the request's URL. + +.. code-block:: php + + use Guzzle\Http\Client; + + // Create a client and provide a base URL + $client = new Client('https://api.github.com'); + + $request = $client->get('/user'); + $request->setAuth('user', 'pass'); + echo $request->getUrl(); + // >>> https://api.github.com/user + + // You must send a request in order for the transfer to occur + $response = $request->send(); + + echo $response->getBody(); + // >>> {"type":"User", ... + + echo $response->getHeader('Content-Length'); + // >>> 792 + + $data = $response->json(); + echo $data['type']; + // >>> User + +Base URLs +~~~~~~~~~ + +Notice that the URL provided to the client's ``get()`` method is relative. Relative URLs will always merge into the +base URL of the client. There are a few rules that control how the URLs are merged. + +.. tip:: + + Guzzle follows `RFC 3986 `_ when merging base URLs and + relative URLs. + +In the above example, we passed ``/user`` to the ``get()`` method of the client. This is a relative URL, so it will +merge into the base URL of the client-- resulting in the derived URL of ``https://api.github.com/users``. + +``/user`` is a relative URL but uses an absolute path because it contains the leading slash. Absolute paths will +overwrite any existing path of the base URL. If an absolute path is provided (e.g. ``/path/to/something``), then the +path specified in the base URL of the client will be replaced with the absolute path, and the query string provided +by the relative URL will replace the query string of the base URL. + +Omitting the leading slash and using relative paths will add to the path of the base URL of the client. So using a +client base URL of ``https://api.twitter.com/v1.1`` and creating a GET request with ``statuses/user_timeline.json`` +will result in a URL of ``https://api.twitter.com/v1.1/statuses/user_timeline.json``. If a relative path and a query +string are provided, then the relative path will be appended to the base URL path, and the query string provided will +be merged into the query string of the base URL. + +If an absolute URL is provided (e.g. ``http://httpbin.org/ip``), then the request will completely use the absolute URL +as-is without merging in any of the URL parts specified in the base URL. + +Configuration options +~~~~~~~~~~~~~~~~~~~~~ + +The second argument of the client's constructor is an array of configuration data. This can include URI template data +or special options that alter the client's behavior: + ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``request.options`` | Associative array of :ref:`Request options ` to apply to every | +| | request created by the client. | ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``redirect.disable`` | Disable HTTP redirects for every request created by the client. | ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``curl.options`` | Associative array of cURL options to apply to every request created by the client. | +| | if either the key or value of an entry in the array is a string, Guzzle will | +| | attempt to find a matching defined cURL constant automatically (e.g. | +| | "CURLOPT_PROXY" will be converted to the constant ``CURLOPT_PROXY``). | ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``ssl.certificate_authority`` | Set to true to use the Guzzle bundled SSL certificate bundle (this is used by | +| | default, 'system' to use the bundle on your system, a string pointing to a file to | +| | use a specific certificate file, a string pointing to a directory to use multiple | +| | certificates, or ``false`` to disable SSL validation (not recommended). | +| | | +| | When using Guzzle inside of a phar file, the bundled SSL certificate will be | +| | extracted to your system's temp folder, and each time a client is created an MD5 | +| | check will be performed to ensure the integrity of the certificate. | ++-------------------------------+-------------------------------------------------------------------------------------+ +| ``command.params`` | When using a ``Guzzle\Service\Client`` object, this is an associative array of | +| | default options to set on each command created by the client. | ++-------------------------------+-------------------------------------------------------------------------------------+ + +Here's an example showing how to set various configuration options, including default headers to send with each request, +default query string parameters to add to each request, a default auth scheme for each request, and a proxy to use for +each request. Values can be injected into the client's base URL using variables from the configuration array. + +.. code-block:: php + + use Guzzle\Http\Client; + + $client = new Client('https://api.twitter.com/{version}', array( + 'version' => 'v1.1', + 'request.options' => array( + 'headers' => array('Foo' => 'Bar'), + 'query' => array('testing' => '123'), + 'auth' => array('username', 'password', 'Basic|Digest|NTLM|Any'), + 'proxy' => 'tcp://localhost:80' + ) + )); + +Setting a custom User-Agent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default Guzzle User-Agent header is ``Guzzle/ curl/ PHP/``. You can +customize the User-Agent header of a client by calling the ``setUserAgent()`` method of a Client object. + +.. code-block:: php + + // Completely override the default User-Agent + $client->setUserAgent('Test/123'); + + // Prepend a string to the default User-Agent + $client->setUserAgent('Test/123', true); + +Creating requests with a client +------------------------------- + +A Client object exposes several methods used to create Request objects: + +* Create a custom HTTP request: ``$client->createRequest($method, $uri, array $headers, $body, $options)`` +* Create a GET request: ``$client->get($uri, array $headers, $options)`` +* Create a HEAD request: ``$client->head($uri, array $headers, $options)`` +* Create a DELETE request: ``$client->delete($uri, array $headers, $body, $options)`` +* Create a POST request: ``$client->post($uri, array $headers, $postBody, $options)`` +* Create a PUT request: ``$client->put($uri, array $headers, $body, $options)`` +* Create a PATCH request: ``$client->patch($uri, array $headers, $body, $options)`` + +.. code-block:: php + + use Guzzle\Http\Client; + + $client = new Client('http://baseurl.com/api/v1'); + + // Create a GET request using Relative to base URL + // URL of the request: http://baseurl.com/api/v1/path?query=123&value=abc) + $request = $client->get('path?query=123&value=abc'); + $response = $request->send(); + + // Create HEAD request using a relative URL with an absolute path + // URL of the request: http://baseurl.com/path?query=123&value=abc + $request = $client->head('/path?query=123&value=abc'); + $response = $request->send(); + + // Create a DELETE request using an absolute URL + $request = $client->delete('http://www.example.com/path?query=123&value=abc'); + $response = $request->send(); + + // Create a PUT request using the contents of a PHP stream as the body + // Specify custom HTTP headers + $request = $client->put('http://www.example.com/upload', array( + 'X-Header' => 'My Header' + ), fopen('http://www.test.com/', 'r')); + $response = $request->send(); + + // Create a POST request and add the POST files manually + $request = $client->post('http://localhost:8983/solr/update') + ->addPostFiles(array('file' => '/path/to/documents.xml')); + $response = $request->send(); + + // Check if a resource supports the DELETE method + $supportsDelete = $client->options('/path')->send()->isMethodAllowed('DELETE'); + $response = $request->send(); + +Client objects create Request objects using a request factory (``Guzzle\Http\Message\RequestFactoryInterface``). +You can inject a custom request factory into the Client using ``$client->setRequestFactory()``, but you can typically +rely on a Client's default request factory. + +Static clients +-------------- + +You can use Guzzle's static client facade to more easily send simple HTTP requests. + +.. code-block:: php + + // Mount the client so that you can access it at \Guzzle + Guzzle\Http\StaticClient::mount(); + $response = Guzzle::get('http://guzzlephp.org'); + +Each request method of the static client (e.g. ``get()``, ``post()`, ``put()``, etc) accepts an associative array of request +options to apply to the request. + +.. code-block:: php + + $response = Guzzle::post('http://test.com', array( + 'headers' => array('X-Foo' => 'Bar'), + 'body' => array('Test' => '123'), + 'timeout' => 10 + )); + +.. _request-options: + +Request options +--------------- + +Request options can be specified when creating a request or in the ``request.options`` parameter of a client. These +options can control various aspects of a request including: headers to send, query string data, where the response +should be downloaded, proxies, auth, etc. + +headers +~~~~~~~ + +Associative array of headers to apply to the request. When specified in the ``$options`` argument of a client creational +method (e.g. ``get()``, ``post()``, etc), the headers in the ``$options`` array will overwrite headers specified in the +``$headers`` array. + +.. code-block:: php + + $request = $client->get($url, array(), array( + 'headers' => array('X-Foo' => 'Bar') + )); + +Headers can be specified on a client to add default headers to every request sent by a client. + +.. code-block:: php + + $client = new Guzzle\Http\Client(); + + // Set a single header using path syntax + $client->setDefaultOption('headers/X-Foo', 'Bar'); + + // Set all headers + $client->setDefaultOption('headers', array('X-Foo' => 'Bar')); + +.. note:: + + In addition to setting request options when creating requests or using the ``setDefaultOption()`` method, any + default client request option can be set using a client's config object: + + .. code-block:: php + + $client->getConfig()->setPath('request.options/headers/X-Foo', 'Bar'); + +query +~~~~~ + +Associative array of query string parameters to the request. When specified in the ``$options`` argument of a client +creational method, the query string parameters in the ``$options`` array will overwrite query string parameters +specified in the `$url`. + +.. code-block:: php + + $request = $client->get($url, array(), array( + 'query' => array('abc' => '123') + )); + +Query string parameters can be specified on a client to add default query string parameters to every request sent by a +client. + +.. code-block:: php + + $client = new Guzzle\Http\Client(); + + // Set a single query string parameter using path syntax + $client->setDefaultOption('query/abc', '123'); + + // Set an array of default query string parameters + $client->setDefaultOption('query', array('abc' => '123')); + +body +~~~~ + +Sets the body of a request. The value supplied to the body option can be a ``Guzzle\Http\EntityBodyInterface``, string, +fopen resource, or array when sending POST requests. When a ``body`` request option is supplied, the option value will +overwrite the ``$body`` argument of a client creational method. + +auth +~~~~ + +Specifies and array of HTTP authorization parameters parameters to use with the request. The array must contain the +username in index [0], the password in index [1], and can optionally contain the authentication type in index [2]. +The available authentication types are: "Basic" (default), "Digest", "NTLM", or "Any". + +.. code-block:: php + + $request = $client->get($url, array(), array( + 'auth' => array('username', 'password', 'Digest') + )); + + // You can add auth headers to every request of a client + $client->setDefaultOption('auth', array('username', 'password', 'Digest')); + +cookies +~~~~~~~ + +Specifies an associative array of cookies to add to the request. + +allow_redirects +~~~~~~~~~~~~~~~ + +Specifies whether or not the request should follow redirects. Requests will follow redirects by default. Set +``allow_redirects`` to ``false`` to disable redirects. + +save_to +~~~~~~~ + +The ``save_to`` option specifies where the body of a response is downloaded. You can pass the path to a file, an fopen +resource, or a ``Guzzle\Http\EntityBodyInterface`` object. + +See :ref:`Changing where a response is downloaded ` for more information on setting the +`save_to` option. + +events +~~~~~~ + +The `events` option makes it easy to attach listeners to the various events emitted by a request object. The `events` +options must be an associative array mapping an event name to a Closure or array the contains a Closure and the +priority of the event. + +.. code-block:: php + + $request = $client->get($url, array(), array( + 'events' => array( + 'request.before_send' => function (\Guzzle\Common\Event $e) { + echo 'About to send ' . $e['request']; + } + ) + )); + + // Using the static client: + Guzzle::get($url, array( + 'events' => array( + 'request.before_send' => function (\Guzzle\Common\Event $e) { + echo 'About to send ' . $e['request']; + } + ) + )); + +plugins +~~~~~~~ + +The `plugins` options makes it easy to attach an array of plugins to a request. + +.. code-block:: php + + // Using the static client: + Guzzle::get($url, array( + 'plugins' => array( + new Guzzle\Plugin\Cache\CachePlugin(), + new Guzzle\Plugin\Cookie\CookiePlugin() + ) + )); + +exceptions +~~~~~~~~~~ + +The `exceptions` option can be used to disable throwing exceptions for unsuccessful HTTP response codes +(e.g. 404, 500, etc). Set `exceptions` to false to not throw exceptions. + +params +~~~~~~ + +The `params` options can be used to specify an associative array of data parameters to add to a request. Note that +these are not query string parameters. + +timeout / connect_timeout +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can specify the maximum number of seconds to allow for an entire transfer to take place before timing out using +the `timeout` request option. You can specify the maximum number of seconds to wait while trying to connect using the +`connect_timeout` request option. Set either of these options to 0 to wait indefinitely. + +.. code-block:: php + + $request = $client->get('http://www.example.com', array(), array( + 'timeout' => 20, + 'connect_timeout' => 1.5 + )); + +verify +~~~~~~ + +Set to true to enable SSL certificate validation (the default), false to disable SSL certificate validation, or supply +the path to a CA bundle to enable verification using a custom certificate. + +cert +~~~~ + +The `cert` option lets you specify a PEM formatted SSL client certificate to use with servers that require one. If the +certificate requires a password, provide an array with the password as the second item. + +This would typically be used in conjunction with the `ssl_key` option. + +.. code-block:: php + + $request = $client->get('https://www.example.com', array(), array( + 'cert' => '/etc/pki/client_certificate.pem' + ) + + $request = $client->get('https://www.example.com', array(), array( + 'cert' => array('/etc/pki/client_certificate.pem', 's3cr3tp455w0rd') + ) + +ssl_key +~~~~~~~ + +The `ssl_key` option lets you specify a file containing your PEM formatted private key, optionally protected by a password. +Note: your password is sensitive, keep the PHP script containing it safe. + +This would typically be used in conjunction with the `cert` option. + +.. code-block:: php + + $request = $client->get('https://www.example.com', array(), array( + 'ssl_key' => '/etc/pki/private_key.pem' + ) + + $request = $client->get('https://www.example.com', array(), array( + 'ssl_key' => array('/etc/pki/private_key.pem', 's3cr3tp455w0rd') + ) + +proxy +~~~~~ + +The `proxy` option is used to specify an HTTP proxy (e.g. `http://username:password@192.168.16.1:10`). + +debug +~~~~~ + +The `debug` option is used to show verbose cURL output for a transfer. + +stream +~~~~~~ + +When using a static client, you can set the `stream` option to true to return a `Guzzle\Stream\Stream` object that can +be used to pull data from a stream as needed (rather than have cURL download the entire contents of a response to a +stream all at once). + +.. code-block:: php + + $stream = Guzzle::get('http://guzzlephp.org', array('stream' => true)); + while (!$stream->feof()) { + echo $stream->readLine(); + } + +Sending requests +---------------- + +Requests can be sent by calling the ``send()`` method of a Request object, but you can also send requests using the +``send()`` method of a Client. + +.. code-block:: php + + $request = $client->get('http://www.amazon.com'); + $response = $client->send($request); + +Sending requests in parallel +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Client's ``send()`` method accept a single ``Guzzle\Http\Message\RequestInterface`` object or an array of +RequestInterface objects. When an array is specified, the requests will be sent in parallel. + +Sending many HTTP requests serially (one at a time) can cause an unnecessary delay in a script's execution. Each +request must complete before a subsequent request can be sent. By sending requests in parallel, a pool of HTTP +requests can complete at the speed of the slowest request in the pool, significantly reducing the amount of time +needed to execute multiple HTTP requests. Guzzle provides a wrapper for the curl_multi functions in PHP. + +Here's an example of sending three requests in parallel using a client object: + +.. code-block:: php + + use Guzzle\Common\Exception\MultiTransferException; + + try { + $responses = $client->send(array( + $client->get('http://www.google.com/'), + $client->head('http://www.google.com/'), + $client->get('https://www.github.com/') + )); + } catch (MultiTransferException $e) { + + echo "The following exceptions were encountered:\n"; + foreach ($e as $exception) { + echo $exception->getMessage() . "\n"; + } + + echo "The following requests failed:\n"; + foreach ($e->getFailedRequests() as $request) { + echo $request . "\n\n"; + } + + echo "The following requests succeeded:\n"; + foreach ($e->getSuccessfulRequests() as $request) { + echo $request . "\n\n"; + } + } + +If the requests succeed, an array of ``Guzzle\Http\Message\Response`` objects are returned. A single request failure +will not cause the entire pool of requests to fail. Any exceptions thrown while transferring a pool of requests will +be aggregated into a ``Guzzle\Common\Exception\MultiTransferException`` exception. + +Plugins and events +------------------ + +Guzzle provides easy to use request plugins that add behavior to requests based on signal slot event notifications +powered by the +`Symfony2 Event Dispatcher component `_. Any +event listener or subscriber attached to a Client object will automatically be attached to each request created by the +client. + +Using the same cookie session for each request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Attach a ``Guzzle\Plugin\Cookie\CookiePlugin`` to a client which will in turn add support for cookies to every request +created by a client, and each request will use the same cookie session: + +.. code-block:: php + + use Guzzle\Plugin\Cookie\CookiePlugin; + use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar; + + // Create a new cookie plugin + $cookiePlugin = new CookiePlugin(new ArrayCookieJar()); + + // Add the cookie plugin to the client + $client->addSubscriber($cookiePlugin); + +.. _client-events: + +Events emitted from a client +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``Guzzle\Http\Client`` object emits the following events: + ++------------------------------+--------------------------------------------+------------------------------------------+ +| Event name | Description | Event data | ++==============================+============================================+==========================================+ +| client.create_request | Called when a client creates a request | * client: The client | +| | | * request: The created request | ++------------------------------+--------------------------------------------+------------------------------------------+ + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Http\Client; + + $client = new Client(); + + // Add a listener that will echo out requests as they are created + $client->getEventDispatcher()->addListener('client.create_request', function (Event $e) { + echo 'Client object: ' . spl_object_hash($e['client']) . "\n"; + echo "Request object: {$e['request']}\n"; + }); diff --git a/source/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst b/source/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst new file mode 100644 index 0000000..823b0c0 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/http-client/entity-bodies.rst @@ -0,0 +1,151 @@ +=========================== +Request and response bodies +=========================== + +`Entity body `_ is the term used for the body of an HTTP +message. The entity body of requests and responses is inherently a +`PHP stream `_ in Guzzle. The body of the request can be either a string or +a PHP stream which are converted into a ``Guzzle\Http\EntityBody`` object using its factory method. When using a +string, the entity body is stored in a `temp PHP stream `_. The use of +temp PHP streams helps to protect your application from running out of memory when sending or receiving large entity +bodies in your messages. When more than 2MB of data is stored in a temp stream, it automatically stores the data on +disk rather than in memory. + +EntityBody objects provide a great deal of functionality: compression, decompression, calculate the Content-MD5, +calculate the Content-Length (when the resource is repeatable), guessing the Content-Type, and more. Guzzle doesn't +need to load an entire entity body into a string when sending or retrieving data; entity bodies are streamed when +being uploaded and downloaded. + +Here's an example of gzip compressing a text file then sending the file to a URL: + +.. code-block:: php + + use Guzzle\Http\EntityBody; + + $body = EntityBody::factory(fopen('/path/to/file.txt', 'r+')); + echo $body->read(1024); + $body->seek(0, SEEK_END); + $body->write('foo'); + echo $body->ftell(); + $body->rewind(); + + // Send a request using the body + $response = $client->put('http://localhost:8080/uploads', null, $body)->send(); + +The body of the request can be specified in the ``Client::put()`` or ``Client::post()`` method, or, you can specify +the body of the request by calling the ``setBody()`` method of any +``Guzzle\Http\Message\EntityEnclosingRequestInterface`` object. + +Compression +----------- + +You can compress the contents of an EntityBody object using the ``compress()`` method. The compress method accepts a +filter that must match to one of the supported +`PHP stream filters `_ on your system (e.g. `zlib.deflate`, +``bzip2.compress``, etc). Compressing an entity body will stream the entire entity body through a stream compression +filter into a temporary PHP stream. You can uncompress an entity body using the ``uncompress()`` method and passing +the PHP stream filter to use when decompressing the stream (e.g. ``zlib.inflate``). + +.. code-block:: php + + use Guzzle\Http\EntityBody; + + $body = EntityBody::factory(fopen('/tmp/test.txt', 'r+')); + echo $body->getSize(); + // >>> 1048576 + + // Compress using the default zlib.deflate filter + $body->compress(); + echo $body->getSize(); + // >>> 314572 + + // Decompress the stream + $body->uncompress(); + echo $body->getSize(); + // >>> 1048576 + +Decorators +---------- + +Guzzle provides several EntityBody decorators that can be used to add functionality to an EntityBody at runtime. + +IoEmittingEntityBody +~~~~~~~~~~~~~~~~~~~~ + +This decorator will emit events when data is read from a stream or written to a stream. Add an event subscriber to the +entity body's ``body.read`` or ``body.write`` methods to receive notifications when data data is transferred. + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Http\EntityBody; + use Guzzle\Http\IoEmittingEntityBody; + + $original = EntityBody::factory(fopen('/tmp/test.txt', 'r+')); + $body = new IoEmittingEntityBody($original); + + // Listen for read events + $body->getEventDispatcher()->addListener('body.read', function (Event $e) { + // Grab data from the event + $entityBody = $e['body']; + // Amount of data retrieved from the body + $lengthOfData = $e['length']; + // The actual data that was read + $data = $e['read']; + }); + + // Listen for write events + $body->getEventDispatcher()->addListener('body.write', function (Event $e) { + // Grab data from the event + $entityBody = $e['body']; + // The data that was written + $data = $e['write']; + // The actual amount of data that was written + $data = $e['read']; + }); + +ReadLimitEntityBody +~~~~~~~~~~~~~~~~~~~ + +The ReadLimitEntityBody decorator can be used to transfer a subset or slice of an existing EntityBody 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). + +.. code-block:: php + + use Guzzle\Http\EntityBody; + use Guzzle\Http\ReadLimitEntityBody; + + $original = EntityBody::factory(fopen('/tmp/test.txt', 'r+')); + echo $original->getSize(); + // >>> 1048576 + + // Limit the size of the body to 1024 bytes and start reading from byte 2048 + $body = new ReadLimitEntityBody($original, 1024, 2048); + echo $body->getSize(); + // >>> 1024 + echo $body->ftell(); + // >>> 0 + +CachingEntityBody +~~~~~~~~~~~~~~~~~ + +The CachingEntityBody decorator is used to allow seeking over previously read bytes on non-seekable read 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. + +.. code-block:: php + + use Guzzle\Http\EntityBody; + use Guzzle\Http\CachingEntityBody; + + $original = EntityBody::factory(fopen('http://www.google.com', 'r')); + $body = new CachingEntityBody($original); + + $body->read(1024); + echo $body->ftell(); + // >>> 1024 + + $body->seek(0); + echo $body->ftell(); + // >>> 0 diff --git a/source/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst b/source/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst new file mode 100644 index 0000000..32ba268 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/http-client/http-redirects.rst @@ -0,0 +1,99 @@ +============== +HTTP redirects +============== + +By default, Guzzle will automatically follow redirects using the non-RFC compliant implementation used by most web +browsers. This means that redirects for POST requests are followed by a GET request. You can force RFC compliance by +enabling the strict mode on a request's parameter object: + +.. code-block:: php + + // Set per request + $request = $client->post(); + $request->getParams()->set('redirect.strict', true); + + // You can set globally on a client so all requests use strict redirects + $client->getConfig()->set('request.params', array( + 'redirect.strict' => true + )); + +By default, Guzzle will redirect up to 5 times before throwing a ``Guzzle\Http\Exception\TooManyRedirectsException``. +You can raise or lower this value using the ``redirect.max`` parameter of a request object: + +.. code-block:: php + + $request->getParams()->set('redirect.max', 2); + +Redirect history +---------------- + +You can get the number of redirects of a request using the resulting response object's ``getRedirectCount()`` method. +Similar to cURL's ``effective_url`` property, Guzzle provides the effective URL, or the last redirect URL that returned +the request, in a response's ``getEffectiveUrl()`` method. + +When testing or debugging, it is often useful to see a history of redirects for a particular request. This can be +achieved using the HistoryPlugin. + +.. code-block:: php + + $request = $client->get('/'); + $history = new Guzzle\Plugin\History\HistoryPlugin(); + $request->addSubscriber($history); + $response = $request->send(); + + // Get the last redirect URL or the URL of the request that received + // this response + echo $response->getEffectiveUrl(); + + // Get the number of redirects + echo $response->getRedirectCount(); + + // Iterate over each sent request and response + foreach ($history->getAll() as $transaction) { + // Request object + echo $transaction['request']->getUrl() . "\n"; + // Response object + echo $transaction['response']->getEffectiveUrl() . "\n"; + } + + // Or, simply cast the HistoryPlugin to a string to view each request and response + echo $history; + +Disabling redirects +------------------- + +You can disable redirects on a client by passing a configuration option in the client's constructor: + +.. code-block:: php + + $client = new Client(null, array('redirect.disable' => true)); + +You can also disable redirects per request: + +.. code-block:: php + + $request = $client->get($url, array(), array('allow_redirects' => false)); + +Redirects and non-repeatable streams +------------------------------------ + +If you are redirected when sending data from a non-repeatable stream and some of the data has been read off of the +stream, then you will get a ``Guzzle\Http\Exception\CouldNotRewindStreamException``. You can get around this error by +adding a custom rewind method to the entity body object being sent in the request. + +.. code-block:: php + + $request = $client->post( + 'http://httpbin.com/redirect/2', + null, + fopen('http://httpbin.com/get', 'r') + ); + + // Add a custom function that can be used to rewind the stream + // (reopen in this example) + $request->getBody()->setRewindFunction(function ($body) { + $body->setStream(fopen('http://httpbin.com/get', 'r')); + return true; + ); + + $response = $client->send(); diff --git a/source/vendor/guzzle/guzzle/docs/http-client/request.rst b/source/vendor/guzzle/guzzle/docs/http-client/request.rst new file mode 100644 index 0000000..a8387a9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/http-client/request.rst @@ -0,0 +1,667 @@ +===================== +Using Request objects +===================== + +HTTP request messages +--------------------- + +Request objects are all about building an HTTP message. Each part of an HTTP request message can be set individually +using methods on the request object or set in bulk using the ``setUrl()`` method. Here's the format of an HTTP request +with each part of the request referencing the method used to change it:: + + PUT(a) /path(b)?query=123(c) HTTP/1.1(d) + X-Header(e): header + Content-Length(e): 4 + + data(f) + ++-------------------------+---------------------------------------------------------------------------------+ +| a. **Method** | The request method can only be set when instantiating a request | ++-------------------------+---------------------------------------------------------------------------------+ +| b. **Path** | ``$request->setPath('/path');`` | ++-------------------------+---------------------------------------------------------------------------------+ +| c. **Query** | ``$request->getQuery()->set('query', '123');`` | ++-------------------------+---------------------------------------------------------------------------------+ +| d. **Protocol version** | ``$request->setProtocolVersion('1.1');`` | ++-------------------------+---------------------------------------------------------------------------------+ +| e. **Header** | ``$request->setHeader('X-Header', 'header');`` | ++-------------------------+---------------------------------------------------------------------------------+ +| f. **Entity Body** | ``$request->setBody('data'); // Only available with PUT, POST, PATCH, DELETE`` | ++-------------------------+---------------------------------------------------------------------------------+ + +Creating requests with a client +------------------------------- + +Client objects are responsible for creating HTTP request objects. + +GET requests +~~~~~~~~~~~~ + +`GET requests `_ are the most common form of HTTP +requests. When you visit a website in your browser, the HTML of the website is downloaded using a GET request. GET +requests are idempotent requests that are typically used to download content (an entity) identified by a request URL. + +.. code-block:: php + + use Guzzle\Http\Client; + + $client = new Client(); + + // Create a request that has a query string and an X-Foo header + $request = $client->get('http://www.amazon.com?a=1', array('X-Foo' => 'Bar')); + + // Send the request and get the response + $response = $request->send(); + +You can change where the body of a response is downloaded on any request using the +``$request->setResponseBody(string|EntityBodyInterface|resource)`` method of a request. You can also set the ``save_to`` +option of a request: + +.. code-block:: php + + // Send the response body to a file + $request = $client->get('http://test.com', array(), array('save_to' => '/path/to/file')); + + // Send the response body to an fopen resource + $request = $client->get('http://test.com', array(), array('save_to' => fopen('/path/to/file', 'w'))); + +HEAD requests +~~~~~~~~~~~~~ + +`HEAD requests `_ work exactly like GET requests except +that they do not actually download the response body (entity) of the response message. HEAD requests are useful for +retrieving meta information about an entity identified by a Request-URI. + +.. code-block:: php + + $client = new Guzzle\Http\Client(); + $request = $client->head('http://www.amazon.com'); + $response = $request->send(); + echo $response->getContentLength(); + // >>> Will output the Content-Length header value + +DELETE requests +~~~~~~~~~~~~~~~ + +A `DELETE method `_ requests that the origin server +delete the resource identified by the Request-URI. + +.. code-block:: php + + $client = new Guzzle\Http\Client(); + $request = $client->delete('http://example.com'); + $response = $request->send(); + +POST requests +~~~~~~~~~~~~~ + +While `POST requests `_ can be used for a number of +reasons, POST requests are often used when submitting HTML form data to a website. POST requests can include an entity +body in the HTTP request. + +POST requests in Guzzle are sent with an ``application/x-www-form-urlencoded`` Content-Type header if POST fields are +present but no files are being sent in the POST. If files are specified in the POST request, then the Content-Type +header will become ``multipart/form-data``. + +The ``post()`` method of a client object accepts four arguments: the URL, optional headers, post fields, and an array of +request options. To send files in the POST request, prepend the ``@`` symbol to the array value (just like you would if +you were using the PHP ``curl_setopt`` function). + +Here's how to create a multipart/form-data POST request containing files and fields: + +.. code-block:: php + + $request = $client->post('http://httpbin.org/post', array(), array( + 'custom_field' => 'my custom value', + 'file_field' => '@/path/to/file.xml' + )); + + $response = $request->send(); + +.. note:: + + Remember to **always** sanitize user input when sending POST requests: + + .. code-block:: php + + // Prevent users from accessing sensitive files by sanitizing input + $_POST = array('firstname' => '@/etc/passwd'); + $request = $client->post('http://www.example.com', array(), array ( + 'firstname' => str_replace('@', '', $_POST['firstname']) + )); + +You can alternatively build up the contents of a POST request. + +.. code-block:: php + + $request = $client->post('http://httpbin.org/post') + ->setPostField('custom_field', 'my custom value') + ->addPostFile('file', '/path/to/file.xml'); + + $response = $request->send(); + +Raw POST data +^^^^^^^^^^^^^ + +POST requests can also contain raw POST data that is not related to HTML forms. + +.. code-block:: php + + $request = $client->post('http://httpbin.org/post', array(), 'this is the body'); + $response = $request->send(); + +You can set the body of POST request using the ``setBody()`` method of the +``Guzzle\Http\Message\EntityEnclosingRequest`` object. This method accepts a string, a resource returned from +``fopen``, or a ``Guzzle\Http\EntityBodyInterface`` object. + +.. code-block:: php + + $request = $client->post('http://httpbin.org/post'); + // Set the body of the POST to stream the contents of /path/to/large_body.txt + $request->setBody(fopen('/path/to/large_body.txt', 'r')); + $response = $request->send(); + +PUT requests +~~~~~~~~~~~~ + +The `PUT method `_ requests that the enclosed entity be +stored under the supplied Request-URI. PUT requests are similar to POST requests in that they both can send an entity +body in the request message. + +The body of a PUT request (any any ``Guzzle\Http\Message\EntityEnclosingRequestInterface`` object) is always stored as +a ``Guzzle\Http\Message\EntityBodyInterface`` object. This allows a great deal of flexibility when sending data to a +remote server. For example, you can stream the contents of a stream returned by fopen, stream the contents of a +callback function, or simply send a string of data. + +.. code-block:: php + + $request = $client->put('http://httpbin.org/put', array(), 'this is the body'); + $response = $request->send(); + +Just like with POST, PATH, and DELETE requests, you can set the body of a PUT request using the ``setBody()`` method. + +.. code-block:: php + + $request = $client->put('http://httpbin.org/put'); + $request->setBody(fopen('/path/to/large_body.txt', 'r')); + $response = $request->send(); + +PATCH requests +~~~~~~~~~~~~~~ + +`PATCH requests `_ are used to modify a resource. + +.. code-block:: php + + $request = $client->patch('http://httpbin.org', array(), 'this is the body'); + $response = $request->send(); + +OPTIONS requests +~~~~~~~~~~~~~~~~ + +The `OPTIONS method `_ represents a request for +information about the communication options available on the request/response chain identified by the Request-URI. + +.. code-block:: php + + $request = $client->options('http://httpbin.org'); + $response = $request->send(); + + // Check if the PUT method is supported by this resource + var_export($response->isMethodAllows('PUT')); + +Custom requests +~~~~~~~~~~~~~~~ + +You can create custom HTTP requests that use non-standard HTTP methods using the ``createRequest()`` method of a +client object. + +.. code-block:: php + + $request = $client->createRequest('COPY', 'http://example.com/foo', array( + 'Destination' => 'http://example.com/bar', + 'Overwrite' => 'T' + )); + $response = $request->send(); + +Query string parameters +----------------------- + +Query string parameters of a request are owned by a request's ``Guzzle\Http\Query`` object that is accessible by +calling ``$request->getQuery()``. The Query class extends from ``Guzzle\Common\Collection`` and allows you to set one +or more query string parameters as key value pairs. You can set a parameter on a Query object using the +``set($key, $value)`` method or access the query string object like an associative array. Any previously specified +value for a key will be overwritten when using ``set()``. Use ``add($key, $value)`` to add a value to query string +object, and in the event of a collision with an existing value at a specific key, the value will be converted to an +array that contains all of the previously set values. + +.. code-block:: php + + $request = new Guzzle\Http\Message\Request('GET', 'http://www.example.com?foo=bar&abc=123'); + + $query = $request->getQuery(); + echo "{$query}\n"; + // >>> foo=bar&abc=123 + + $query->remove('abc'); + echo "{$query}\n"; + // >>> foo=bar + + $query->set('foo', 'baz'); + echo "{$query}\n"; + // >>> foo=baz + + $query->add('foo', 'bar'); + echo "{$query}\n"; + // >>> foo%5B0%5D=baz&foo%5B1%5D=bar + +Whoah! What happened there? When ``foo=bar`` was added to the existing ``foo=baz`` query string parameter, the +aggregator associated with the Query object was used to help convert multi-value query string parameters into a string. +Let's disable URL-encoding to better see what's happening. + +.. code-block:: php + + $query->useUrlEncoding(false); + echo "{$query}\n"; + // >>> foo[0]=baz&foo[1]=bar + +.. note:: + + URL encoding can be disabled by passing false, enabled by passing true, set to use RFC 1738 by passing + ``Query::FORM_URLENCODED`` (internally uses PHP's ``urlencode`` function), or set to RFC 3986 by passing + ``Query::RFC_3986`` (this is the default and internally uses PHP's ``rawurlencode`` function). + +As you can see, the multiple values were converted into query string parameters following the default PHP convention of +adding numerically indexed square bracket suffixes to each key (``foo[0]=baz&foo[1]=bar``). The strategy used to convert +multi-value parameters into a string can be customized using the ``setAggregator()`` method of the Query class. Guzzle +ships with the following query string aggregators by default: + +1. ``Guzzle\Http\QueryAggregator\PhpAggregator``: Aggregates using PHP style brackets (e.g. ``foo[0]=baz&foo[1]=bar``) +2. ``Guzzle\Http\QueryAggregator\DuplicateAggregator``: Performs no aggregation and allows for key value pairs to be + repeated in a URL (e.g. ``foo=baz&foo=bar``) +3. ``Guzzle\Http\QueryAggregator\CommaAggregator``: Aggregates using commas (e.g. ``foo=baz,bar``) + +.. _http-message-headers: + +HTTP Message Headers +-------------------- + +HTTP message headers are case insensitive, multiple occurrences of any header can be present in an HTTP message +(whether it's valid or not), and some servers require specific casing of particular headers. Because of this, request +and response headers are stored in ``Guzzle\Http\Message\Header`` objects. The Header object can be cast as a string, +counted, or iterated to retrieve each value from the header. Casting a Header object to a string will return all of +the header values concatenated together using a glue string (typically ", "). + +A request (and response) object have several methods that allow you to retrieve and modify headers. + +* ``getHeaders()``: Get all of the headers of a message as a ``Guzzle\Http\Message\Header\HeaderCollection`` object. +* ``getHeader($header)``: Get a specific header from a message. If the header exists, you'll get a + ``Guzzle\Http\Message\Header`` object. If the header does not exist, this methods returns ``null``. +* ``hasHeader($header)``: Returns true or false based on if the message has a particular header. +* ``setHeader($header, $value)``: Set a header value and overwrite any previously set value for this header. +* ``addHeader($header, $value)``: Add a header with a particular name. If a previous value was already set by the same, + then the header will contain multiple values. +* ``removeHeader($header)``: Remove a header by name from the message. + +.. code-block:: php + + $request = new Request('GET', 'http://httpbin.com/cookies'); + // addHeader will set and append to any existing header values + $request->addHeader('Foo', 'bar'); + $request->addHeader('foo', 'baz'); + // setHeader overwrites any existing values + $request->setHeader('Test', '123'); + + // Request headers can be cast as a string + echo $request->getHeader('Foo'); + // >>> bar, baz + echo $request->getHeader('Test'); + // >>> 123 + + // You can count the number of headers of a particular case insensitive name + echo count($request->getHeader('foO')); + // >>> 2 + + // You can iterate over Header objects + foreach ($request->getHeader('foo') as $header) { + echo $header . "\n"; + } + + // You can get all of the request headers as a Guzzle\Http\Message\Header\HeaderCollection object + $headers = $request->getHeaders(); + + // Missing headers return NULL + var_export($request->getHeader('Missing')); + // >>> null + + // You can see all of the different variations of a header by calling raw() on the Header + var_export($request->getHeader('foo')->raw()); + +Setting the body of a request +----------------------------- + +Requests that can send a body (e.g. PUT, POST, DELETE, PATCH) are instances of +``Guzzle\Http\Message\EntityEnclosingRequestInterface``. Entity enclosing requests contain several methods that allow +you to specify the body to send with a request. + +Use the ``setBody()`` method of a request to set the body that will be sent with a request. This method accepts a +string, a resource returned by ``fopen()``, an array, or an instance of ``Guzzle\Http\EntityBodyInterface``. The body +will then be streamed from the underlying ``EntityBodyInterface`` object owned by the request. When setting the body +of the request, you can optionally specify a Content-Type header and whether or not to force the request to use +chunked Transfer-Encoding. + +.. code-block:: php + + $request = $client->put('/user.json'); + $request->setBody('{"foo":"baz"}', 'application/json'); + +Content-Type header +~~~~~~~~~~~~~~~~~~~ + +Guzzle will automatically add a Content-Type header to a request if the Content-Type can be guessed based on the file +extension of the payload being sent or the file extension present in the path of a request. + +.. code-block:: php + + $request = $client->put('/user.json', array(), '{"foo":"bar"}'); + // The Content-Type was guessed based on the path of the request + echo $request->getHeader('Content-Type'); + // >>> application/json + + $request = $client->put('/user.json'); + $request->setBody(fopen('/tmp/user_data.json', 'r')); + // The Content-Type was guessed based on the path of the entity body + echo $request->getHeader('Content-Type'); + // >>> application/json + +Transfer-Encoding: chunked header +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When sending HTTP requests that contain a payload, you must let the remote server know how to determine when the entire +message has been sent. This usually is done by supplying a ``Content-Length`` header that tells the origin server the +size of the body that is to be sent. In some cases, the size of the payload being sent in a request cannot be known +before initiating the transfer. In these cases (when using HTTP/1.1), you can use the ``Transfer-Encoding: chunked`` +header. + +If the Content-Length cannot be determined (i.e. using a PHP ``http://`` stream), then Guzzle will automatically add +the ``Transfer-Encoding: chunked`` header to the request. + +.. code-block:: php + + $request = $client->put('/user.json'); + $request->setBody(fopen('http://httpbin.org/get', 'r')); + + // The Content-Length could not be determined + echo $request->getHeader('Transfer-Encoding'); + // >>> chunked + +See :doc:`/http-client/entity-bodies` for more information on entity bodies. + +Expect: 100-Continue header +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Expect: 100-Continue`` header is used to help a client prevent sending a large payload to a server that will +reject the request. This allows clients to fail fast rather than waste bandwidth sending an erroneous payload. Guzzle +will automatically add the ``Expect: 100-Continue`` header to a request when the size of the payload exceeds 1MB or if +the body of the request is not seekable (this helps to prevent errors when a non-seekable body request is redirected). + +.. note:: + + If you find that your larger requests are taking too long to complete, you should first check if the + ``Expect: 100-Continue`` header is being sent with the request. Some servers do not respond well to this header, + which causes cURL to sleep for `1 second `_. + +POST fields and files +~~~~~~~~~~~~~~~~~~~~~ + +Any entity enclosing request can send POST style fields and files. This includes POST, PUT, PATCH, and DELETE requests. +Any request that has set POST fields or files will use cURL's POST message functionality. + +.. code-block:: php + + $request = $client->post('/post'); + // Set an overwrite any previously specified value + $request->setPostField('foo', 'bar'); + // Append a value to any existing values + $request->getPostFields()->add('foo', 'baz'); + // Remove a POST field by name + $request->removePostField('fizz'); + + // Add a file to upload (forces multipart/form-data) + $request->addPostFile('my_file', '/path/to/file', 'plain/text'); + // Remove a POST file by POST key name + $request->removePostFile('my_other_file'); + +.. tip:: + + Adding a large number of POST fields to a POST request is faster if you use the ``addPostFields()`` method so that + you can add and process multiple fields with a single call. Adding multiple POST files is also faster using + ``addPostFiles()``. + +Working with cookies +-------------------- + +Cookies can be modified and retrieved from a request using the following methods: + +.. code-block:: php + + $request->addCookie($name, $value); + $request->removeCookie($name); + $value = $request->getCookie($name); + $valueArray = $request->getCookies(); + +Use the :doc:`cookie plugin ` if you need to reuse cookies between requests. + +.. _request-set-response-body: + +Changing where a response is downloaded +---------------------------------------- + +When a request is sent, the body of the response will be stored in a PHP temp stream by default. You can change the +location in which the response will be downloaded using ``$request->setResponseBody($body)`` or the ``save_to`` request +option. This can be useful for downloading the contents of a URL to a specific file. + +Here's an example of using request options: + +.. code-block:: php + + $request = $this->client->get('http://example.com/large.mov', array(), array( + 'save_to' => '/tmp/large_file.mov' + )); + $request->send(); + var_export(file_exists('/tmp/large_file.mov')); + // >>> true + +Here's an example of using ``setResponseBody()``: + +.. code-block:: php + + $body = fopen('/tmp/large_file.mov', 'w'); + $request = $this->client->get('http://example.com/large.mov'); + $request->setResponseBody($body); + + // You can more easily specify the name of a file to save the contents + // of the response to by passing a string to ``setResponseBody()``. + + $request = $this->client->get('http://example.com/large.mov'); + $request->setResponseBody('/tmp/large_file.mov'); + +Custom cURL options +------------------- + +Most of the functionality implemented in the libcurl bindings has been simplified and abstracted by Guzzle. Developers +who need access to `cURL specific functionality `_ can still add cURL handle +specific behavior to Guzzle HTTP requests by modifying the cURL options collection of a request: + +.. code-block:: php + + $request->getCurlOptions()->set(CURLOPT_LOW_SPEED_LIMIT, 200); + +Other special options that can be set in the ``curl.options`` array include: + ++-------------------------+---------------------------------------------------------------------------------+ +| debug | Adds verbose cURL output to a temp stream owned by the cURL handle object | ++-------------------------+---------------------------------------------------------------------------------+ +| progress | Instructs cURL to emit events when IO events occur. This allows you to be | +| | notified when bytes are transferred over the wire by subscribing to a request's | +| | ``curl.callback.read``, ``curl.callback.write``, and ``curl.callback.progress`` | +| | events. | ++-------------------------+---------------------------------------------------------------------------------+ + +Request options +--------------- + +Requests options can be specified when creating a request or in the ``request.options`` parameter of a client. These +options can control various aspects of a request including: headers to send, query string data, where the response +should be downloaded, proxies, auth, etc. + +.. code-block:: php + + $request = $client->get($url, $headers, array('proxy' => 'http://proxy.com')); + +See :ref:`Request options ` for more information. + +Working with errors +------------------- + +HTTP errors +~~~~~~~~~~~ + +Requests that receive a 4xx or 5xx response will throw a ``Guzzle\Http\Exception\BadResponseException``. More +specifically, 4xx errors throw a ``Guzzle\Http\Exception\ClientErrorResponseException``, and 5xx errors throw a +``Guzzle\Http\Exception\ServerErrorResponseException``. You can catch the specific exceptions or just catch the +BadResponseException to deal with either type of error. Here's an example of catching a generic BadResponseException: + +.. code-block:: php + + try { + $response = $client->get('/not_found.xml')->send(); + } catch (Guzzle\Http\Exception\BadResponseException $e) { + echo 'Uh oh! ' . $e->getMessage(); + echo 'HTTP request URL: ' . $e->getRequest()->getUrl() . "\n"; + echo 'HTTP request: ' . $e->getRequest() . "\n"; + echo 'HTTP response status: ' . $e->getResponse()->getStatusCode() . "\n"; + echo 'HTTP response: ' . $e->getResponse() . "\n"; + } + +Throwing an exception when a 4xx or 5xx response is encountered is the default behavior of Guzzle requests. This +behavior can be overridden by adding an event listener with a higher priority than -255 that stops event propagation. +You can subscribe to ``request.error`` to receive notifications any time an unsuccessful response is received. + +You can change the response that will be associated with the request by calling ``setResponse()`` on the +``$event['request']`` object passed into your listener, or by changing the ``$event['response']`` value of the +``Guzzle\Common\Event`` object that is passed to your listener. Transparently changing the response associated with a +request by modifying the event allows you to retry failed requests without complicating the code that uses the client. +This might be useful for sending requests to a web service that has expiring auth tokens. When a response shows that +your token has expired, you can get a new token, retry the request with the new token, and return the successful +response to the user. + +Here's an example of retrying a request using updated authorization credentials when a 401 response is received, +overriding the response of the original request with the new response, and still allowing the default exception +behavior to be called when other non-200 response status codes are encountered: + +.. code-block:: php + + // Add custom error handling to any request created by this client + $client->getEventDispatcher()->addListener('request.error', function(Event $event) { + + if ($event['response']->getStatusCode() == 401) { + + $newRequest = $event['request']->clone(); + $newRequest->setHeader('X-Auth-Header', MyApplication::getNewAuthToken()); + $newResponse = $newRequest->send(); + + // Set the response object of the request without firing more events + $event['response'] = $newResponse; + + // You can also change the response and fire the normal chain of + // events by calling $event['request']->setResponse($newResponse); + + // Stop other events from firing when you override 401 responses + $event->stopPropagation(); + } + + }); + +cURL errors +~~~~~~~~~~~ + +Connection problems and cURL specific errors can also occur when transferring requests using Guzzle. When Guzzle +encounters cURL specific errors while transferring a single request, a ``Guzzle\Http\Exception\CurlException`` is +thrown with an informative error message and access to the cURL error message. + +A ``Guzzle\Http\Exception\MultiTransferException`` exception is thrown when a cURL specific error occurs while +transferring multiple requests in parallel. You can then iterate over all of the exceptions encountered during the +transfer. + +Plugins and events +------------------ + +Guzzle request objects expose various events that allow you to hook in custom logic. A request object owns a +``Symfony\Component\EventDispatcher\EventDispatcher`` object that can be accessed by calling +``$request->getEventDispatcher()``. You can use the event dispatcher to add listeners (a simple callback function) or +event subscribers (classes that listen to specific events of a dispatcher). You can add event subscribers to a request +directly by just calling ``$request->addSubscriber($mySubscriber);``. + +.. _request-events: + +Events emitted from a request +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``Guzzle\Http\Message\Request`` and ``Guzzle\Http\Message\EntityEnclosingRequest`` object emit the following events: + ++------------------------------+--------------------------------------------+------------------------------------------+ +| Event name | Description | Event data | ++==============================+============================================+==========================================+ +| request.before_send | About to send request | * request: Request to be sent | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.sent | Sent the request | * request: Request that was sent | +| | | * response: Received response | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.complete | Completed a full HTTP transaction | * request: Request that was sent | +| | | * response: Received response | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.success | Completed a successful request | * request: Request that was sent | +| | | * response: Received response | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.error | Completed an unsuccessful request | * request: Request that was sent | +| | | * response: Received response | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.exception | An unsuccessful response was | * request: Request | +| | received. | * response: Received response | +| | | * exception: BadResponseException | ++------------------------------+--------------------------------------------+------------------------------------------+ +| request.receive.status_line | Received the start of a response | * line: Full response start line | +| | | * status_code: Status code | +| | | * reason_phrase: Reason phrase | +| | | * previous_response: (e.g. redirect) | ++------------------------------+--------------------------------------------+------------------------------------------+ +| curl.callback.progress | cURL progress event (only dispatched when | * handle: CurlHandle | +| | ``emit_io`` is set on a request's curl | * download_size: Total download size | +| | options) | * downloaded: Bytes downloaded | +| | | * upload_size: Total upload bytes | +| | | * uploaded: Bytes uploaded | ++------------------------------+--------------------------------------------+------------------------------------------+ +| curl.callback.write | cURL event called when data is written to | * request: Request | +| | an outgoing stream | * write: Data being written | ++------------------------------+--------------------------------------------+------------------------------------------+ +| curl.callback.read | cURL event called when data is written to | * request: Request | +| | an incoming stream | * read: Data being read | ++------------------------------+--------------------------------------------+------------------------------------------+ + +Creating a request event listener +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here's an example that listens to the ``request.complete`` event of a request and prints the request and response. + +.. code-block:: php + + use Guzzle\Common\Event; + + $request = $client->get('http://www.google.com'); + + // Echo out the response that was received + $request->getEventDispatcher()->addListener('request.complete', function (Event $e) { + echo $e['request'] . "\n\n"; + echo $e['response']; + }); diff --git a/source/vendor/guzzle/guzzle/docs/http-client/response.rst b/source/vendor/guzzle/guzzle/docs/http-client/response.rst new file mode 100644 index 0000000..ba48731 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/http-client/response.rst @@ -0,0 +1,141 @@ +====================== +Using Response objects +====================== + +Sending a request will return a ``Guzzle\Http\Message\Response`` object. You can view the raw HTTP response message by +casting the Response object to a string. Casting the response to a string will return the entity body of the response +as a string too, so this might be an expensive operation if the entity body is stored in a file or network stream. If +you only want to see the response headers, you can call ``getRawHeaders()``. + +Response status line +-------------------- + +The different parts of a response's `status line `_ +(the first line of the response HTTP message) are easily retrievable. + +.. code-block:: php + + $response = $client->get('http://www.amazon.com')->send(); + + echo $response->getStatusCode(); // >>> 200 + echo $response->getReasonPhrase(); // >>> OK + echo $response->getProtocol(); // >>> HTTP + echo $response->getProtocolVersion(); // >>> 1.1 + +You can determine the type of the response using several helper methods: + +.. code-block:: php + + $response->isSuccessful(); // true + $response->isInformational(); + $response->isRedirect(); + $response->isClientError(); + $response->isServerError(); + +Response headers +---------------- + +The Response object contains helper methods for retrieving common response headers. These helper methods normalize the +variations of HTTP response headers. + +.. code-block:: php + + $response->getCacheControl(); + $response->getContentType(); + $response->getContentLength(); + $response->getContentEncoding(); + $response->getContentMd5(); + $response->getEtag(); + // etc... There are methods for every known response header + +You can interact with the Response headers using the same exact methods used to interact with Request headers. See +:ref:`http-message-headers` for more information. + +.. code-block:: php + + echo $response->getHeader('Content-Type'); + echo $response->getHeader('Content-Length'); + echo $response->getHeaders()['Content-Type']; // PHP 5.4 + +Response body +------------- + +The entity body object of a response can be retrieved by calling ``$response->getBody()``. The response EntityBody can +be cast to a string, or you can pass ``true`` to this method to retrieve the body as a string. + +.. code-block:: php + + $request = $client->get('http://www.amazon.com'); + $response = $request->send(); + echo $response->getBody(); + +See :doc:`/http-client/entity-bodies` for more information on entity bodies. + +JSON Responses +~~~~~~~~~~~~~~ + +You can easily parse and use a JSON response as an array using the ``json()`` method of a response. This method will +always return an array if the response is valid JSON or if the response body is empty. You will get an exception if you +call this method and the response is not valid JSON. + +.. code-block:: php + + $data = $response->json(); + echo gettype($data); + // >>> array + +XML Responses +~~~~~~~~~~~~~ + +You can easily parse and use a XML response as SimpleXMLElement object using the ``xml()`` method of a response. This +method will always return a SimpleXMLElement object if the response is valid XML or if the response body is empty. You +will get an exception if you call this method and the response is not valid XML. + +.. code-block:: php + + $xml = $response->xml(); + echo $xml->foo; + // >>> Bar! + +Streaming responses +------------------- + +Some web services provide streaming APIs that allow a client to keep a HTTP request open for an extended period of +time while polling and reading. Guzzle provides a simple way to convert HTTP request messages into +``Guzzle\Stream\Stream`` objects so that you can send the initial headers of a request, read the response headers, and +pull in the response body manually as needed. + +Here's an example using the Twitter Streaming API to track the keyword "bieber": + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Stream\PhpStreamRequestFactory; + + $client = new Client('https://stream.twitter.com/1'); + + $request = $client->post('statuses/filter.json', null, array( + 'track' => 'bieber' + )); + + $request->setAuth('myusername', 'mypassword'); + + $factory = new PhpStreamRequestFactory(); + $stream = $factory->fromRequest($request); + + // Read until the stream is closed + while (!$stream->feof()) { + // Read a line from the stream + $line = $stream->readLine(); + // JSON decode the line of data + $data = json_decode($line, true); + } + +You can use the ``stream`` request option when using a static client to more easily create a streaming response. + +.. code-block:: php + + $stream = Guzzle::get('http://guzzlephp.org', array('stream' => true)); + while (!$stream->feof()) { + echo $stream->readLine(); + } diff --git a/source/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst b/source/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst new file mode 100644 index 0000000..c18ac3e --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/http-client/uri-templates.rst @@ -0,0 +1,52 @@ +============= +URI templates +============= + +The ``$uri`` passed to one of the client's request creational methods or the base URL of a client can utilize URI +templates. Guzzle supports the entire `URI templates RFC `_. URI templates add a +special syntax to URIs that replace template place holders with user defined variables. + +Every request created by a Guzzle HTTP client passes through a URI template so that URI template expressions are +automatically expanded: + +.. code-block:: php + + $client = new Guzzle\Http\Client('https://example.com/', array('a' => 'hi')); + $request = $client->get('/{a}'); + +Because of URI template expansion, the URL of the above request will become ``https://example.com/hi``. Notice that +the template was expanded using configuration variables of the client. You can pass in custom URI template variables +by passing the URI of your request as an array where the first index of the array is the URI template and the second +index of the array are template variables that are merged into the client's configuration variables. + +.. code-block:: php + + $request = $client->get(array('/test{?a,b}', array('b' => 'there'))); + +The URL for this request will become ``https://test.com?a=hi&b=there``. URI templates aren't limited to just simple +variable replacements; URI templates can provide an enormous amount of flexibility when creating request URIs. + +.. code-block:: php + + $request = $client->get(array('http://example.com{+path}{/segments*}{?query,data*}', array( + 'path' => '/foo/bar', + 'segments' => array('one', 'two'), + 'query' => 'test', + 'data' => array( + 'more' => 'value' + ) + ))); + +The resulting URL would become ``http://example.com/foo/bar/one/two?query=test&more=value``. + +By default, URI template expressions are enclosed in an opening and closing brace (e.g. ``{var}``). If you are working +with a web service that actually uses braces (e.g. Solr), then you can specify a custom regular expression to use to +match URI template expressions. + +.. code-block:: php + + $client->getUriTemplate()->setRegex('/\<\$(.+)\>/'); + $client->get('/<$a>'); + +You can learn about all of the different features of URI templates by reading the +`URI templates RFC `_. diff --git a/source/vendor/guzzle/guzzle/docs/index.rst b/source/vendor/guzzle/guzzle/docs/index.rst new file mode 100644 index 0000000..f76f3bb --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/index.rst @@ -0,0 +1,5 @@ +.. title:: Guzzle | PHP HTTP client and framework for consuming RESTful web services +.. toctree:: + :hidden: + + docs.rst diff --git a/source/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst b/source/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst new file mode 100644 index 0000000..a5c7fd3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/iterators/guzzle-iterators.rst @@ -0,0 +1,97 @@ +================ +Guzzle iterators +================ + +Guzzle provides several SPL iterators that can be used with other SPL iterators, including Guzzle resource iterators. +Guzzle's ``guzzle/iterator`` component can also be used independently of the rest of Guzzle through Packagist and +Composer: https://packagist.org/packages/guzzle/iterator + +ChunkedIterator +--------------- + +Pulls out multiple values from an inner iterator and yields and array of values for each outer iteration -- essentially +pulling out chunks of values from the inner iterator. + +.. code-block:: php + + use Guzzle\Iterator\ChunkedIterator; + + $inner = new ArrayIterator(range(0, 8)); + $chunkedIterator = new ChunkedIterator($inner, 2); + + foreach ($chunkedIterator as $chunk) { + echo implode(', ', $chunk) . "\n"; + } + + // >>> 0, 1 + // >>> 2, 3 + // >>> 4, 5 + // >>> 6, 7 + // >>> 8 + +FilterIterator +-------------- + +This iterator is used to filter values out of the inner iterator. This iterator can be used when PHP 5.4's +CallbackFilterIterator is not available. + +.. code-block:: php + + use Guzzle\Iterator\FilterIterator; + + $inner = new ArrayIterator(range(1, 10)); + $filterIterator = new FilterIterator($inner, function ($value) { + return $value % 2; + }); + + foreach ($filterIterator as $value) { + echo $value . "\n"; + } + + // >>> 2 + // >>> 4 + // >>> 6 + // >>> 8 + // >>> 10 + +MapIterator +----------- + +This iterator modifies the values of the inner iterator before yielding. + +.. code-block:: php + + use Guzzle\Iterator\MapIterator; + + $inner = new ArrayIterator(range(0, 3)); + + $mapIterator = new MapIterator($inner, function ($value) { + return $value * 10; + }); + + foreach ($mapIterator as $value) { + echo $value . "\n"; + } + + // >>> 0 + // >>> 10 + // >>> 20 + // >>> 30 + +MethodProxyIterator +------------------- + +This decorator is useful when you need to expose a specific method from an inner iterator that might be wrapper +by one or more iterator decorators. This decorator proxies missing method calls to each inner iterator until one +of the inner iterators can fulfill the call. + +.. code-block:: php + + use Guzzle\Iterator\MethodProxyIterator; + + $inner = new \ArrayIterator(); + $proxy = new MethodProxyIterator($inner); + + // Proxy method calls to the ArrayIterator + $proxy->append('a'); + $proxy->append('b'); diff --git a/source/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst b/source/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst new file mode 100644 index 0000000..ce0bee5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/iterators/resource-iterators.rst @@ -0,0 +1,149 @@ +================== +Resource iterators +================== + +Web services often implement pagination in their responses which requires the end-user to issue a series of consecutive +requests in order to fetch all of the data they asked for. Users of your web service client should not be responsible +for implementing the logic involved in iterating through pages of results. Guzzle provides a simple resource iterator +foundation to make it easier on web service client developers to offer a useful abstraction layer. + +Getting an iterator from a client +--------------------------------- + + ResourceIteratorInterface Guzzle\Service\Client::getIterator($command [, array $commandOptions, array $iteratorOptions ]) + +The ``getIterator`` method of a ``Guzzle\Service\ClientInterface`` object provides a convenient interface for +instantiating a resource iterator for a specific command. This method implicitly uses a +``Guzzle\Service\Resource\ResourceIteratorFactoryInterface`` object to create resource iterators. Pass an +instantiated command object or the name of a command in the first argument. When passing the name of a command, the +command factory of the client will create the command by name using the ``$commandOptions`` array. The third argument +may be used to pass an array of options to the constructor of the instantiated ``ResourceIteratorInterface`` object. + +.. code-block:: php + + $iterator = $client->getIterator('get_users'); + + foreach ($iterator as $user) { + echo $user['name'] . ' age ' . $user['age'] . PHP_EOL; + } + +The above code sample might execute a single request or a thousand requests. As a consumer of a web service, I don't +care. I just want to iterate over all of the users. + +Iterator options +~~~~~~~~~~~~~~~~ + +The two universal options that iterators should support are ``limit`` and ``page_size``. Using the ``limit`` option +tells the resource iterator to attempt to limit the total number of iterated resources to a specific amount. Keep in +mind that this is not always possible due to limitations that may be inherent to a web service. The ``page_size`` +option is used to tell a resource iterator how many resources to request per page of results. Much like the ``limit`` +option, you can not rely on getting back exactly the number of resources your specify in the ``page_size`` option. + +.. note:: + + The ``limit`` and ``page_size`` options can also be specified on an iterator using the ``setLimit($limit)`` and + ``setPageSize($pageSize)`` methods. + +Resolving iterator class names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default resource iterator factory of a client object expects that your iterators are stored under the ``Model`` +folder of your client and that an iterator is names after the CamelCase name of a command followed by the word +"Iterator". For example, if you wanted to create an iterator for the ``get_users`` command, then your iterator class +would be ``Model\GetUsersIterator`` and would be stored in ``Model/GetUsersIterator.php``. + +Creating an iterator +-------------------- + +While not required, resource iterators in Guzzle typically iterate using a ``Guzzle\Service\Command\CommandInterface`` +object. ``Guzzle\Service\Resource\ResourceIterator``, the default iterator implementation that you should extend, +accepts a command object and array of iterator options in its constructor. The command object passed to the resource +iterator is expected to be ready to execute and not previously executed. The resource iterator keeps a reference of +this command and clones the original command each time a subsequent request needs to be made to fetch more data. + +Implement the sendRequest method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The most important thing (and usually the only thing) you need to do when creating a resource iterator is to implement +the ``sendRequest()`` method of the resource iterator. The ``sendRequest()`` method is called when you begin +iterating or if there are no resources left to iterate and it you expect to retrieve more resources by making a +subsequent request. The ``$this->command`` property of the resource iterator is updated with a cloned copy of the +original command object passed into the constructor of the iterator. Use this command object to issue your subsequent +requests. + +The ``sendRequest()`` method must return an array of the resources you retrieved from making the subsequent call. +Returning an empty array will stop the iteration. If you suspect that your web service client will occasionally return +an empty result set but still requires further iteration, then you must implement a sort of loop in your +``sendRequest()`` method that will continue to issue subsequent requests until your reach the end of the paginated +result set or until additional resources are retrieved from the web service. + +Update the nextToken property +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Beyond fetching more results, the ``sendRequest()`` method is responsible for updating the ``$this->nextToken`` +property of the iterator. Setting this property to anything other than null tells the iterator that issuing a +subsequent request using the nextToken value will probably return more results. You must continually update this +value in your ``sendRequest()`` method as each response is received from the web service. + +Example iterator +---------------- + +Let's say you want to implement a resource iterator for the ``get_users`` command of your web service. The +``get_users`` command receives a response that contains a list of users, and if there are more pages of results to +retrieve, returns a value called ``next_user``. This return value is known as the **next token** and should be used to +issue subsequent requests. + +Assume the response to a ``get_users`` command returns JSON data that looks like this: + +.. code-block:: javascript + + { + "users": [ + { "name": "Craig Johnson", "age": 10 }, + { "name": "Tom Barker", "age": 20 }, + { "name": "Bob Mitchell", "age": 74 } + ], + "next_user": "Michael Dowling" + } + +Assume that because there is a ``next_user`` value, there will be more users if a subsequent request is issued. If the +``next_user`` value is missing or null, then we know there are no more results to fetch. Let's implement a resource +iterator for this command. + +.. code-block:: php + + namespace MyService\Model; + + use Guzzle\Service\Resource\ResourceIterator; + + /** + * Iterate over a get_users command + */ + class GetUsersIterator extends ResourceIterator + { + protected function sendRequest() + { + // If a next token is set, then add it to the command + if ($this->nextToken) { + $this->command->set('next_user', $this->nextToken); + } + + // Execute the command and parse the result + $result = $this->command->execute(); + + // Parse the next token + $this->nextToken = isset($result['next_user']) ? $result['next_user'] : false; + + return $result['users']; + } + } + +As you can see, it's pretty simple to implement an iterator. There are a few things that you should notice from this +example: + +1. You do not need to create a new command in the ``sendRequest()`` method. A new command object is cloned from the + original command passed into the constructor of the iterator before the ``sendRequest()`` method is called. + Remember that the resource iterator expects a command that has not been executed. +2. When the ``sendRequest()`` method is first called, you will not have a ``$this->nextToken`` value, so always check + before setting it on a command. Notice that the next token is being updated each time a request is sent. +3. After fetching more resources from the service, always return an array of resources. diff --git a/source/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst new file mode 100644 index 0000000..9bd8f42 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/async-plugin.rst @@ -0,0 +1,18 @@ +============ +Async plugin +============ + +The AsyncPlugin allows you to send requests that do not wait on a response. This is handled through cURL by utilizing +the progress event. When a request has sent all of its data to the remote server, Guzzle adds a 1ms timeout on the +request and instructs cURL to not download the body of the response. The async plugin then catches the exception and +adds a mock response to the request, along with an X-Guzzle-Async header to let you know that the response was not +fully downloaded. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Async\AsyncPlugin; + + $client = new Client('http://www.example.com'); + $client->addSubscriber(new AsyncPlugin()); + $response = $client->get()->send(); diff --git a/source/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst new file mode 100644 index 0000000..5a76941 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/backoff-plugin.rst @@ -0,0 +1,22 @@ +==================== +Backoff retry plugin +==================== + +The ``Guzzle\Plugin\Backoff\BackoffPlugin`` automatically retries failed HTTP requests using custom backoff strategies: + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Backoff\BackoffPlugin; + + $client = new Client('http://www.test.com/'); + // Use a static factory method to get a backoff plugin using the exponential backoff strategy + $backoffPlugin = BackoffPlugin::getExponentialBackoff(); + + // Add the backoff plugin to the client object + $client->addSubscriber($backoffPlugin); + +The BackoffPlugin's constructor accepts a ``Guzzle\Plugin\Backoff\BackoffStrategyInterface`` object that is used to +determine when a retry should be issued and how long to delay between retries. The above code example shows how to +attach a BackoffPlugin to a client that is pre-configured to retry failed 500 and 503 responses using truncated +exponential backoff (emulating the behavior of Guzzle 2's ExponentialBackoffPlugin). diff --git a/source/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst new file mode 100644 index 0000000..d2fd5df --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/cache-plugin.rst @@ -0,0 +1,169 @@ +================= +HTTP Cache plugin +================= + +Guzzle can leverage HTTP's caching specifications using the ``Guzzle\Plugin\Cache\CachePlugin``. The CachePlugin +provides a private transparent proxy cache that caches HTTP responses. The caching logic, based on +`RFC 2616 `_, uses HTTP headers to control caching behavior, +cache lifetime, and supports Vary, ETag, and Last-Modified based revalidation: + +.. code-block:: php + + use Guzzle\Http\Client; + use Doctrine\Common\Cache\FilesystemCache; + use Guzzle\Cache\DoctrineCacheAdapter; + use Guzzle\Plugin\Cache\CachePlugin; + use Guzzle\Plugin\Cache\DefaultCacheStorage; + + $client = new Client('http://www.test.com/'); + + $cachePlugin = new CachePlugin(array( + 'storage' => new DefaultCacheStorage( + new DoctrineCacheAdapter( + new FilesystemCache('/path/to/cache/files') + ) + ) + )); + + // Add the cache plugin to the client object + $client->addSubscriber($cachePlugin); + $client->get('http://www.wikipedia.org/')->send(); + + // The next request will revalidate against the origin server to see if it + // has been modified. If a 304 response is received the response will be + // served from cache + $client->get('http://www.wikipedia.org/')->send(); + +The cache plugin intercepts GET and HEAD requests before they are actually transferred to the origin server. The cache +plugin then generates a hash key based on the request method and URL, and checks to see if a response exists in the cache. If +a response exists in the cache, the cache adapter then checks to make sure that the caching rules associated with the response +satisfy the request, and ensures that response still fresh. If the response is acceptable for the request any required +revalidation, then the cached response is served instead of contacting the origin server. + +Vary +---- + +Cache keys are derived from a request method and a request URL. Multiple responses can map to the same cache key and +stored in Guzzle's underlying cache storage object. You should use the ``Vary`` HTTP header to tell the cache storage +object that the cache response must have been cached for a request that matches the headers specified in the Vary header +of the request. This allows you to have specific cache entries for the same request URL but variations in a request's +headers determine which cache entry is served. Please see the http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44 +for more information. + +Cache options +------------- + +There are several options you can add to requests or clients to modify the behavior of the cache plugin. + +Override cache TTL +~~~~~~~~~~~~~~~~~~ + +You can override the number of seconds a cacheable response is stored in the cache by setting the +``cache.override_ttl`` parameter on the params object of a request: + +.. code-block:: php + + // If the response to the request is cacheable, then the response will be cached for 100 seconds + $request->getParams()->set('cache.override_ttl', 100); + +If a response doesn't specify any freshness policy, it will be kept in cache for 3600 seconds by default. + +Custom caching decision +~~~~~~~~~~~~~~~~~~~~~~~ + +If the service you are interacting with does not return caching headers or returns responses that are normally +something that would not be cached, you can set a custom ``can_cache`` object on the constructor of the CachePlugin +and provide a ``Guzzle\Plugin\Cache\CanCacheInterface`` object. You can use the +``Guzzle\Plugin\Cache\CallbackCanCacheStrategy`` to easily make a caching decision based on an HTTP request and +response. + +Revalidation options +~~~~~~~~~~~~~~~~~~~~ + +You can change the revalidation behavior of a request using the ``cache.revalidate`` parameter. Setting this +parameter to ``never`` will ensure that a revalidation request is never sent, and the response is always served from +the origin server. Setting this parameter to ``skip`` will never revalidate and uses the response stored in the cache. + +Normalizing requests for caching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``cache.key_filter`` parameter if you wish to strip certain query string parameters from your +request before creating a unique hash for the request. This parameter can be useful if your requests have query +string values that cause each request URL to be unique (thus preventing a cache hit). The ``cache.key_filter`` +format is simply a comma separated list of query string values to remove from the URL when creating a cache key. +For example, here we are saying that the ``a`` and ``q`` query string variables should be ignored when generating a +cache key for the request: + +.. code-block:: php + + $request->getParams()->set('cache.key_filter', 'a, q'); + +Other options +~~~~~~~~~~~~~ + +There are many other options available to the CachePlugin that can meet almost any caching requirement, including +custom revalidation implementations, custom cache key generators, custom caching decision strategies, and custom +cache storage objects. Take a look the constructor of ``Guzzle\Plugin\Cache\CachePlugin`` for more information. + +Setting Client-wide cache settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can specify cache settings for every request created by a client by adding cache settings to the configuration +options of a client. + +.. code-block:: php + + $client = new Guzzle\Http\Client('http://www.test.com', array( + 'request.params' => array( + 'cache.override_ttl' => 3600, + 'params.cache.revalidate' => 'never' + ) + )); + + echo $client->get('/')->getParams()->get('cache.override_ttl'); + // >>> 3600 + + echo $client->get('/')->getParams()->get('cache.revalidate'); + // >>> never + +Cache revalidation +------------------ + +If the cache plugin determines that a response to a GET request needs revalidation, a conditional GET is transferred +to the origin server. If the origin server returns a 304 response, then a response containing the merged headers of +the cached response with the new response and the entity body of the cached response is returned. Custom revalidation +strategies can be injected into a CachePlugin if needed. + +Cache adapters +-------------- + +Guzzle doesn't try to reinvent the wheel when it comes to caching or logging. Plenty of other frameworks have +excellent solutions in place that you are probably already using in your applications. Guzzle uses adapters for +caching and logging. The cache plugin requires a cache adapter so that is can store responses in a cache. Guzzle +currently supports cache adapters for `Doctrine 2.0 `_ and the +`Zend Framework `_. + +Doctrine cache adapter +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + use Doctrine\Common\Cache\ArrayCache; + use Guzzle\Cache\DoctrineCacheAdapter; + use Guzzle\Plugin\Cache\CachePlugin; + + $backend = new ArrayCache(); + $adapter = new DoctrineCacheAdapter($backend); + $cache = new CachePlugin($adapter); + +Zend Framework cache adapter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + use Guzzle\Cache\ZendCacheAdapter; + use Zend\Cache\Backend\TestBackend; + + $backend = new TestBackend(); + $adapter = new ZendCacheAdapter($backend); + $cache = new CachePlugin($adapter); diff --git a/source/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst new file mode 100644 index 0000000..a6cc7d9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/cookie-plugin.rst @@ -0,0 +1,33 @@ +============= +Cookie plugin +============= + +Some web services require a Cookie in order to maintain a session. The ``Guzzle\Plugin\Cookie\CookiePlugin`` will add +cookies to requests and parse cookies from responses using a CookieJar object: + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Cookie\CookiePlugin; + use Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar; + + $cookiePlugin = new CookiePlugin(new ArrayCookieJar()); + + // Add the cookie plugin to a client + $client = new Client('http://www.test.com/'); + $client->addSubscriber($cookiePlugin); + + // Send the request with no cookies and parse the returned cookies + $client->get('http://www.yahoo.com/')->send(); + + // Send the request again, noticing that cookies are being sent + $request = $client->get('http://www.yahoo.com/'); + $request->send(); + + echo $request; + +You can disable cookies per-request by setting the ``cookies.disable`` value to true on a request's params object. + +.. code-block:: php + + $request->getParams()->set('cookies.disable', true); diff --git a/source/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst b/source/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst new file mode 100644 index 0000000..0870155 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/creating-plugins.rst @@ -0,0 +1,93 @@ +================ +Creating plugins +================ + +.. highlight:: php + +Guzzle is extremely extensible because of the behavioral modifications that can be added to requests, clients, and +commands using an event system. Before and after the majority of actions are taken in the library, an event is emitted +with the name of the event and context surrounding the event. Observers can subscribe to a subject and modify the +subject based on the events received. Guzzle's event system utilizes the Symfony2 EventDispatcher and is the backbone +of its plugin architecture. + +Overview +-------- + +Plugins must implement the ``Symfony\Component\EventDispatcher\EventSubscriberInterface`` interface. The +``EventSubscriberInterface`` requires that your class implements a static method, ``getSubscribedEvents()``, that +returns an associative array mapping events to methods on the object. See the +`Symfony2 documentation `_ for more information. + +Plugins can be attached to any subject, or object in Guzzle that implements that +``Guzzle\Common\HasDispatcherInterface``. + +Subscribing to a subject +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can subscribe an instantiated observer to an event by calling ``addSubscriber`` on a subject. + +.. code-block:: php + + $testPlugin = new TestPlugin(); + $client->addSubscriber($testPlugin); + +You can also subscribe to only specific events using a closure:: + + $client->getEventDispatcher()->addListener('request.create', function(Event $event) { + echo $event->getName(); + echo $event['request']; + }); + +``Guzzle\Common\Event`` objects are passed to notified functions. The Event object has a ``getName()`` method which +return the name of the emitted event and may contain contextual information that can be accessed like an array. + +Knowing what events to listen to +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Any class that implements the ``Guzzle\Common\HasDispatcherInterface`` must implement a static method, +``getAllEvents()``, that returns an array of the events that are emitted from the object. You can browse the source +to see each event, or you can call the static method directly in your code to get a list of available events. + +Event hooks +----------- + +* :ref:`client-events` +* :ref:`service-client-events` +* :ref:`request-events` +* ``Guzzle\Http\Curl\CurlMulti``: +* :ref:`service-builder-events` + +Examples of the event system +---------------------------- + +Simple Echo plugin +~~~~~~~~~~~~~~~~~~ + +This simple plugin prints a string containing the request that is about to be sent by listening to the +``request.before_send`` event:: + + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + + class EchoPlugin implements EventSubscriberInterface + { + public static function getSubscribedEvents() + { + return array('request.before_send' => 'onBeforeSend'); + } + + public function onBeforeSend(Guzzle\Common\Event $event) + { + echo 'About to send a request: ' . $event['request'] . "\n"; + } + } + + $client = new Guzzle\Service\Client('http://www.test.com/'); + + // Create the plugin and add it as an event subscriber + $plugin = new EchoPlugin(); + $client->addSubscriber($plugin); + + // Send a request and notice that the request is printed to the screen + $client->get('/')->send(); + +Running the above code will print a string containing the HTTP request that is about to be sent. diff --git a/source/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst new file mode 100644 index 0000000..66d4a01 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/curl-auth-plugin.rst @@ -0,0 +1,32 @@ +========================== +cURL authentication plugin +========================== + +.. warning:: + + The CurlAuthPlugin is deprecated. You should use the `auth` parameter of a client to add authorization headers to + every request created by a client. + + .. code-block:: php + + $client->setDefaultOption('auth', array('username', 'password', 'Basic|Digest|NTLM|Any')); + +If your web service client requires basic authorization, then you can use the CurlAuthPlugin to easily add an +Authorization header to each request sent by the client. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\CurlAuth\CurlAuthPlugin; + + $client = new Client('http://www.test.com/'); + + // Add the auth plugin to the client object + $authPlugin = new CurlAuthPlugin('username', 'password'); + $client->addSubscriber($authPlugin); + + $response = $client->get('projects/1/people')->send(); + $xml = new SimpleXMLElement($response->getBody(true)); + foreach ($xml->person as $person) { + echo $person->email . "\n"; + } diff --git a/source/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst new file mode 100644 index 0000000..b96befe --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/history-plugin.rst @@ -0,0 +1,24 @@ +============== +History plugin +============== + +The history plugin tracks all of the requests and responses sent through a request or client. This plugin can be +useful for crawling or unit testing. By default, the history plugin stores up to 10 requests and responses. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\History\HistoryPlugin; + + $client = new Client('http://www.test.com/'); + + // Add the history plugin to the client object + $history = new HistoryPlugin(); + $history->setLimit(5); + $client->addSubscriber($history); + + $client->get('http://www.yahoo.com/')->send(); + + echo $history->getLastRequest(); + echo $history->getLastResponse(); + echo count($history); diff --git a/source/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst new file mode 100644 index 0000000..3e2b229 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/log-plugin.rst @@ -0,0 +1,69 @@ +========== +Log plugin +========== + +Use the ``Guzzle\Plugin\Log\LogPlugin`` to view all data sent over the wire, including entity bodies and redirects. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Log\Zf1LogAdapter; + use Guzzle\Plugin\Log\LogPlugin; + use Guzzle\Log\MessageFormatter; + + $client = new Client('http://www.test.com/'); + + $adapter = new Zf1LogAdapter( + new \Zend_Log(new \Zend_Log_Writer_Stream('php://output')) + ); + $logPlugin = new LogPlugin($adapter, MessageFormatter::DEBUG_FORMAT); + + // Attach the plugin to the client, which will in turn be attached to all + // requests generated by the client + $client->addSubscriber($logPlugin); + + $response = $client->get('http://google.com')->send(); + +The code sample above wraps a ``Zend_Log`` object using a ``Guzzle\Log\Zf1LogAdapter``. After attaching the plugin to +the client, all data sent over the wire will be logged to stdout. + +The first argument of the LogPlugin's constructor accepts a ``Guzzle\Log\LogAdapterInterface`` object. This object is +an adapter that allows you to use the logging capabilities of your favorite log implementation. The second argument of +the constructor accepts a ``Guzzle\Log\MessageFormatter`` or a log messaged format string. The format string uses +variable substitution and allows you to define the log data that is important to your application. The different +variables that can be injected are as follows: + +================== ==================================================================================== +Variable Substitution +================== ==================================================================================== +{request} Full HTTP request message +{response} Full HTTP response message +{ts} Timestamp +{host} Host of the request +{method} Method of the request +{url} URL of the request +{host} Host of the request +{protocol} Request protocol +{version} Protocol version +{resource} Resource of the request (path + query + fragment) +{port} Port of the request +{hostname} Hostname of the machine that sent the request +{code} Status code of the response (if available) +{phrase} Reason phrase of the response (if available) +{curl_error} Curl error message (if available) +{curl_code} Curl error code (if available) +{curl_stderr} Curl standard error (if available) +{connect_time} Time in seconds it took to establish the connection (if available) +{total_time} Total transaction time in seconds for last transfer (if available) +{req_header_*} Replace `*` with the lowercased name of a request header to add to the message +{res_header_*} Replace `*` with the lowercased name of a response header to add to the message +{req_body} Request body +{res_body} Response body +================== ==================================================================================== + +The LogPlugin has a helper method that can be used when debugging that will output the full HTTP request and +response of a transaction: + +.. code-block:: php + + $client->addSubscriber(LogPlugin::getDebugPlugin()); diff --git a/source/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst new file mode 100644 index 0000000..1b1cfa8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/md5-validator-plugin.rst @@ -0,0 +1,29 @@ +==================== +MD5 validator plugin +==================== + +Entity bodies can sometimes be modified over the wire due to a faulty TCP transport or misbehaving proxy. If an HTTP +response contains a Content-MD5 header, then a MD5 hash of the entity body of a response can be compared against the +Content-MD5 header of the response to determine if the response was delivered intact. The +``Guzzle\Plugin\Md5\Md5ValidatorPlugin`` will throw an ``UnexpectedValueException`` if the calculated MD5 hash does +not match the Content-MD5 header value: + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Md5\Md5ValidatorPlugin; + + $client = new Client('http://www.test.com/'); + + $md5Plugin = new Md5ValidatorPlugin(); + + // Add the md5 plugin to the client object + $client->addSubscriber($md5Plugin); + + $request = $client->get('http://www.yahoo.com/'); + $request->send(); + +Calculating the MD5 hash of a large entity body or an entity body that was transferred using a Content-Encoding is an +expensive operation. When working in high performance applications, you might consider skipping the MD5 hash +validation for entity bodies bigger than a certain size or Content-Encoded entity bodies +(see ``Guzzle\Plugin\Md5\Md5ValidatorPlugin`` for more information). diff --git a/source/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst new file mode 100644 index 0000000..4900cb5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/mock-plugin.rst @@ -0,0 +1,27 @@ +=========== +Mock plugin +=========== + +The mock plugin is useful for testing Guzzle clients. The mock plugin allows you to queue an array of responses that +will satisfy requests sent from a client by consuming the request queue in FIFO order. + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Mock\MockPlugin; + use Guzzle\Http\Message\Response; + + $client = new Client('http://www.test.com/'); + + $mock = new MockPlugin(); + $mock->addResponse(new Response(200)) + ->addResponse(new Response(404)); + + // Add the mock plugin to the client object + $client->addSubscriber($mock); + + // The following request will receive a 200 response from the plugin + $client->get('http://www.example.com/')->send(); + + // The following request will receive a 404 response from the plugin + $client->get('http://www.test.com/')->send(); diff --git a/source/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst b/source/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst new file mode 100644 index 0000000..e67eaba --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/oauth-plugin.rst @@ -0,0 +1,30 @@ +============ +OAuth plugin +============ + +Guzzle ships with an OAuth 1.0 plugin that can sign requests using a consumer key, consumer secret, OAuth token, +and OAuth secret. Here's an example showing how to send an authenticated request to the Twitter REST API: + +.. code-block:: php + + use Guzzle\Http\Client; + use Guzzle\Plugin\Oauth\OauthPlugin; + + $client = new Client('http://api.twitter.com/1'); + $oauth = new OauthPlugin(array( + 'consumer_key' => 'my_key', + 'consumer_secret' => 'my_secret', + 'token' => 'my_token', + 'token_secret' => 'my_token_secret' + )); + $client->addSubscriber($oauth); + + $response = $client->get('statuses/public_timeline.json')->send(); + +If you need to use a custom signing method, you can pass a ``signature_method`` configuration option in the +constructor of the OAuth plugin. The ``signature_method`` option must be a callable variable that accepts a string to +sign and signing key and returns a signed string. + +.. note:: + + You can omit the ``token`` and ``token_secret`` options to use two-legged OAuth. diff --git a/source/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc b/source/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc new file mode 100644 index 0000000..8d6d09b --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/plugins-list.rst.inc @@ -0,0 +1,9 @@ +* :doc:`/plugins/async-plugin` +* :doc:`/plugins/backoff-plugin` +* :doc:`/plugins/cache-plugin` +* :doc:`/plugins/cookie-plugin` +* :doc:`/plugins/history-plugin` +* :doc:`/plugins/log-plugin` +* :doc:`/plugins/md5-validator-plugin` +* :doc:`/plugins/mock-plugin` +* :doc:`/plugins/oauth-plugin` diff --git a/source/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst b/source/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst new file mode 100644 index 0000000..19ae57e --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/plugins/plugins-overview.rst @@ -0,0 +1,59 @@ +====================== +Plugin system overview +====================== + +The workflow of sending a request and parsing a response is driven by Guzzle's event system, which is powered by the +`Symfony2 Event Dispatcher component `_. + +Any object in Guzzle that emits events will implement the ``Guzzle\Common\HasEventDispatcher`` interface. You can add +event subscribers directly to these objects using the ``addSubscriber()`` method, or you can grab the +``Symfony\Component\EventDispatcher\EventDispatcher`` object owned by the object using ``getEventDispatcher()`` and +add a listener or event subscriber. + +Adding event subscribers to clients +----------------------------------- + +Any event subscriber or event listener attached to the EventDispatcher of a ``Guzzle\Http\Client`` or +``Guzzle\Service\Client`` object will automatically be attached to all request objects created by the client. This +allows you to attach, for example, a HistoryPlugin to a client object, and from that point on, every request sent +through that client will utilize the HistoryPlugin. + +.. code-block:: php + + use Guzzle\Plugin\History\HistoryPlugin; + use Guzzle\Service\Client; + + $client = new Client(); + + // Create a history plugin and attach it to the client + $history = new HistoryPlugin(); + $client->addSubscriber($history); + + // Create and send a request. This request will also utilize the HistoryPlugin + $client->get('http://httpbin.org')->send(); + + // Echo out the last sent request by the client + echo $history->getLastRequest(); + +.. tip:: + + :doc:`Create event subscribers `, or *plugins*, to implement reusable logic that can be + shared across clients. Event subscribers are also easier to test than anonymous functions. + +Pre-Built plugins +----------------- + +Guzzle provides easy to use request plugins that add behavior to requests based on signal slot event notifications +powered by the Symfony2 Event Dispatcher component. + +* :doc:`async-plugin` +* :doc:`backoff-plugin` +* :doc:`cache-plugin` +* :doc:`cookie-plugin` +* :doc:`curl-auth-plugin` +* :doc:`history-plugin` +* :doc:`log-plugin` +* :doc:`md5-validator-plugin` +* :doc:`mock-plugin` +* :doc:`oauth-plugin` + diff --git a/source/vendor/guzzle/guzzle/docs/requirements.txt b/source/vendor/guzzle/guzzle/docs/requirements.txt new file mode 100644 index 0000000..f62e318 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/requirements.txt @@ -0,0 +1,2 @@ +Sphinx>=1.2b1 +guzzle_sphinx_theme>=0.5.0 diff --git a/source/vendor/guzzle/guzzle/docs/testing/unit-testing.rst b/source/vendor/guzzle/guzzle/docs/testing/unit-testing.rst new file mode 100644 index 0000000..f4297af --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/testing/unit-testing.rst @@ -0,0 +1,201 @@ +=========================== +Unit Testing Guzzle clients +=========================== + +Guzzle provides several tools that will enable you to easily unit test your web service clients. + +* PHPUnit integration +* Mock responses +* node.js web server for integration testing + +PHPUnit integration +------------------- + +Guzzle is unit tested using `PHPUnit `_. Your web service client's unit tests should extend +``Guzzle\Tests\GuzzleTestCase`` so that you can take advantage of some of the built in helpers. + +In order to unit test your client, a developer would need to copy phpunit.xml.dist to phpunit.xml and make any needed +modifications. As a best practice and security measure for you and your contributors, it is recommended to add an +ignore statement to your SCM so that phpunit.xml is ignored. + +Bootstrapping +~~~~~~~~~~~~~ + +Your web service client should have a tests/ folder that contains a bootstrap.php file. The bootstrap.php file +responsible for autoloading and configuring a ``Guzzle\Service\Builder\ServiceBuilder`` that is used throughout your +unit tests for loading a configured client. You can add custom parameters to your phpunit.xml file that expects users +to provide the path to their configuration data. + +.. code-block:: php + + Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Aws\Common\Aws::factory($_SERVER['CONFIG'])); + + Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Guzzle\Service\Builder\ServiceBuilder::factory(array( + 'test.unfuddle' => array( + 'class' => 'Guzzle.Unfuddle.UnfuddleClient', + 'params' => array( + 'username' => 'test_user', + 'password' => '****', + 'subdomain' => 'test' + ) + ) + ))); + +The above code registers a service builder that can be used throughout your unit tests. You would then be able to +retrieve an instantiated and configured Unfuddle client by calling ``$this->getServiceBuilder()->get('test.unfuddle)``. +The above code assumes that ``$_SERVER['CONFIG']`` contains the path to a file that stores service description +configuration. + +Unit testing remote APIs +------------------------ + +Mock responses +~~~~~~~~~~~~~~ + +One of the benefits of unit testing is the ability to quickly determine if there are errors in your code. If your +unit tests run slowly, then they become tedious and will likely be run less frequently. Guzzle's philosophy on unit +testing web service clients is that no network access should be required to run the unit tests. This means that +responses are served from mock responses or local servers. By adhering to this principle, tests will run much faster +and will not require an external resource to be available. The problem with this approach is that your mock responses +must first be gathered and then subsequently updated each time the remote API changes. + +Integration testing over the internet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can perform integration testing with a web service over the internet by making calls directly to the service. If +the web service you are requesting uses a complex signing algorithm or some other specific implementation, then you +may want to include at least one actual network test that can be run specifically through the command line using +`PHPUnit group annotations `_. + +@group internet annotation +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When creating tests that require an internet connection, it is recommended that you add ``@group internet`` annotations +to your unit tests to specify which tests require network connectivity. + +You can then `run PHPUnit tests `_ that exclude the @internet +group by running ``phpunit --exclude-group internet``. + +API credentials +^^^^^^^^^^^^^^^ + +If API credentials are required to run your integration tests, you must add ```` parameters to your +phpunit.xml.dist file and extract these parameters in your bootstrap.php file. + +.. code-block:: xml + + + + + + + + + + + + + ./Tests + + + + +You can then extract the ``server`` variables in your bootstrap.php file by grabbing them from the ``$_SERVER`` +superglobal: ``$apiUser = $_SERVER['API_USER'];`` + +Further reading +^^^^^^^^^^^^^^^ + +A good discussion on the topic of testing remote APIs can be found in Sebastian Bergmann's +`Real-World Solutions for Developing High-Quality PHP Frameworks and Applications `_. + +Queueing Mock responses +----------------------- + +Mock responses can be used to test if requests are being generated correctly and responses and handled correctly by +your client. Mock responses can be queued up for a client using the ``$this->setMockResponse($client, $path)`` method +of your test class. Pass the client you are adding mock responses to and a single path or array of paths to mock +response files relative to the ``/tests/mock/ folder``. This will queue one or more mock responses for your client by +creating a simple observer on the client. Mock response files must contain a full HTTP response message: + +.. code-block:: none + + HTTP/1.1 200 OK + Date: Wed, 25 Nov 2009 12:00:00 GMT + Connection: close + Server: AmazonS3 + Content-Type: application/xml + + + EU + +After queuing mock responses for a client, you can get an array of the requests that were sent by the client that +were issued a mock response by calling ``$this->getMockedRequests()``. + +You can also use the ``Guzzle\Plugin\Mock\MockPlugin`` object directly with your clients. + +.. code-block:: php + + $plugin = new Guzzle\Plugin\Mock\MockPlugin(); + $plugin->addResponse(new Guzzle\Http\Message\Response(200)); + $client = new Guzzle\Http\Client(); + $client->addSubscriber($plugin); + + // The following request will get the mock response from the plugin in FIFO order + $request = $client->get('http://www.test.com/'); + $request->send(); + + // The MockPlugin maintains a list of requests that were mocked + $this->assertContainsOnly($request, $plugin->getReceivedRequests()); + +node.js web server for integration testing +------------------------------------------ + +Using mock responses is usually enough when testing a web service client. If your client needs to add custom cURL +options to requests, then you should use the node.js test web server to ensure that your HTTP request message is +being created correctly. + +Guzzle is based around PHP's libcurl bindings. cURL sometimes modifies an HTTP request message based on +``CURLOPT_*`` options. Headers that are added to your request by cURL will not be accounted for if you inject mock +responses into your tests. Additionally, some request entity bodies cannot be loaded by the client before transmitting +it to the sever (for example, when using a client as a sort of proxy and streaming content from a remote server). You +might also need to inspect the entity body of a ``multipart/form-data`` POST request. + +.. note:: + + You can skip all of the tests that require the node.js test web server by excluding the ``server`` group: + ``phpunit --exclude-group server`` + +Using the test server +~~~~~~~~~~~~~~~~~~~~~ + +The node.js test server receives requests and returns queued responses. The test server exposes a simple API that is +used to enqueue responses and inspect the requests that it has received. + +Retrieve the server object by calling ``$this->getServer()``. If the node.js server is not running, it will be +started as a forked process and an object that interfaces with the server will be returned. (note: stopping the +server is handled internally by Guzzle.) + +You can queue an HTTP response or an array of responses by calling ``$this->getServer()->enqueue()``: + +.. code-block:: php + + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + +The above code queues a single 200 response with an empty body. Responses are queued using a FIFO order; this +response will be returned by the server when it receives the first request and then removed from the queue. If a +request is received by a server with no queued responses, an exception will be thrown in your unit test. + +You can inspect the requests that the server has retrieved by calling ``$this->getServer()->getReceivedRequests()``. +This method accepts an optional ``$hydrate`` parameter that specifies if you are retrieving an array of string HTTP +requests or an array of ``Guzzle\Http\RequestInterface`` subclassed objects. "Hydrating" the requests will allow +greater flexibility in your unit tests so that you can easily assert the state of the various parts of a request. + +You will need to modify the base_url of your web service client in order to use it against the test server. + +.. code-block:: php + + $client = $this->getServiceBuilder()->get('my_client'); + $client->setBaseUrl($this->getServer()->getUrl()); + +After running the above code, all calls made from the ``$client`` object will be sent to the test web server. diff --git a/source/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst b/source/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst new file mode 100644 index 0000000..ad6070b --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/webservice-client/guzzle-service-descriptions.rst @@ -0,0 +1,619 @@ +=========================== +Guzzle service descriptions +=========================== + +Guzzle allows you to serialize HTTP requests and parse HTTP responses using a DSL called a service descriptions. +Service descriptions define web service APIs by documenting each operation, the operation's parameters, validation +options for each parameter, an operation's response, how the response is parsed, and any errors that can be raised for +an operation. Writing a service description for a web service allows you to more quickly consume a web service than +writing concrete commands for each web service operation. + +Guzzle service descriptions can be representing using a PHP array or JSON document. Guzzle's service descriptions are +heavily inspired by `Swagger `_. + +Service description schema +========================== + +A Guzzle Service description must match the following JSON schema document. This document can also serve as a guide when +implementing a Guzzle service description. + +Download the schema here: :download:`Guzzle JSON schema document ` + +.. class:: overflow-height-500px + + .. literalinclude:: ../_downloads/guzzle-schema-1.0.json + :language: json + +Top-level attributes +-------------------- + +Service descriptions are comprised of the following top-level attributes: + +.. code-block:: json + + { + "name": "string", + "apiVersion": "string|number", + "baseUrl": "string", + "description": "string", + "operations": {}, + "models": {}, + "includes": ["string.php", "string.json"] + } + ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| Property Name | Value | Description | ++=========================================+=========================+=======================================================================================================================+ +| name | string | Name of the web service | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| apiVersion | string|number | Version identifier that the service description is compatible with | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| baseUrl or basePath | string | Base URL of the web service. Any relative URI specified in an operation will be merged with the baseUrl using the | +| | | process defined in RFC 2396. Some clients require custom logic to determine the baseUrl. In those cases, it is best | +| | | to not include a baseUrl in the service description, but rather allow the factory method of the client to configure | +| | | the client’s baseUrl. | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| description | string | Short summary of the web service | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| operations | object containing | Operations of the service. The key is the name of the operation and value is the attributes of the operation. | +| | :ref:`operation-schema` | | +| | | | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| models | object containing | Schema models that can be referenced throughout the service description. Models can be used to define how an HTTP | +| | :ref:`model-schema` | response is parsed into a ``Guzzle\Service\Resource\Model`` object when an operation uses a ``model`` ``responseType``| ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| includes | array of .js, | Service description files to include and extend from (can be a .json, .js, or .php file) | +| | .json, or .php | | +| | files. | | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ +| (any additional properties) | mixed | Any additional properties specified as top-level attributes are allowed and will be treated as arbitrary data | ++-----------------------------------------+-------------------------+-----------------------------------------------------------------------------------------------------------------------+ + +.. _operation-schema: + +Operations +---------- + +Operations are the actions that can be taken on a service. Each operation is given a unique name and has a distinct +endpoint and HTTP method. If an API has a ``DELETE /users/:id`` operation, a satisfactory operation name might be +``DeleteUser`` with a parameter of ``id`` that is inserted into the URI. + +.. class:: overflow-height-250px + + .. code-block:: json + + { + "operations": { + "operationName": { + "extends": "string", + "httpMethod": "GET|POST|PUT|DELETE|PATCH|string", + "uri": "string", + "summary": "string", + "class": "string", + "responseClass": "string", + "responseNotes": "string", + "type": "string", + "description": "string", + "responseType": "primitive|class|(model by name)|documentation|(string)", + "deprecated": false, + "errorResponses": [ + { + "code": 500, + "reason": "Unexpected Error", + "class": "string" + } + ], + "data": { + "foo": "bar", + "baz": "bam" + }, + "parameters": {} + } + } + } + +.. csv-table:: + :header: "Property Name", "Value", "Description" + :widths: 20, 15, 65 + + "extends", "string", "Extend from another operation by name. The parent operation must be defined before the child." + "httpMethod", "string", "HTTP method used with the operation (e.g. GET, POST, PUT, DELETE, PATCH, etc)" + "uri", "string", "URI of the operation. The uri attribute can contain URI templates. The variables of the URI template are parameters of the operation with a location value of uri" + "summary", "string", "Short summary of what the operation does" + "class", "string", "Custom class to instantiate instead of the default Guzzle\\Service\\Command\\OperationCommand. Using this attribute allows you to define an operation using a service description, but allows more customized logic to be implemented in user-land code." + "responseClass", "string", "Defined what is returned from the method. Can be a primitive, class name, or model name. You can specify the name of a class to return a more customized result from the operation (for example, a domain model object). When using the name of a PHP class, the class must implement ``Guzzle\Service\Command\ResponseClassInterface``." + "responseNotes", "string", "A description of the response returned by the operation" + "responseType", "string", "The type of response that the operation creates: one of primitive, class, model, or documentation. If not specified, this value will be automatically inferred based on whether or not there is a model matching the name, if a matching class name is found, or set to 'primitive' by default." + "deprecated", "boolean", "Whether or not the operation is deprecated" + "errorResponses", "array", "Errors that could occur while executing the operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error), 'reason' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this error is encountered)" + "data", "object", "Any arbitrary data to associate with the operation" + "parameters", "object containing :ref:`parameter-schema` objects", "Parameters of the operation. Parameters are used to define how input data is serialized into a HTTP request." + "additionalParameters", "A single :ref:`parameter-schema` object", "Validation and serialization rules for any parameter supplied to the operation that was not explicitly defined." + +additionalParameters +~~~~~~~~~~~~~~~~~~~~ + +When a webservice offers a large number of parameters that all are set in the same location (for example the query +string or a JSON document), defining each parameter individually can require a lot of time and repetition. Furthermore, +some web services allow for completely arbitrary parameters to be supplied for an operation. The +``additionalParameters`` attribute can be used to solve both of these issues. + +As an example, we can define a Twitter API operation quite easily using ``additionalParameters``. The +GetMentions operation accepts a large number of query string parameters. Defining each of these parameters +is ideal because it provide much more introspection for the client and opens the possibility to use the description with +other tools (e.g. a documentation generator). However, you can very quickly provide a "catch-all" serialization rule +that will place any custom parameters supplied to an operation the generated request's query string parameters. + +.. class:: overflow-height-250px + + .. code-block:: json + + { + "name": "Twitter", + "apiVersion": "1.1", + "baseUrl": "https://api.twitter.com/1.1", + "operations": { + "GetMentions": { + "httpMethod": "GET", + "uri": "statuses/mentions_timeline.json", + "responseClass": "GetMentionsOutput", + "additionalParameters": { + "location": "query" + } + } + }, + "models": { + "GetMentionsOutput": { + "type": "object", + "additionalProperties": { + "location": "json" + } + } + } + } + +responseClass +~~~~~~~~~~~~~ + +The ``responseClass`` attribute is used to define the return value of an operation (what is returned by calling the +``getResult()`` method of a command object). The value set in the responseClass attribute can be one of "primitive" +(meaning the result with be primitive type like a string), a class name meaning the result will be an instance of a +specific user-land class, or a model name meaning the result will be a ``Guzzle\Service\Resource\Model`` object that +uses a :ref:`model schema ` to define how the HTTP response is parsed. + +.. note:: + + Using a class name with a ``responseClass`` will only work if it is supported by the ``class`` that is instantiated + for the operation. Keep this in mind when specifying a custom ``class`` attribute that points to a custom + ``Guzzle\Service\Command\CommandInterface`` class. The default ``class``, + ``Guzzle\Service\Command\OperationCommand``, does support setting custom ``class`` attributes. + +You can specify the name of a class to return a more customized result from the operation (for example, a domain model +object). When using the name of a PHP class, the class must implement ``Guzzle\Service\Command\ResponseClassInterface``. +Here's a very simple example of implementing a custom responseClass object. + +.. code-block:: json + + { + "operations": { + "test": { + "responseClass": "MyApplication\\User" + } + } + } + +.. code-block:: php + + namespace MyApplication; + + use Guzzle\Service\Command\ResponseClassInterface; + use Guzzle\Service\Command\OperationCommand; + + class User implements ResponseClassInterface + { + protected $name; + + public static function fromCommand(OperationCommand $command) + { + $response = $command->getResponse(); + $xml = $response->xml(); + + return new self((string) $xml->name); + } + + public function __construct($name) + { + $this->name = $name; + } + } + +errorResponses +~~~~~~~~~~~~~~ + +``errorResponses`` is an array containing objects that define the errors that could occur while executing the +operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error), +'reason' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this +error is encountered). + +ErrorResponsePlugin +^^^^^^^^^^^^^^^^^^^ + +Error responses are by default only used for documentation. If you don't need very complex exception logic for your web +service errors, then you can use the ``Guzzle\Plugin\ErrorResponse\ErrorResponsePlugin`` to automatically throw defined +exceptions when one of the ``errorResponse`` rules are matched. The error response plugin will listen for the +``request.complete`` event of a request created by a command object. Every response (including a successful response) is +checked against the list of error responses for an exact match using the following order of checks: + +1. Does the errorResponse have a defined ``class``? +2. Is the errorResponse ``code`` equal to the status code of the response? +3. Is the errorResponse ``reason`` equal to the reason phrase of the response? +4. Throw the exception stored in the ``class`` attribute of the errorResponse. + +The ``class`` attribute must point to a class that implements +``Guzzle\Plugin\ErrorResponse\ErrorResponseExceptionInterface``. This interface requires that an error response class +implements ``public static function fromCommand(CommandInterface $command, Response $response)``. This method must +return an object that extends from ``\Exception``. After an exception is returned, it is thrown by the plugin. + +.. _parameter-schema: + +Parameter schema +---------------- + +Parameters in both operations and models are represented using the +`JSON schema `_ syntax. + +.. csv-table:: + :header: "Property Name", "Value", "Description" + :widths: 20, 15, 65 + + "name", "string", "Unique name of the parameter" + "type", "string|array", "Type of variable (string, number, integer, boolean, object, array, numeric, null, any). Types are using for validation and determining the structure of a parameter. You can use a union type by providing an array of simple types. If one of the union types matches the provided value, then the value is valid." + "instanceOf", "string", "When the type is an object, you can specify the class that the object must implement" + "required", "boolean", "Whether or not the parameter is required" + "default", "mixed", "Default value to use if no value is supplied" + "static", "boolean", "Set to true to specify that the parameter value cannot be changed from the default setting" + "description", "string", "Documentation of the parameter" + "location", "string", "The location of a request used to apply a parameter. Custom locations can be registered with a command, but the defaults are uri, query, statusCode, reasonPhrase, header, body, json, xml, postField, postFile, responseBody" + "sentAs", "string", "Specifies how the data being modeled is sent over the wire. For example, you may wish to include certain headers in a response model that have a normalized casing of FooBar, but the actual header is x-foo-bar. In this case, sentAs would be set to x-foo-bar." + "filters", "array", "Array of functions to to run a parameter value through." + +filters +~~~~~~~ + +Each value in the array must be a string containing the full class path to a static method or an array of complex +filter information. You can specify static methods of classes using the full namespace class name followed by +"::" (e.g. ``FooBar::baz()``). Some filters require arguments in order to properly filter a value. For complex filters, +use an object containing a ``method`` attribute pointing to a function, and an ``args`` attribute containing an +array of positional arguments to pass to the function. Arguments can contain keywords that are replaced when filtering +a value: ``@value`` is replaced with the value being filtered, and ``@api`` is replaced with the actual Parameter +object. + +.. code-block:: json + + { + "filters": [ + "strtolower", + { + "method": "MyClass::convertString", + "args": [ "test", "@value", "@api" ] + } + ] + } + +The above example will filter a parameter using ``strtolower``. It will then call the ``convertString`` static method +of ``MyClass``, passing in "test", the actual value of the parameter, and a ``Guzzle\Service\Description\Parameter`` +object. + +Operation parameter location attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The location field of top-level parameters control how a parameter is serialized when generating a request. + +uri location +^^^^^^^^^^^^ + +Parameters are injected into the ``uri`` attribute of the operation using +`URI-template expansion `_. + +.. code-block:: json + + { + "operations": { + "uriTest": { + "uri": "/test/{testValue}", + "parameters": { + "testValue": { + "location": "uri" + } + } + } + } + } + +query location +^^^^^^^^^^^^^^ + +Parameters are injected into the query string of a request. Query values can be nested, which would result in a PHP +style nested query string. The name of a parameter is the default name of the query string parameter added to the +request. You can override this behavior by specifying the ``sentAs`` attribute on the parameter. + +.. code-block:: json + + { + "operations": { + "queryTest": { + "parameters": { + "testValue": { + "location": "query", + "sentAs": "test_value" + } + } + } + } + } + +header location +^^^^^^^^^^^^^^^ + +Parameters are injected as headers on an HTTP request. The name of the parameter is used as the name of the header by +default. You can change the name of the header created by the parameter using the ``sentAs`` attribute. + +Headers that are of type ``object`` will be added as multiple headers to a request using the key of the input array as +the header key. Setting a ``sentAs`` attribute along with a type ``object`` will use the value of ``sentAs`` as a +prefix for each header key. + +body location +^^^^^^^^^^^^^ + +Parameters are injected as the body of a request. The input of these parameters may be anything that can be cast to a +string or a ``Guzzle\Http\EntityBodyInterface`` object. + +postField location +^^^^^^^^^^^^^^^^^^ + +Parameters are inserted as POST fields in a request. Nested values may be supplied and will be represented using +PHP style nested query strings. The POST field name is the same as the parameter name by default. You can use the +``sentAs`` parameter to override the POST field name. + +postFile location +^^^^^^^^^^^^^^^^^ + +Parameters are added as POST files. A postFile value may be a string pointing to a local filename or a +``Guzzle\Http\Message\PostFileInterface`` object. The name of the POST file will be the name of the parameter by +default. You can use a custom POST file name by using the ``sentAs`` attribute. + +Supports "string" and "array" types. + +json location +^^^^^^^^^^^^^ + +Parameters are added to the body of a request as top level keys of a JSON document. Nested values may be specified, +with any number of nested ``Guzzle\Common\ToArrayInterface`` objects. When JSON parameters are specified, the +``Content-Type`` of the request will change to ``application/json`` if a ``Content-Type`` has not already been specified +on the request. + +xml location +^^^^^^^^^^^^ + +Parameters are added to the body of a request as top level nodes of an XML document. Nested values may be specified, +with any number of nested ``Guzzle\Common\ToArrayInterface`` objects. When XML parameters are specified, the +``Content-Type`` of the request will change to ``application/xml`` if a ``Content-Type`` has not already been specified +on the request. + +responseBody location +^^^^^^^^^^^^^^^^^^^^^ + +Specifies the EntityBody of a response. This can be used to download the response body to a file or a custom Guzzle +EntityBody object. + +No location +^^^^^^^^^^^ + +If a parameter has no location attribute, then the parameter is simply used as a data value. + +Other locations +^^^^^^^^^^^^^^^ + +Custom locations can be registered as new locations or override default locations if needed. + +.. _model-schema: + +Model Schema +------------ + +Models are used in service descriptions to provide generic JSON schema definitions that can be extended from or used in +``$ref`` attributes. Models can also be referenced in a ``responseClass`` attribute to provide valuable output to an +operation. Models are JSON schema documents and use the exact syntax and attributes used in parameters. + +Response Models +~~~~~~~~~~~~~~~ + +Response models describe how a response is parsed into a ``Guzzle\Service\Resource\Model`` object. Response models are +always modeled as JSON schema objects. When an HTTP response is parsed using a response model, the rules specified on +each property of a response model will translate 1:1 as keys in a PHP associative array. When a ``sentAs`` attribute is +found in response model parameters, the value retrieved from the HTTP response is retrieved using the ``sentAs`` +parameter but stored in the response model using the name of the parameter. + +The location field of top-level parameters in a response model tell response parsers how data is retrieved from a +response. + +statusCode location +^^^^^^^^^^^^^^^^^^^ + +Retrieves the status code of the response. + +reasonPhrase location +^^^^^^^^^^^^^^^^^^^^^ + +Retrieves the reason phrase of the response. + +header location +^^^^^^^^^^^^^^^ + +Retrieves a header from the HTTP response. + +body location +^^^^^^^^^^^^^ + +Retrieves the body of an HTTP response. + +json location +^^^^^^^^^^^^^ + +Retrieves a top-level parameter from a JSON document contained in an HTTP response. + +You can use ``additionalProperties`` if the JSON document is wrapped in an outer array. This allows you to parse the +contents of each item in the array using the parsing rules defined in the ``additionalProperties`` schema. + +xml location +^^^^^^^^^^^^ + +Retrieves a top-level node value from an XML document contained in an HTTP response. + +Other locations +^^^^^^^^^^^^^^^ + +Custom locations can be registered as new locations or override default locations if needed. + +Example service description +--------------------------- + +Let's say you're interacting with a web service called 'Foo' that allows for the following routes and methods:: + + GET/POST /users + GET/DELETE /users/:id + +The following JSON service description implements this simple web service: + +.. class:: overflow-height-500px + + .. code-block:: json + + { + "name": "Foo", + "apiVersion": "2012-10-14", + "baseUrl": "http://api.foo.com", + "description": "Foo is an API that allows you to Baz Bar", + "operations": { + "GetUsers": { + "httpMethod": "GET", + "uri": "/users", + "summary": "Gets a list of users", + "responseClass": "GetUsersOutput" + }, + "CreateUser": { + "httpMethod": "POST", + "uri": "/users", + "summary": "Creates a new user", + "responseClass": "CreateUserOutput", + "parameters": { + "name": { + "location": "json", + "type": "string" + }, + "age": { + "location": "json", + "type": "integer" + } + } + }, + "GetUser": { + "httpMethod": "GET", + "uri": "/users/{id}", + "summary": "Retrieves a single user", + "responseClass": "GetUserOutput", + "parameters": { + "id": { + "location": "uri", + "description": "User to retrieve by ID", + "required": true + } + } + }, + "DeleteUser": { + "httpMethod": "DELETE", + "uri": "/users/{id}", + "summary": "Deletes a user", + "responseClass": "DeleteUserOutput", + "parameters": { + "id": { + "location": "uri", + "description": "User to delete by ID", + "required": true + } + } + } + }, + "models": { + "GetUsersOutput": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "location": "json", + "type": "string" + }, + "age": { + "location": "json", + "type": "integer" + } + } + } + }, + "CreateUserOutput": { + "type": "object", + "properties": { + "id": { + "location": "json", + "type": "string" + }, + "location": { + "location": "header", + "sentAs": "Location", + "type": "string" + } + } + }, + "GetUserOutput": { + "type": "object", + "properties": { + "name": { + "location": "json", + "type": "string" + }, + "age": { + "location": "json", + "type": "integer" + } + } + }, + "DeleteUserOutput": { + "type": "object", + "properties": { + "status": { + "location": "statusCode", + "type": "integer" + } + } + } + } + } + +If you attach this service description to a client, you would completely configure the client to interact with the +Foo web service and provide valuable response models for each operation. + +.. code-block:: php + + use Guzzle\Service\Description\ServiceDescription; + + $description = ServiceDescription::factory('/path/to/client.json'); + $client->setDescription($description); + + $command = $client->getCommand('DeleteUser', array('id' => 123)); + $responseModel = $client->execute($command); + echo $responseModel['status']; + +.. note:: + + You can add the service description to your client's factory method or constructor. diff --git a/source/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst b/source/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst new file mode 100644 index 0000000..b7113d6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/webservice-client/using-the-service-builder.rst @@ -0,0 +1,316 @@ +======================= +Using a service builder +======================= + +The best way to instantiate Guzzle web service clients is to let Guzzle handle building the clients for you using a +ServiceBuilder. A ServiceBuilder is responsible for creating concrete client objects based on configuration settings +and helps to manage credentials for different environments. + +You don't have to use a service builder, but they help to decouple your application from concrete classes and help to +share configuration data across multiple clients. Consider the following example. Here we are creating two clients that +require the same API public key and secret key. The clients are created using their ``factory()`` methods. + +.. code-block:: php + + use MyService\FooClient; + use MyService\BarClient; + + $foo = FooClient::factory(array( + 'key' => 'abc', + 'secret' => '123', + 'custom' => 'and above all' + )); + + $bar = BarClient::factory(array( + 'key' => 'abc', + 'secret' => '123', + 'custom' => 'listen to me' + )); + +The redundant specification of the API keys can be removed using a service builder. + +.. code-block:: php + + use Guzzle\Service\Builder\ServiceBuilder; + + $builder = ServiceBuilder::factory(array( + 'services' => array( + 'abstract_client' => array( + 'params' => array( + 'key' => 'abc', + 'secret' => '123' + ) + ), + 'foo' => array( + 'extends' => 'abstract_client', + 'class' => 'MyService\FooClient', + 'params' => array( + 'custom' => 'and above all' + ) + ), + 'bar' => array( + 'extends' => 'abstract_client', + 'class' => 'MyService\FooClient', + 'params' => array( + 'custom' => 'listen to me' + ) + ) + ) + )); + + $foo = $builder->get('foo'); + $bar = $builder->get('bar'); + +You can make managing your API keys even easier by saving the service builder configuration in a JSON format in a +.json file. + +Creating a service builder +-------------------------- + +A ServiceBuilder can source information from an array, an PHP include file that returns an array, or a JSON file. + +.. code-block:: php + + use Guzzle\Service\Builder\ServiceBuilder; + + // Source service definitions from a JSON file + $builder = ServiceBuilder::factory('services.json'); + +Sourcing data from an array +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Data can be source from a PHP array. The array must contain an associative ``services`` array that maps the name of a +client to the configuration information used by the service builder to create the client. Clients are given names +which are used to identify how a client is retrieved from a service builder. This can be useful for using multiple +accounts for the same service or creating development clients vs. production clients. + +.. code-block:: php + + $services = array( + 'includes' => array( + '/path/to/other/services.json', + '/path/to/other/php_services.php' + ), + 'services' => array( + 'abstract.foo' => array( + 'params' => array( + 'username' => 'foo', + 'password' => 'bar' + ) + ), + 'bar' => array( + 'extends' => 'abstract.foo', + 'class' => 'MyClientClass', + 'params' => array( + 'other' => 'abc' + ) + ) + ) + ); + +A service builder configuration array contains two top-level array keys: + ++------------+---------------------------------------------------------------------------------------------------------+ +| Key | Description | ++============+=========================================================================================================+ +| includes | Array of paths to JSON or PHP include files to include in the configuration. | ++------------+---------------------------------------------------------------------------------------------------------+ +| services | Associative array of defined services that can be created by the service builder. Each service can | +| | contain the following keys: | +| | | +| | +------------+----------------------------------------------------------------------------------------+ | +| | | Key | Description | | +| | +============+========================================================================================+ | +| | | class | The concrete class to instantiate that implements the | | +| | | | ``Guzzle\Common\FromConfigInterface``. | | +| | +------------+----------------------------------------------------------------------------------------+ | +| | | extends | The name of a previously defined service to extend from | | +| | +------------+----------------------------------------------------------------------------------------+ | +| | | params | Associative array of parameters to pass to the factory method of the service it is | | +| | | | instantiated | | +| | +------------+----------------------------------------------------------------------------------------+ | +| | | alias | An alias that can be used in addition to the array key for retrieving a client from | | +| | | | the service builder. | | +| | +------------+----------------------------------------------------------------------------------------+ | ++------------+---------------------------------------------------------------------------------------------------------+ + +The first client defined, ``abstract.foo``, is used as a placeholder of shared configuration values. Any service +extending abstract.foo will inherit its params. As an example, this can be useful when clients share the same username +and password. + +The next client, ``bar``, extends from ``abstract.foo`` using the ``extends`` attribute referencing the client from +which to extend. Additional parameters can be merged into the original service definition when extending a parent +service. + +.. important:: + + Each client that you intend to instantiate must specify a ``class`` attribute that references the full class name + of the client being created. The class referenced in the ``class`` parameter must implement a static ``factory()`` + method that accepts an array or ``Guzzle\Common\Collection`` object and returns an instantiated object. + +Sourcing from a PHP include +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can create service builder configurations using a PHP include file. This can be useful if you wish to take +advantage of an opcode cache like APC to speed up the process of loading and processing the configuration. The PHP +include file is the same format as an array, but you simply create a PHP script that returns an array and save the +file with the .php file extension. + +.. code-block:: php + + '...'); + // Saved as config.php + +This configuration file can then be used with a service builder. + +.. code-block:: php + + $builder = ServiceBuilder::factory('/path/to/config.php'); + +Sourcing from a JSON document +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use JSON documents to serialize your service descriptions. The JSON format uses the exact same structure as +the PHP array syntax, but it's just serialized using JSON. + +.. code-block:: javascript + + { + "includes": ["/path/to/other/services.json", "/path/to/other/php_services.php"], + "services": { + "abstract.foo": { + "params": { + "username": "foo", + "password": "bar" + } + }, + "bar": { + "extends": "abstract.foo", + "class": "MyClientClass", + "params": { + "other": "abc" + } + } + } + } + +Referencing other clients in parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If one of your clients depends on another client as one of its parameters, you can reference that client by name by +enclosing the client's reference key in ``{}``. + +.. code-block:: javascript + + { + "services": { + "token": { + "class": "My\Token\TokenFactory", + "params": { + "access_key": "xyz" + } + }, + "client": { + "class": "My\Client", + "params": { + "token_client": "{token}", + "version": "1.0" + } + } + } + } + +When ``client`` is constructed by the service builder, the service builder will first create the ``token`` service +and then inject the token service into ``client``'s factory method in the ``token_client`` parameter. + +Retrieving clients from a service builder +----------------------------------------- + +Clients are referenced using a customizable name you provide in your service definition. The ServiceBuilder is a sort +of multiton object-- it will only instantiate a client once and return that client for subsequent retrievals. Clients +are retrieved by name (the array key used in the configuration) or by the ``alias`` setting of a service. + +Here's an example of retrieving a client from your ServiceBuilder: + +.. code-block:: php + + $client = $builder->get('foo'); + + // You can also use the ServiceBuilder object as an array + $client = $builder['foo']; + +Creating throwaway clients +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can get a "throwaway" client (a client that is not persisted by the ServiceBuilder) by passing ``true`` in the +second argument of ``ServiceBuilder::get()``. This allows you to create a client that will not be returned by other +parts of your code that use the service builder. Instead of passing ``true``, you can pass an array of configuration +settings that will override the configuration settings specified in the service builder. + +.. code-block:: php + + // Get a throwaway client and overwrite the "custom" setting of the client + $foo = $builder->get('foo', array( + 'custom' => 'in this world there are rules' + )); + +Getting raw configuration settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can get the raw configuration settings provided to the service builder for a specific service using the +``getData($name)`` method of a service builder. This method will null if the service was not found in the service +builder or an array of configuration settings if the service was found. + +.. code-block:: php + + $data = $builder->getData('foo'); + echo $data['key'] . "\n"; + echo $data['secret'] . "\n"; + echo $data['custom'] . "\n"; + +Adding a plugin to all clients +------------------------------ + +You can add a plugin to all clients created by a service builder using the ``addGlobalPlugin($plugin)`` method of a +service builder and passing a ``Symfony\Component\EventDispatcher\EventSubscriberInterface`` object. The service builder +will then attach each global plugin to every client as it is created. This allows you to, for example, add a LogPlugin +to every request created by a service builder for easy debugging. + +.. code-block:: php + + use Guzzle\Plugin\Log\LogPlugin; + + // Add a debug log plugin to every client as it is created + $builder->addGlobalPlugin(LogPlugin::getDebugPlugin()); + + $foo = $builder->get('foo'); + $foo->get('/')->send(); + // Should output all of the data sent over the wire + +.. _service-builder-events: + +Events emitted from a service builder +------------------------------------- + +A ``Guzzle\Service\Builder\ServiceBuilder`` object emits the following events: + ++-------------------------------+--------------------------------------------+-----------------------------------------+ +| Event name | Description | Event data | ++===============================+============================================+=========================================+ +| service_builder.create_client | Called when a client is created | * client: The created client object | ++-------------------------------+--------------------------------------------+-----------------------------------------+ + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Service\Builder\ServiceBuilder; + + $builder = ServiceBuilder::factory('/path/to/config.json'); + + // Add an event listener to print out each client client as it is created + $builder->getEventDispatcher()->addListener('service_builder.create_client', function (Event $e) { + echo 'Client created: ' . get_class($e['client']) . "\n"; + }); + + $foo = $builder->get('foo'); + // Should output the class used for the "foo" client diff --git a/source/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst b/source/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst new file mode 100644 index 0000000..7ec771e --- /dev/null +++ b/source/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst @@ -0,0 +1,659 @@ +====================== +The web service client +====================== + +The ``Guzzle\Service`` namespace contains various abstractions that help to make it easier to interact with a web +service API, including commands, service descriptions, and resource iterators. + +In this chapter, we'll build a simple `Twitter API client `_. + +Creating a client +================= + +A class that extends from ``Guzzle\Service\Client`` or implements ``Guzzle\Service\ClientInterface`` must implement a +``factory()`` method in order to be used with a :doc:`service builder `. + +Factory method +-------------- + +You can use the ``factory()`` method of a client directly if you do not need a service builder. + +.. code-block:: php + + use mtdowling\TwitterClient; + + // Create a client and pass an array of configuration data + $twitter = TwitterClient::factory(array( + 'consumer_key' => '****', + 'consumer_secret' => '****', + 'token' => '****', + 'token_secret' => '****' + )); + +.. note:: + + If you'd like to follow along, here's how to get your Twitter API credentials: + + 1. Visit https://dev.twitter.com/apps + 2. Click on an application that you've created + 3. Click on the "OAuth tool" tab + 4. Copy all of the settings under "OAuth Settings" + +Implementing a factory method +----------------------------- + +Creating a client and its factory method is pretty simple. You just need to implement ``Guzzle\Service\ClientInterface`` +or extend from ``Guzzle\Service\Client``. + +.. code-block:: php + + namespace mtdowling; + + use Guzzle\Common\Collection; + use Guzzle\Plugin\Oauth\OauthPlugin; + use Guzzle\Service\Client; + use Guzzle\Service\Description\ServiceDescription; + + /** + * A simple Twitter API client + */ + class TwitterClient extends Client + { + public static function factory($config = array()) + { + // Provide a hash of default client configuration options + $default = array('base_url' => 'https://api.twitter.com/1.1'); + + // The following values are required when creating the client + $required = array( + 'base_url', + 'consumer_key', + 'consumer_secret', + 'token', + 'token_secret' + ); + + // Merge in default settings and validate the config + $config = Collection::fromConfig($config, $default, $required); + + // Create a new Twitter client + $client = new self($config->get('base_url'), $config); + + // Ensure that the OauthPlugin is attached to the client + $client->addSubscriber(new OauthPlugin($config->toArray())); + + return $client; + } + } + +Service Builder +--------------- + +A service builder is used to easily create web service clients, provides a simple configuration driven approach to +creating clients, and allows you to share configuration settings across multiple clients. You can find out more about +Guzzle's service builder in :doc:`using-the-service-builder`. + +.. code-block:: php + + use Guzzle\Service\Builder\ServiceBuilder; + + // Create a service builder and provide client configuration data + $builder = ServiceBuilder::factory('/path/to/client_config.json'); + + // Get the client from the service builder by name + $twitter = $builder->get('twitter'); + +The above example assumes you have JSON data similar to the following stored in "/path/to/client_config.json": + +.. code-block:: json + + { + "services": { + "twitter": { + "class": "mtdowling\\TwitterClient", + "params": { + "consumer_key": "****", + "consumer_secret": "****", + "token": "****", + "token_secret": "****" + } + } + } + } + +.. note:: + + A service builder becomes much more valuable when using multiple web service clients in a single application or + if you need to utilize the same client with varying configuration settings (e.g. multiple accounts). + +Commands +======== + +Commands are a concept in Guzzle that helps to hide the underlying implementation of an API by providing an easy to use +parameter driven object for each action of an API. A command is responsible for accepting an array of configuration +parameters, serializing an HTTP request, and parsing an HTTP response. Following the +`command pattern `_, commands in Guzzle offer a greater level of +flexibility when implementing and utilizing a web service client. + +Executing commands +------------------ + +You must explicitly execute a command after creating a command using the ``getCommand()`` method. A command has an +``execute()`` method that may be called, or you can use the ``execute()`` method of a client object and pass in the +command object. Calling either of these execute methods will return the result value of the command. The result value is +the result of parsing the HTTP response with the ``process()`` method. + +.. code-block:: php + + // Get a command from the client and pass an array of parameters + $command = $twitter->getCommand('getMentions', array( + 'count' => 5 + )); + + // Other parameters can be set on the command after it is created + $command['trim_user'] = false; + + // Execute the command using the command object. + // The result value contains an array of JSON data from the response + $result = $command->execute(); + + // You can retrieve the result of the command later too + $result = $command->getResult(). + +Command object also contains methods that allow you to inspect the HTTP request and response that was utilized with +the command. + +.. code-block:: php + + $request = $command->getRequest(); + $response = $command->getResponse(); + +.. note:: + + The format and notation used to retrieve commands from a client can be customized by injecting a custom command + factory, ``Guzzle\Service\Command\Factory\FactoryInterface``, on the client using ``$client->setCommandFactory()``. + +Executing with magic methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using method missing magic methods with a command, the command will be executed right away and the result of the +command is returned. + +.. code-block:: php + + $jsonData = $twitter->getMentions(array( + 'count' => 5, + 'trim_user' => true + )); + +Creating commands +----------------- + +Commands are created using either the ``getCommand()`` method of a client or a magic missing method of a client. Using +the ``getCommand()`` method allows you to create a command without executing it, allowing for customization of the +command or the request serialized by the command. + +When a client attempts to create a command, it uses the client's ``Guzzle\Service\Command\Factory\FactoryInterface``. +By default, Guzzle will utilize a command factory that first looks for a concrete class for a particular command +(concrete commands) followed by a command defined by a service description (operation commands). We'll learn more about +concrete commands and operation commands later in this chapter. + +.. code-block:: php + + // Get a command from the twitter client. + $command = $twitter->getCommand('getMentions'); + $result = $command->execute(); + +Unless you've skipped ahead, running the above code will throw an exception. + + PHP Fatal error: Uncaught exception 'Guzzle\Common\Exception\InvalidArgumentException' with message + 'Command was not found matching getMentions' + +This exception was thrown because the "getMentions" command has not yet been implemented. Let's implement one now. + +Concrete commands +~~~~~~~~~~~~~~~~~ + +Commands can be created in one of two ways: create a concrete command class that extends +``Guzzle\Service\Command\AbstractCommand`` or +:doc:`create an OperationCommand based on a service description `. The recommended +approach is to use a service description to define your web service, but you can use concrete commands when custom +logic must be implemented for marshaling or unmarshaling a HTTP message. + +Commands are the method in which you abstract away the underlying format of the requests that need to be sent to take +action on a web service. Commands in Guzzle are meant to be built by executing a series of setter methods on a command +object. Commands are only validated right before they are executed. A ``Guzzle\Service\Client`` object is responsible +for executing commands. Commands created for your web service must implement +``Guzzle\Service\Command\CommandInterface``, but it's easier to extend the ``Guzzle\Service\Command\AbstractCommand`` +class, implement the ``build()`` method, and optionally implement the ``process()`` method. + +Serializing requests +^^^^^^^^^^^^^^^^^^^^ + +The ``build()`` method of a command is responsible for using the arguments of the command to build and serialize a +HTTP request and set the request on the ``$request`` property of the command object. This step is usually taken care of +for you when using a service description driven command that uses the default +``Guzzle\Service\Command\OperationCommand``. You may wish to implement the process method yourself when you aren't +using a service description or need to implement more complex request serialization. + +.. important:::: + + When implementing a custom ``build()`` method, be sure to set the class property of ``$this->request`` to an + instantiated and ready to send request. + +The following example shows how to implement the ``getMentions`` +`Twitter API `_ method using a concrete command. + +.. code-block:: php + + namespace mtdowling\Twitter\Command; + + use Guzzle\Service\Command\AbstractCommand; + + class GetMentions extends AbstractCommand + { + protected function build() + { + // Create the request property of the command + $this->request = $this->client->get('statuses/mentions_timeline.json'); + + // Grab the query object of the request because we will use it for + // serializing command parameters on the request + $query = $this->request->getQuery(); + + if ($this['count']) { + $query->set('count', $this['count']); + } + + if ($this['since_id']) { + $query->set('since_id', $this['since_id']); + } + + if ($this['max_id']) { + $query->set('max_id', $this['max_id']); + } + + if ($this['trim_user'] !== null) { + $query->set('trim_user', $this['trim_user'] ? 'true' : 'false'); + } + + if ($this['contributor_details'] !== null) { + $query->set('contributor_details', $this['contributor_details'] ? 'true' : 'false'); + } + + if ($this['include_entities'] !== null) { + $query->set('include_entities', $this['include_entities'] ? 'true' : 'false'); + } + } + } + +By default, a client will attempt to find concrete command classes under the ``Command`` namespace of a client. First +the client will attempt to find an exact match for the name of the command to the name of the command class. If an +exact match is not found, the client will calculate a class name using inflection. This is calculated based on the +folder hierarchy of a command and converting the CamelCased named commands into snake_case. Here are some examples on +how the command names are calculated: + +#. ``Foo\Command\JarJar`` **->** jar_jar +#. ``Foo\Command\Test`` **->** test +#. ``Foo\Command\People\GetCurrentPerson`` **->** people.get_current_person + +Notice how any sub-namespace beneath ``Command`` is converted from ``\`` to ``.`` (a period). CamelCasing is converted +to lowercased snake_casing (e.g. JarJar == jar_jar). + +Parsing responses +^^^^^^^^^^^^^^^^^ + +The ``process()`` method of a command is responsible for converting an HTTP response into something more useful. For +example, a service description operation that has specified a model object in the ``responseClass`` attribute of the +operation will set a ``Guzzle\Service\Resource\Model`` object as the result of the command. This behavior can be +completely modified as needed-- even if you are using operations and responseClass models. Simply implement a custom +``process()`` method that sets the ``$this->result`` class property to whatever you choose. You can reuse parts of the +default Guzzle response parsing functionality or get inspiration from existing code by using +``Guzzle\Service\Command\OperationResponseParser`` and ``Guzzle\Service\Command\DefaultResponseParser`` classes. + +If you do not implement a custom ``process()`` method and are not using a service description, then Guzzle will attempt +to guess how a response should be processed based on the Content-Type header of the response. Because the Twitter API +sets a ``Content-Type: application/json`` header on this response, we do not need to implement any custom response +parsing. + +Operation commands +~~~~~~~~~~~~~~~~~~ + +Operation commands are commands in which the serialization of an HTTP request and the parsing of an HTTP response are +driven by a Guzzle service description. Because request serialization, validation, and response parsing are +described using a DSL, creating operation commands is a much faster process than writing concrete commands. + +Creating operation commands for our Twitter client can remove a great deal of redundancy from the previous concrete +command, and allows for a deeper runtime introspection of the API. Here's an example service description we can use to +create the Twitter API client: + +.. code-block:: json + + { + "name": "Twitter", + "apiVersion": "1.1", + "baseUrl": "https://api.twitter.com/1.1", + "description": "Twitter REST API client", + "operations": { + "GetMentions": { + "httpMethod": "GET", + "uri": "statuses/mentions_timeline.json", + "summary": "Returns the 20 most recent mentions for the authenticating user.", + "responseClass": "GetMentionsOutput", + "parameters": { + "count": { + "description": "Specifies the number of tweets to try and retrieve", + "type": "integer", + "location": "query" + }, + "since_id": { + "description": "Returns results with an ID greater than the specified ID", + "type": "integer", + "location": "query" + }, + "max_id": { + "description": "Returns results with an ID less than or equal to the specified ID.", + "type": "integer", + "location": "query" + }, + "trim_user": { + "description": "Limits the amount of data returned for each user", + "type": "boolean", + "location": "query" + }, + "contributor_details": { + "description": "Adds more data to contributor elements", + "type": "boolean", + "location": "query" + }, + "include_entities": { + "description": "The entities node will be disincluded when set to false.", + "type": "boolean", + "location": "query" + } + } + } + }, + "models": { + "GetMentionsOutput": { + "type": "object", + "additionalProperties": { + "location": "json" + } + } + } + } + +If you're lazy, you can define the API in a less descriptive manner using ``additionalParameters``. +``additionalParameters`` define the serialization and validation rules of parameters that are not explicitly defined +in a service description. + +.. code-block:: json + + { + "name": "Twitter", + "apiVersion": "1.1", + "baseUrl": "https://api.twitter.com/1.1", + "description": "Twitter REST API client", + "operations": { + "GetMentions": { + "httpMethod": "GET", + "uri": "statuses/mentions_timeline.json", + "summary": "Returns the 20 most recent mentions for the authenticating user.", + "responseClass": "GetMentionsOutput", + "additionalParameters": { + "location": "query" + } + } + }, + "models": { + "GetMentionsOutput": { + "type": "object", + "additionalProperties": { + "location": "json" + } + } + } + } + +You should attach the service description to the client at the end of the client's factory method: + +.. code-block:: php + + // ... + class TwitterClient extends Client + { + public static function factory($config = array()) + { + // ... same code as before ... + + // Set the service description + $client->setDescription(ServiceDescription::factory('path/to/twitter.json')); + + return $client; + } + } + +The client can now use operations defined in the service description instead of requiring you to create concrete +command classes. Feel free to delete the concrete command class we created earlier. + +.. code-block:: php + + $jsonData = $twitter->getMentions(array( + 'count' => 5, + 'trim_user' => true + )); + +Executing commands in parallel +------------------------------ + +Much like HTTP requests, Guzzle allows you to send multiple commands in parallel. You can send commands in parallel by +passing an array of command objects to a client's ``execute()`` method. The client will serialize each request and +send them all in parallel. If an error is encountered during the transfer, then a +``Guzzle\Service\Exception\CommandTransferException`` is thrown, which allows you to retrieve a list of commands that +succeeded and a list of commands that failed. + +.. code-block:: php + + use Guzzle\Service\Exception\CommandTransferException; + + $commands = array(); + $commands[] = $twitter->getCommand('getMentions'); + $commands[] = $twitter->getCommand('otherCommandName'); + // etc... + + try { + $result = $client->execute($commands); + foreach ($result as $command) { + echo $command->getName() . ': ' . $command->getResponse()->getStatusCode() . "\n"; + } + } catch (CommandTransferException $e) { + // Get an array of the commands that succeeded + foreach ($e->getSuccessfulCommands() as $command) { + echo $command->getName() . " succeeded\n"; + } + // Get an array of the commands that failed + foreach ($e->getFailedCommands() as $command) { + echo $command->getName() . " failed\n"; + } + } + +.. note:: + + All commands executed from a client using an array must originate from the same client. + +Special command options +----------------------- + +Guzzle exposes several options that help to control how commands are validated, serialized, and parsed. +Command options can be specified when creating a command or in the ``command.params`` parameter in the +``Guzzle\Service\Client``. + +=========================== ============================================================================================ +command.request_options Option used to add :ref:`Request options ` to the request created by a + command +command.hidden_params An array of the names of parameters ignored by the ``additionalParameters`` parameter schema +command.disable_validation Set to true to disable JSON schema validation of the command's input parameters +command.response_processing Determines how the default response parser will parse the command. One of "raw" no parsing, + "model" (the default method used to parse commands using response models defined in service + descriptions) +command.headers (deprecated) Option used to specify custom headers. Use ``command.request_options`` instead +command.on_complete (deprecated) Option used to add an onComplete method to a command. Use + ``command.after_send`` event instead +command.response_body (deprecated) Option used to change the entity body used to store a response. + Use ``command.request_options`` instead +=========================== ============================================================================================ + +Advanced client configuration +============================= + +Default command parameters +-------------------------- + +When creating a client object, you can specify default command parameters to pass into all commands. Any key value pair +present in the ``command.params`` settings of a client will be added as default parameters to any command created +by the client. + +.. code-block:: php + + $client = new Guzzle\Service\Client(array( + 'command.params' => array( + 'default_1' => 'foo', + 'another' => 'bar' + ) + )); + +Magic methods +------------- + +Client objects will, by default, attempt to create and execute commands when a missing method is invoked on a client. +This powerful concept applies to both concrete commands and operation commands powered by a service description. This +makes it appear to the end user that you have defined actual methods on a client object, when in fact, the methods are +invoked using PHP's magic ``__call`` method. + +The ``__call`` method uses the ``getCommand()`` method of a client, which uses the client's internal +``Guzzle\Service\Command\Factory\FactoryInterface`` object. The default command factory allows you to instantiate +operations defined in a client's service description. The method in which a client determines which command to +execute is defined as follows: + +1. The client will first try to find a literal match for an operation in the service description. +2. If the literal match is not found, the client will try to uppercase the first character of the operation and find + the match again. +3. If a match is still not found, the command factory will inflect the method name from CamelCase to snake_case and + attempt to find a matching command. +4. If a command still does not match, an exception is thrown. + +.. code-block:: php + + // Use the magic method + $result = $twitter->getMentions(); + + // This is exactly the same as: + $result = $twitter->getCommand('getMentions')->execute(); + +You can disable magic methods on a client by passing ``false`` to the ``enableMagicMethod()`` method. + +Custom command factory +---------------------- + +A client by default uses the ``Guzzle\Service\Command\Factory\CompositeFactory`` which allows multiple command +factories to attempt to create a command by a certain name. The default CompositeFactory uses a ``ConcreteClassFactory`` +and a ``ServiceDescriptionFactory`` if a service description is specified on a client. You can specify a custom +command factory if your client requires custom command creation logic using the ``setCommandFactory()`` method of +a client. + +Custom resource Iterator factory +-------------------------------- + +Resource iterators can be retrieved from a client using the ``getIterator($name)`` method of a client. This method uses +a client's internal ``Guzzle\Service\Resource\ResourceIteratorFactoryInterface`` object. A client by default uses a +``Guzzle\Service\Resource\ResourceIteratorClassFactory`` to attempt to find concrete classes that implement resource +iterators. The default factory will first look for matching iterators in the ``Iterator`` subdirectory of the client +followed by the ``Model`` subdirectory of a client. Use the ``setResourceIteratorFactory()`` method of a client to +specify a custom resource iterator factory. + +Plugins and events +================== + +``Guzzle\Service\Client`` exposes various events that allow you to hook in custom logic. A client object owns a +``Symfony\Component\EventDispatcher\EventDispatcher`` object that can be accessed by calling +``$client->getEventDispatcher()``. You can use the event dispatcher to add listeners (a simple callback function) or +event subscribers (classes that listen to specific events of a dispatcher). + +.. _service-client-events: + +Events emitted from a Service Client +------------------------------------ + +A ``Guzzle\Service\Client`` object emits the following events: + ++------------------------------+--------------------------------------------+------------------------------------------+ +| Event name | Description | Event data | ++==============================+============================================+==========================================+ +| client.command.create | The client created a command object | * client: Client object | +| | | * command: Command object | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.before_prepare | Before a command is validated and built. | * command: Command being prepared | +| | This is also before a request is created. | | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.after_prepare | After a command instantiates and | * command: Command that was prepared | +| | configures its request object. | | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.before_send | The client is about to execute a prepared | * command: Command to execute | +| | command | | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.after_send | The client successfully completed | * command: The command that was executed | +| | executing a command | | ++------------------------------+--------------------------------------------+------------------------------------------+ +| command.parse_response | Called when ``responseType`` is ``class`` | * command: The command with a response | +| | and the response is about to be parsed. | about to be parsed. | ++------------------------------+--------------------------------------------+------------------------------------------+ + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Service\Client; + + $client = new Client(); + + // create an event listener that operates on request objects + $client->getEventDispatcher()->addListener('command.after_prepare', function (Event $event) { + $command = $event['command']; + $request = $command->getRequest(); + + // do something with request + }); + +.. code-block:: php + + use Guzzle\Common\Event; + use Guzzle\Common\Client; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + + class EventSubscriber implements EventSubscriberInterface + { + public static function getSubscribedEvents() + { + return array( + 'client.command.create' => 'onCommandCreate', + 'command.parse_response' => 'onParseResponse' + ); + } + + public function onCommandCreate(Event $event) + { + $client = $event['client']; + $command = $event['command']; + // operate on client and command + } + + public function onParseResponse(Event $event) + { + $command = $event['command']; + // operate on the command + } + } + + $client = new Client(); + + $client->addSubscriber(new EventSubscriber()); diff --git a/source/vendor/guzzle/guzzle/phar-stub.php b/source/vendor/guzzle/guzzle/phar-stub.php new file mode 100644 index 0000000..cc2b53f --- /dev/null +++ b/source/vendor/guzzle/guzzle/phar-stub.php @@ -0,0 +1,16 @@ +registerNamespaces(array( + 'Guzzle' => 'phar://guzzle.phar/src', + 'Symfony\\Component\\EventDispatcher' => 'phar://guzzle.phar/vendor/symfony/event-dispatcher', + 'Doctrine' => 'phar://guzzle.phar/vendor/doctrine/common/lib', + 'Monolog' => 'phar://guzzle.phar/vendor/monolog/monolog/src' +)); +$classLoader->register(); + +__HALT_COMPILER(); diff --git a/source/vendor/guzzle/guzzle/phing/build.properties.dist b/source/vendor/guzzle/guzzle/phing/build.properties.dist new file mode 100644 index 0000000..c60d3d9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/phing/build.properties.dist @@ -0,0 +1,16 @@ +# you may need to update this if you're working on a fork. +guzzle.remote=git@github.com:guzzle/guzzle.git + +# github credentials -- only used by GitHub API calls to create subtree repos +github.basicauth=username:password +# for the subtree split and testing +github.org=guzzle + +# your git path +cmd.git=git + +# your composer command +cmd.composer=composer + +# test server start +cmd.testserver=node diff --git a/source/vendor/guzzle/guzzle/phing/imports/dependencies.xml b/source/vendor/guzzle/guzzle/phing/imports/dependencies.xml new file mode 100644 index 0000000..e40e037 --- /dev/null +++ b/source/vendor/guzzle/guzzle/phing/imports/dependencies.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + using git at ${cmd.git} + + + + found git at ${cmd.git} + + + + + + + + + + diff --git a/source/vendor/guzzle/guzzle/phing/imports/deploy.xml b/source/vendor/guzzle/guzzle/phing/imports/deploy.xml new file mode 100644 index 0000000..109e5ec --- /dev/null +++ b/source/vendor/guzzle/guzzle/phing/imports/deploy.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + On branch ${head} + + + + + + + + + + working directory clean + + + ${git.status} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ChangeLog Match: ${version.changelog} + Guzzle\Common\Version Match: ${version.version} + + + + releasing: phing -Dnew.version=3.0.x -Dhead=master release + -- + + + + + + + + + + + + + + + BEGINNING RELEASE FOR ${new.version} + + + + + + + + + + + + + + + + + + + + + + + + Tip: to create a new release, do: phing -Dnew.version=[TAG] -Dhead=[BRANCH] release + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php b/source/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php new file mode 100644 index 0000000..3b70409 --- /dev/null +++ b/source/vendor/guzzle/guzzle/phing/tasks/ComposerLintTask.php @@ -0,0 +1,152 @@ + + * @license http://claylo.mit-license.org/2012/ MIT License + */ + +require_once 'phing/Task.php'; + +class ComposerLintTask extends Task +{ + protected $dir = null; + protected $file = null; + protected $passthru = false; + protected $composer = null; + + /** + * The setter for the dir + * + * @param string $str Directory to crawl recursively for composer files + */ + public function setDir($str) + { + $this->dir = $str; + } + + /** + * The setter for the file + * + * @param string $str Individual file to validate + */ + public function setFile($str) + { + $this->file = $str; + } + + /** + * Whether to use PHP's passthru() function instead of exec() + * + * @param boolean $passthru If passthru shall be used + */ + public function setPassthru($passthru) + { + $this->passthru = (bool) $passthru; + } + + /** + * Composer to execute. If unset, will attempt composer.phar in project + * basedir, and if that fails, will attempt global composer + * installation. + * + * @param string $str Individual file to validate + */ + public function setComposer($str) + { + $this->file = $str; + } + + /** + * The init method: do init steps + */ + public function init() + { + // nothing needed here + } + + /** + * The main entry point + */ + public function main() + { + if ($this->composer === null) { + $this->findComposer(); + } + + $files = array(); + if (!empty($this->file) && file_exists($this->file)) { + $files[] = $this->file; + } + + if (!empty($this->dir)) { + $found = $this->findFiles(); + foreach ($found as $file) { + $files[] = $this->dir . DIRECTORY_SEPARATOR . $file; + } + } + + foreach ($files as $file) { + + $cmd = $this->composer . ' validate ' . $file; + $cmd = escapeshellcmd($cmd); + + if ($this->passthru) { + $retval = null; + passthru($cmd, $retval); + if ($retval == 1) { + throw new BuildException('invalid composer.json'); + } + } else { + $out = array(); + $retval = null; + exec($cmd, $out, $retval); + if ($retval == 1) { + $err = join("\n", $out); + throw new BuildException($err); + } else { + $this->log($out[0]); + } + } + + } + + } + + /** + * Find the composer.json files using Phing's directory scanner + * + * @return array + */ + protected function findFiles() + { + $ds = new DirectoryScanner(); + $ds->setBasedir($this->dir); + $ds->setIncludes(array('**/composer.json')); + $ds->scan(); + return $ds->getIncludedFiles(); + } + + /** + * Find composer installation + * + */ + protected function findComposer() + { + $basedir = $this->project->getBasedir(); + $php = $this->project->getProperty('php.interpreter'); + + if (file_exists($basedir . '/composer.phar')) { + $this->composer = "$php $basedir/composer.phar"; + } else { + $out = array(); + exec('which composer', $out); + if (empty($out)) { + throw new BuildException( + 'Could not determine composer location.' + ); + } + $this->composer = $out[0]; + } + } +} diff --git a/source/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php b/source/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php new file mode 100644 index 0000000..f72a6b5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/phing/tasks/GuzzlePearPharPackageTask.php @@ -0,0 +1,338 @@ + + * @license http://claylo.mit-license.org/2012/ MIT License + */ + +require_once 'phing/Task.php'; +require_once 'PEAR/PackageFileManager2.php'; +require_once 'PEAR/PackageFileManager/File.php'; +require_once 'PEAR/Packager.php'; + +class GuzzlePearPharPackageTask extends Task +{ + private $version; + private $deploy = true; + private $makephar = true; + + private $subpackages = array(); + + public function setVersion($str) + { + $this->version = $str; + } + + public function getVersion() + { + return $this->version; + } + + public function setDeploy($deploy) + { + $this->deploy = (bool) $deploy; + } + + public function getDeploy() + { + return $this->deploy; + } + + public function setMakephar($makephar) + { + $this->makephar = (bool) $makephar; + } + + public function getMakephar() + { + return $this->makephar; + } + + private $basedir; + private $guzzleinfo; + private $changelog_release_date; + private $changelog_notes = '-'; + + public function main() + { + $this->basedir = $this->getProject()->getBasedir(); + + if (!is_dir((string) $this->basedir.'/.subsplit')) { + throw new BuildException('PEAR packaging requires .subsplit directory'); + } + + // main composer file + $composer_file = file_get_contents((string) $this->basedir.'/.subsplit/composer.json'); + $this->guzzleinfo = json_decode($composer_file, true); + + // make sure we have a target + $pearwork = (string) $this->basedir . '/build/pearwork'; + if (!is_dir($pearwork)) { + mkdir($pearwork, 0777, true); + } + $pearlogs = (string) $this->basedir . '/build/artifacts/logs'; + if (!is_dir($pearlogs)) { + mkdir($pearlogs, 0777, true); + } + + $version = $this->getVersion(); + $this->grabChangelog(); + if ($version[0] == '2') { + $this->log('building single PEAR package'); + $this->buildSinglePackage(); + } else { + // $this->log("building PEAR subpackages"); + // $this->createSubPackages(); + // $this->log("building PEAR bundle package"); + $this->buildSinglePackage(); + } + + if ($this->getMakephar()) { + $this->log("building PHAR"); + $this->getProject()->executeTarget('package-phar'); + } + + if ($this->getDeploy()) { + $this->doDeployment(); + } + } + + public function doDeployment() + { + $basedir = (string) $this->basedir; + $this->log('beginning PEAR/PHAR deployment'); + + chdir($basedir . '/build/pearwork'); + if (!is_dir('./channel')) { + mkdir('./channel'); + } + + // Pull the PEAR channel down locally + passthru('aws s3 sync s3://pear.guzzlephp.org ./channel'); + + // add PEAR packages + foreach (scandir('./') as $file) { + if (substr($file, -4) == '.tgz') { + passthru('pirum add ./channel ' . $file); + } + } + + // if we have a new phar, add it + if ($this->getMakephar() && file_exists($basedir . '/build/artifacts/guzzle.phar')) { + rename($basedir . '/build/artifacts/guzzle.phar', './channel/guzzle.phar'); + } + + // Sync up with the S3 bucket + chdir($basedir . '/build/pearwork/channel'); + passthru('aws s3 sync . s3://pear.guzzlephp.org'); + } + + public function buildSinglePackage() + { + $v = $this->getVersion(); + $apiversion = $v[0] . '.0.0'; + + $opts = array( + 'packagedirectory' => (string) $this->basedir . '/.subsplit/src/', + 'filelistgenerator' => 'file', + 'ignore' => array('*composer.json'), + 'baseinstalldir' => '/', + 'packagefile' => 'package.xml' + //'outputdirectory' => (string) $this->basedir . '/build/pearwork/' + ); + $pfm = new PEAR_PackageFileManager2(); + $pfm->setOptions($opts); + $pfm->addRole('md', 'doc'); + $pfm->addRole('pem', 'php'); + $pfm->setPackage('Guzzle'); + $pfm->setSummary("Object-oriented PHP HTTP Client for PHP 5.3+"); + $pfm->setDescription($this->guzzleinfo['description']); + $pfm->setPackageType('php'); + $pfm->setChannel('guzzlephp.org/pear'); + $pfm->setAPIVersion($apiversion); + $pfm->setReleaseVersion($this->getVersion()); + $pfm->setAPIStability('stable'); + $pfm->setReleaseStability('stable'); + $pfm->setNotes($this->changelog_notes); + $pfm->setPackageType('php'); + $pfm->setLicense('MIT', 'http://github.com/guzzle/guzzle/blob/master/LICENSE'); + $pfm->addMaintainer('lead', 'mtdowling', 'Michael Dowling', 'mtdowling@gmail.com', 'yes'); + $pfm->setDate($this->changelog_release_date); + $pfm->generateContents(); + + $phpdep = $this->guzzleinfo['require']['php']; + $phpdep = str_replace('>=', '', $phpdep); + $pfm->setPhpDep($phpdep); + $pfm->addExtensionDep('required', 'curl'); + $pfm->setPearinstallerDep('1.4.6'); + $pfm->addPackageDepWithChannel('required', 'EventDispatcher', 'pear.symfony.com', '2.1.0'); + if (!empty($this->subpackages)) { + foreach ($this->subpackages as $package) { + $pkg = dirname($package); + $pkg = str_replace('/', '_', $pkg); + $pfm->addConflictingPackageDepWithChannel($pkg, 'guzzlephp.org/pear', false, $apiversion); + } + } + + ob_start(); + $startdir = getcwd(); + chdir((string) $this->basedir . '/build/pearwork'); + + echo "DEBUGGING GENERATED PACKAGE FILE\n"; + $result = $pfm->debugPackageFile(); + if ($result) { + $out = $pfm->writePackageFile(); + echo "\n\n\nWRITE PACKAGE FILE RESULT:\n"; + var_dump($out); + // load up package file and build package + $packager = new PEAR_Packager(); + echo "\n\n\nBUILDING PACKAGE FROM PACKAGE FILE:\n"; + $dest_package = $packager->package($opts['packagedirectory'].'package.xml'); + var_dump($dest_package); + } else { + echo "\n\n\nDEBUGGING RESULT:\n"; + var_dump($result); + } + echo "removing package.xml"; + unlink($opts['packagedirectory'].'package.xml'); + $log = ob_get_clean(); + file_put_contents((string) $this->basedir . '/build/artifacts/logs/pear_package.log', $log); + chdir($startdir); + } + + public function createSubPackages() + { + $this->findComponents(); + + foreach ($this->subpackages as $package) { + $baseinstalldir = dirname($package); + $dir = (string) $this->basedir.'/.subsplit/src/' . $baseinstalldir; + $composer_file = file_get_contents((string) $this->basedir.'/.subsplit/src/'. $package); + $package_info = json_decode($composer_file, true); + $this->log('building ' . $package_info['target-dir'] . ' subpackage'); + $this->buildSubPackage($dir, $baseinstalldir, $package_info); + } + } + + public function buildSubPackage($dir, $baseinstalldir, $info) + { + $package = str_replace('/', '_', $baseinstalldir); + $opts = array( + 'packagedirectory' => $dir, + 'filelistgenerator' => 'file', + 'ignore' => array('*composer.json', '*package.xml'), + 'baseinstalldir' => '/' . $info['target-dir'], + 'packagefile' => 'package.xml' + ); + $pfm = new PEAR_PackageFileManager2(); + $pfm->setOptions($opts); + $pfm->setPackage($package); + $pfm->setSummary($info['description']); + $pfm->setDescription($info['description']); + $pfm->setPackageType('php'); + $pfm->setChannel('guzzlephp.org/pear'); + $pfm->setAPIVersion('3.0.0'); + $pfm->setReleaseVersion($this->getVersion()); + $pfm->setAPIStability('stable'); + $pfm->setReleaseStability('stable'); + $pfm->setNotes($this->changelog_notes); + $pfm->setPackageType('php'); + $pfm->setLicense('MIT', 'http://github.com/guzzle/guzzle/blob/master/LICENSE'); + $pfm->addMaintainer('lead', 'mtdowling', 'Michael Dowling', 'mtdowling@gmail.com', 'yes'); + $pfm->setDate($this->changelog_release_date); + $pfm->generateContents(); + + $phpdep = $this->guzzleinfo['require']['php']; + $phpdep = str_replace('>=', '', $phpdep); + $pfm->setPhpDep($phpdep); + $pfm->setPearinstallerDep('1.4.6'); + + foreach ($info['require'] as $type => $version) { + if ($type == 'php') { + continue; + } + if ($type == 'symfony/event-dispatcher') { + $pfm->addPackageDepWithChannel('required', 'EventDispatcher', 'pear.symfony.com', '2.1.0'); + } + if ($type == 'ext-curl') { + $pfm->addExtensionDep('required', 'curl'); + } + if (substr($type, 0, 6) == 'guzzle') { + $gdep = str_replace('/', ' ', $type); + $gdep = ucwords($gdep); + $gdep = str_replace(' ', '_', $gdep); + $pfm->addPackageDepWithChannel('required', $gdep, 'guzzlephp.org/pear', $this->getVersion()); + } + } + + // can't have main Guzzle package AND sub-packages + $pfm->addConflictingPackageDepWithChannel('Guzzle', 'guzzlephp.org/pear', false, $apiversion); + + ob_start(); + $startdir = getcwd(); + chdir((string) $this->basedir . '/build/pearwork'); + + echo "DEBUGGING GENERATED PACKAGE FILE\n"; + $result = $pfm->debugPackageFile(); + if ($result) { + $out = $pfm->writePackageFile(); + echo "\n\n\nWRITE PACKAGE FILE RESULT:\n"; + var_dump($out); + // load up package file and build package + $packager = new PEAR_Packager(); + echo "\n\n\nBUILDING PACKAGE FROM PACKAGE FILE:\n"; + $dest_package = $packager->package($opts['packagedirectory'].'/package.xml'); + var_dump($dest_package); + } else { + echo "\n\n\nDEBUGGING RESULT:\n"; + var_dump($result); + } + echo "removing package.xml"; + unlink($opts['packagedirectory'].'/package.xml'); + $log = ob_get_clean(); + file_put_contents((string) $this->basedir . '/build/artifacts/logs/pear_package_'.$package.'.log', $log); + chdir($startdir); + } + + public function findComponents() + { + $ds = new DirectoryScanner(); + $ds->setBasedir((string) $this->basedir.'/.subsplit/src'); + $ds->setIncludes(array('**/composer.json')); + $ds->scan(); + $files = $ds->getIncludedFiles(); + $this->subpackages = $files; + } + + public function grabChangelog() + { + $cl = file((string) $this->basedir.'/.subsplit/CHANGELOG.md'); + $notes = ''; + $in_version = false; + $release_date = null; + + foreach ($cl as $line) { + $line = trim($line); + if (preg_match('/^\* '.$this->getVersion().' \(([0-9\-]+)\)$/', $line, $matches)) { + $release_date = $matches[1]; + $in_version = true; + continue; + } + if ($in_version && empty($line) && empty($notes)) { + continue; + } + if ($in_version && ! empty($line)) { + $notes .= $line."\n"; + } + if ($in_version && empty($line) && !empty($notes)) { + $in_version = false; + } + } + $this->changelog_release_date = $release_date; + + if (! empty($notes)) { + $this->changelog_notes = $notes; + } + } +} diff --git a/source/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php b/source/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php new file mode 100644 index 0000000..5d56a5b --- /dev/null +++ b/source/vendor/guzzle/guzzle/phing/tasks/GuzzleSubSplitTask.php @@ -0,0 +1,385 @@ + + * @license http://claylo.mit-license.org/2012/ MIT License + */ + +require_once 'phing/tasks/ext/git/GitBaseTask.php'; + +// base - base of tree to split out +// subIndicatorFile - composer.json, package.xml? +class GuzzleSubSplitTask extends GitBaseTask +{ + /** + * What git repository to pull from and publish to + */ + protected $remote = null; + + /** + * Publish for comma-separated heads instead of all heads + */ + protected $heads = null; + + /** + * Publish for comma-separated tags instead of all tags + */ + protected $tags = null; + + /** + * Base of the tree RELATIVE TO .subsplit working dir + */ + protected $base = null; + + /** + * The presence of this file will indicate that the directory it resides + * in is at the top level of a split. + */ + protected $subIndicatorFile = 'composer.json'; + + /** + * Do everything except actually send the update. + */ + protected $dryRun = null; + + /** + * Do not sync any heads. + */ + protected $noHeads = false; + + /** + * Do not sync any tags. + */ + protected $noTags = false; + + /** + * The splits we found in the heads + */ + protected $splits; + + public function setRemote($str) + { + $this->remote = $str; + } + + public function getRemote() + { + return $this->remote; + } + + public function setHeads($str) + { + $this->heads = explode(',', $str); + } + + public function getHeads() + { + return $this->heads; + } + + public function setTags($str) + { + $this->tags = explode(',', $str); + } + + public function getTags() + { + return $this->tags; + } + + public function setBase($str) + { + $this->base = $str; + } + + public function getBase() + { + return $this->base; + } + + public function setSubIndicatorFile($str) + { + $this->subIndicatorFile = $str; + } + + public function getSubIndicatorFile() + { + return $this->subIndicatorFile; + } + + public function setDryRun($bool) + { + $this->dryRun = (bool) $bool; + } + + public function getDryRun() + { + return $this->dryRun; + } + + public function setNoHeads($bool) + { + $this->noHeads = (bool) $bool; + } + + public function getNoHeads() + { + return $this->noHeads; + } + + public function setNoTags($bool) + { + $this->noTags = (bool) $bool; + } + + public function getNoTags() + { + return $this->noTags; + } + + /** + * GitClient from VersionControl_Git + */ + protected $client = null; + + /** + * The main entry point + */ + public function main() + { + $repo = $this->getRepository(); + if (empty($repo)) { + throw new BuildException('"repository" is a required parameter'); + } + + $remote = $this->getRemote(); + if (empty($remote)) { + throw new BuildException('"remote" is a required parameter'); + } + + chdir($repo); + $this->client = $this->getGitClient(false, $repo); + + // initalized yet? + if (!is_dir('.subsplit')) { + $this->subsplitInit(); + } else { + // update + $this->subsplitUpdate(); + } + + // find all splits based on heads requested + $this->findSplits(); + + // check that GitHub has the repos + $this->verifyRepos(); + + // execute the subsplits + $this->publish(); + } + + public function publish() + { + $this->log('DRY RUN ONLY FOR NOW'); + $base = $this->getBase(); + $base = rtrim($base, '/') . '/'; + $org = $this->getOwningTarget()->getProject()->getProperty('github.org'); + + $splits = array(); + + $heads = $this->getHeads(); + foreach ($heads as $head) { + foreach ($this->splits[$head] as $component => $meta) { + $splits[] = $base . $component . ':git@github.com:'. $org.'/'.$meta['repo']; + } + + $cmd = 'git subsplit publish '; + $cmd .= escapeshellarg(implode(' ', $splits)); + + if ($this->getNoHeads()) { + $cmd .= ' --no-heads'; + } else { + $cmd .= ' --heads='.$head; + } + + if ($this->getNoTags()) { + $cmd .= ' --no-tags'; + } else { + if ($this->getTags()) { + $cmd .= ' --tags=' . escapeshellarg(implode(' ', $this->getTags())); + } + } + + passthru($cmd); + } + } + + /** + * Runs `git subsplit update` + */ + public function subsplitUpdate() + { + $repo = $this->getRepository(); + $this->log('git-subsplit update...'); + $cmd = $this->client->getCommand('subsplit'); + $cmd->addArgument('update'); + try { + $cmd->execute(); + } catch (Exception $e) { + throw new BuildException('git subsplit update failed'. $e); + } + chdir($repo . '/.subsplit'); + passthru('php ../composer.phar update --dev'); + chdir($repo); + } + + /** + * Runs `git subsplit init` based on the remote repository. + */ + public function subsplitInit() + { + $remote = $this->getRemote(); + $cmd = $this->client->getCommand('subsplit'); + $this->log('running git-subsplit init ' . $remote); + + $cmd->setArguments(array( + 'init', + $remote + )); + + try { + $output = $cmd->execute(); + } catch (Exception $e) { + throw new BuildException('git subsplit init failed'. $e); + } + $this->log(trim($output), Project::MSG_INFO); + $repo = $this->getRepository(); + chdir($repo . '/.subsplit'); + passthru('php ../composer.phar install --dev'); + chdir($repo); + } + + /** + * Find the composer.json files using Phing's directory scanner + * + * @return array + */ + protected function findSplits() + { + $this->log("checking heads for subsplits"); + $repo = $this->getRepository(); + $base = $this->getBase(); + + $splits = array(); + $heads = $this->getHeads(); + + if (!empty($base)) { + $base = '/' . ltrim($base, '/'); + } else { + $base = '/'; + } + + chdir($repo . '/.subsplit'); + foreach ($heads as $head) { + $splits[$head] = array(); + + // check each head requested *BEFORE* the actual subtree split command gets it + passthru("git checkout '$head'"); + $ds = new DirectoryScanner(); + $ds->setBasedir($repo . '/.subsplit' . $base); + $ds->setIncludes(array('**/'.$this->subIndicatorFile)); + $ds->scan(); + $files = $ds->getIncludedFiles(); + + // Process the files we found + foreach ($files as $file) { + $pkg = file_get_contents($repo . '/.subsplit' . $base .'/'. $file); + $pkg_json = json_decode($pkg, true); + $name = $pkg_json['name']; + $component = str_replace('/composer.json', '', $file); + // keep this for split cmd + $tmpreponame = explode('/', $name); + $reponame = $tmpreponame[1]; + $splits[$head][$component]['repo'] = $reponame; + $nscomponent = str_replace('/', '\\', $component); + $splits[$head][$component]['desc'] = "[READ ONLY] Subtree split of $nscomponent: " . $pkg_json['description']; + } + } + + // go back to how we found it + passthru("git checkout master"); + chdir($repo); + $this->splits = $splits; + } + + /** + * Based on list of repositories we determined we *should* have, talk + * to GitHub and make sure they're all there. + * + */ + protected function verifyRepos() + { + $this->log('verifying GitHub target repos'); + $github_org = $this->getOwningTarget()->getProject()->getProperty('github.org'); + $github_creds = $this->getOwningTarget()->getProject()->getProperty('github.basicauth'); + + if ($github_creds == 'username:password') { + $this->log('Skipping GitHub repo checks. Update github.basicauth in build.properties to verify repos.', 1); + return; + } + + $ch = curl_init('https://api.github.com/orgs/'.$github_org.'/repos?type=all'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERPWD, $github_creds); + // change this when we know we can use our bundled CA bundle! + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $result = curl_exec($ch); + curl_close($ch); + $repos = json_decode($result, true); + $existing_repos = array(); + + // parse out the repos we found on GitHub + foreach ($repos as $repo) { + $tmpreponame = explode('/', $repo['full_name']); + $reponame = $tmpreponame[1]; + $existing_repos[$reponame] = $repo['description']; + } + + $heads = $this->getHeads(); + foreach ($heads as $head) { + foreach ($this->splits[$head] as $component => $meta) { + + $reponame = $meta['repo']; + + if (!isset($existing_repos[$reponame])) { + $this->log("Creating missing repo $reponame"); + $payload = array( + 'name' => $reponame, + 'description' => $meta['desc'], + 'homepage' => 'http://www.guzzlephp.org/', + 'private' => true, + 'has_issues' => false, + 'has_wiki' => false, + 'has_downloads' => true, + 'auto_init' => false + ); + $ch = curl_init('https://api.github.com/orgs/'.$github_org.'/repos'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERPWD, $github_creds); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + // change this when we know we can use our bundled CA bundle! + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $result = curl_exec($ch); + echo "Response code: ".curl_getinfo($ch, CURLINFO_HTTP_CODE)."\n"; + curl_close($ch); + } else { + $this->log("Repo $reponame exists", 2); + } + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/phpunit.xml.dist b/source/vendor/guzzle/guzzle/phpunit.xml.dist new file mode 100644 index 0000000..208fdc0 --- /dev/null +++ b/source/vendor/guzzle/guzzle/phpunit.xml.dist @@ -0,0 +1,48 @@ + + + + + + ./tests/Guzzle/Tests + + + + + + + + + + ./src/Guzzle + + ./src/Guzzle + ./src/Guzzle/Common/Exception/GuzzleException.php + ./src/Guzzle/Http/Exception/HttpException.php + ./src/Guzzle/Http/Exception/ServerErrorResponseException.php + ./src/Guzzle/Http/Exception/ClientErrorResponseException.php + ./src/Guzzle/Http/Exception/TooManyRedirectsException.php + ./src/Guzzle/Http/Exception/CouldNotRewindStreamException.php + ./src/Guzzle/Common/Exception/BadMethodCallException.php + ./src/Guzzle/Common/Exception/InvalidArgumentException.php + ./src/Guzzle/Common/Exception/RuntimeException.php + ./src/Guzzle/Common/Exception/UnexpectedValueException.php + ./src/Guzzle/Service/Exception/ClientNotFoundException.php + ./src/Guzzle/Service/Exception/CommandException.php + ./src/Guzzle/Service/Exception/DescriptionBuilderException.php + ./src/Guzzle/Service/Exception/ServiceBuilderException.php + ./src/Guzzle/Service/Exception/ServiceNotFoundException.php + ./src/Guzzle/Service/Exception/ValidationException.php + ./src/Guzzle/Service/Exception/JsonException.php + + + + + diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php new file mode 100644 index 0000000..0625d71 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/AbstractBatchDecorator.php @@ -0,0 +1,66 @@ +decoratedBatch = $decoratedBatch; + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + * @codeCoverageIgnore + */ + public function __call($method, array $args) + { + return call_user_func_array(array($this->decoratedBatch, $method), $args); + } + + public function add($item) + { + $this->decoratedBatch->add($item); + + return $this; + } + + public function flush() + { + return $this->decoratedBatch->flush(); + } + + public function isEmpty() + { + return $this->decoratedBatch->isEmpty(); + } + + /** + * Trace the decorators associated with the batch + * + * @return array + */ + public function getDecorators() + { + $found = array($this); + if (method_exists($this->decoratedBatch, 'getDecorators')) { + $found = array_merge($found, $this->decoratedBatch->getDecorators()); + } + + return $found; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php new file mode 100644 index 0000000..4d41c54 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/Batch.php @@ -0,0 +1,92 @@ +transferStrategy = $transferStrategy; + $this->divisionStrategy = $divisionStrategy; + $this->queue = new \SplQueue(); + $this->queue->setIteratorMode(\SplQueue::IT_MODE_DELETE); + $this->dividedBatches = array(); + } + + public function add($item) + { + $this->queue->enqueue($item); + + return $this; + } + + public function flush() + { + $this->createBatches(); + + $items = array(); + foreach ($this->dividedBatches as $batchIndex => $dividedBatch) { + while ($dividedBatch->valid()) { + $batch = $dividedBatch->current(); + $dividedBatch->next(); + try { + $this->transferStrategy->transfer($batch); + $items = array_merge($items, $batch); + } catch (\Exception $e) { + throw new BatchTransferException($batch, $items, $e, $this->transferStrategy, $this->divisionStrategy); + } + } + // Keep the divided batch down to a minimum in case of a later exception + unset($this->dividedBatches[$batchIndex]); + } + + return $items; + } + + public function isEmpty() + { + return count($this->queue) == 0 && count($this->dividedBatches) == 0; + } + + /** + * Create batches for any queued items + */ + protected function createBatches() + { + if (count($this->queue)) { + if ($batches = $this->divisionStrategy->createBatches($this->queue)) { + // Convert arrays into iterators + if (is_array($batches)) { + $batches = new \ArrayIterator($batches); + } + $this->dividedBatches[] = $batches; + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php new file mode 100644 index 0000000..ea99b4d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchBuilder.php @@ -0,0 +1,199 @@ + 'Guzzle\Batch\BatchRequestTransfer', + 'command' => 'Guzzle\Batch\BatchCommandTransfer' + ); + + /** + * Create a new instance of the BatchBuilder + * + * @return BatchBuilder + */ + public static function factory() + { + return new self(); + } + + /** + * Automatically flush the batch when the size of the queue reaches a certain threshold. Adds {@see FlushingBatch}. + * + * @param $threshold Number of items to allow in the queue before a flush + * + * @return BatchBuilder + */ + public function autoFlushAt($threshold) + { + $this->autoFlush = $threshold; + + return $this; + } + + /** + * Maintain a history of all items that have been transferred using the batch. Adds {@see HistoryBatch}. + * + * @return BatchBuilder + */ + public function keepHistory() + { + $this->history = true; + + return $this; + } + + /** + * Buffer exceptions thrown during transfer so that you can transfer as much as possible, and after a transfer + * completes, inspect each exception that was thrown. Enables the {@see ExceptionBufferingBatch} decorator. + * + * @return BatchBuilder + */ + public function bufferExceptions() + { + $this->exceptionBuffering = true; + + return $this; + } + + /** + * Notify a callable each time a batch flush completes. Enables the {@see NotifyingBatch} decorator. + * + * @param mixed $callable Callable function to notify + * + * @return BatchBuilder + * @throws InvalidArgumentException if the argument is not callable + */ + public function notify($callable) + { + $this->afterFlush = $callable; + + return $this; + } + + /** + * Configures the batch to transfer batches of requests. Associates a {@see \Guzzle\Http\BatchRequestTransfer} + * object as both the transfer and divisor strategy. + * + * @param int $batchSize Batch size for each batch of requests + * + * @return BatchBuilder + */ + public function transferRequests($batchSize = 50) + { + $className = self::$mapping['request']; + $this->transferStrategy = new $className($batchSize); + $this->divisorStrategy = $this->transferStrategy; + + return $this; + } + + /** + * Configures the batch to transfer batches commands. Associates as + * {@see \Guzzle\Service\Command\BatchCommandTransfer} as both the transfer and divisor strategy. + * + * @param int $batchSize Batch size for each batch of commands + * + * @return BatchBuilder + */ + public function transferCommands($batchSize = 50) + { + $className = self::$mapping['command']; + $this->transferStrategy = new $className($batchSize); + $this->divisorStrategy = $this->transferStrategy; + + return $this; + } + + /** + * Specify the strategy used to divide the queue into an array of batches + * + * @param BatchDivisorInterface $divisorStrategy Strategy used to divide a batch queue into batches + * + * @return BatchBuilder + */ + public function createBatchesWith(BatchDivisorInterface $divisorStrategy) + { + $this->divisorStrategy = $divisorStrategy; + + return $this; + } + + /** + * Specify the strategy used to transport the items when flush is called + * + * @param BatchTransferInterface $transferStrategy How items are transferred + * + * @return BatchBuilder + */ + public function transferWith(BatchTransferInterface $transferStrategy) + { + $this->transferStrategy = $transferStrategy; + + return $this; + } + + /** + * Create and return the instantiated batch + * + * @return BatchInterface + * @throws RuntimeException if no transfer strategy has been specified + */ + public function build() + { + if (!$this->transferStrategy) { + throw new RuntimeException('No transfer strategy has been specified'); + } + + if (!$this->divisorStrategy) { + throw new RuntimeException('No divisor strategy has been specified'); + } + + $batch = new Batch($this->transferStrategy, $this->divisorStrategy); + + if ($this->exceptionBuffering) { + $batch = new ExceptionBufferingBatch($batch); + } + + if ($this->afterFlush) { + $batch = new NotifyingBatch($batch, $this->afterFlush); + } + + if ($this->autoFlush) { + $batch = new FlushingBatch($batch, $this->autoFlush); + } + + if ($this->history) { + $batch = new HistoryBatch($batch); + } + + return $batch; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php new file mode 100644 index 0000000..e0a2d95 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureDivisor.php @@ -0,0 +1,39 @@ +callable = $callable; + $this->context = $context; + } + + public function createBatches(\SplQueue $queue) + { + return call_user_func($this->callable, $queue, $this->context); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php new file mode 100644 index 0000000..9cbf1ab --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchClosureTransfer.php @@ -0,0 +1,40 @@ +callable = $callable; + $this->context = $context; + } + + public function transfer(array $batch) + { + return empty($batch) ? null : call_user_func($this->callable, $batch, $this->context); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php new file mode 100644 index 0000000..d55ac7d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchCommandTransfer.php @@ -0,0 +1,75 @@ +batchSize = $batchSize; + } + + /** + * Creates batches by grouping commands by their associated client + * {@inheritdoc} + */ + public function createBatches(\SplQueue $queue) + { + $groups = new \SplObjectStorage(); + foreach ($queue as $item) { + if (!$item instanceof CommandInterface) { + throw new InvalidArgumentException('All items must implement Guzzle\Service\Command\CommandInterface'); + } + $client = $item->getClient(); + if (!$groups->contains($client)) { + $groups->attach($client, new \ArrayObject(array($item))); + } else { + $groups[$client]->append($item); + } + } + + $batches = array(); + foreach ($groups as $batch) { + $batches = array_merge($batches, array_chunk($groups[$batch]->getArrayCopy(), $this->batchSize)); + } + + return $batches; + } + + public function transfer(array $batch) + { + if (empty($batch)) { + return; + } + + // Get the client of the first found command + $client = reset($batch)->getClient(); + + // Keep a list of all commands with invalid clients + $invalid = array_filter($batch, function ($command) use ($client) { + return $command->getClient() !== $client; + }); + + if (!empty($invalid)) { + throw new InconsistentClientTransferException($invalid); + } + + $client->execute($batch); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php new file mode 100644 index 0000000..0214f05 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchDivisorInterface.php @@ -0,0 +1,18 @@ +batchSize = $batchSize; + } + + /** + * Creates batches of requests by grouping requests by their associated curl multi object. + * {@inheritdoc} + */ + public function createBatches(\SplQueue $queue) + { + // Create batches by client objects + $groups = new \SplObjectStorage(); + foreach ($queue as $item) { + if (!$item instanceof RequestInterface) { + throw new InvalidArgumentException('All items must implement Guzzle\Http\Message\RequestInterface'); + } + $client = $item->getClient(); + if (!$groups->contains($client)) { + $groups->attach($client, array($item)); + } else { + $current = $groups[$client]; + $current[] = $item; + $groups[$client] = $current; + } + } + + $batches = array(); + foreach ($groups as $batch) { + $batches = array_merge($batches, array_chunk($groups[$batch], $this->batchSize)); + } + + return $batches; + } + + public function transfer(array $batch) + { + if ($batch) { + reset($batch)->getClient()->send($batch); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php new file mode 100644 index 0000000..67f90a5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchSizeDivisor.php @@ -0,0 +1,47 @@ +size = $size; + } + + /** + * Set the size of each batch + * + * @param int $size Size of each batch + * + * @return BatchSizeDivisor + */ + public function setSize($size) + { + $this->size = $size; + + return $this; + } + + /** + * Get the size of each batch + * + * @return int + */ + public function getSize() + { + return $this->size; + } + + public function createBatches(\SplQueue $queue) + { + return array_chunk(iterator_to_array($queue, false), $this->size); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php new file mode 100644 index 0000000..2e0b60d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/BatchTransferInterface.php @@ -0,0 +1,16 @@ +batch = $batch; + $this->transferredItems = $transferredItems; + $this->transferStrategy = $transferStrategy; + $this->divisorStrategy = $divisorStrategy; + parent::__construct( + 'Exception encountered while transferring batch: ' . $exception->getMessage(), + $exception->getCode(), + $exception + ); + } + + /** + * Get the batch that we being sent when the exception occurred + * + * @return array + */ + public function getBatch() + { + return $this->batch; + } + + /** + * Get the items transferred at the point in which the exception was encountered + * + * @return array + */ + public function getTransferredItems() + { + return $this->transferredItems; + } + + /** + * Get the transfer strategy + * + * @return TransferStrategy + */ + public function getTransferStrategy() + { + return $this->transferStrategy; + } + + /** + * Get the divisor strategy + * + * @return DivisorStrategy + */ + public function getDivisorStrategy() + { + return $this->divisorStrategy; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php new file mode 100644 index 0000000..d7a8928 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/ExceptionBufferingBatch.php @@ -0,0 +1,50 @@ +decoratedBatch->isEmpty()) { + try { + $transferredItems = $this->decoratedBatch->flush(); + } catch (BatchTransferException $e) { + $this->exceptions[] = $e; + $transferredItems = $e->getTransferredItems(); + } + $items = array_merge($items, $transferredItems); + } + + return $items; + } + + /** + * Get the buffered exceptions + * + * @return array Array of BatchTransferException objects + */ + public function getExceptions() + { + return $this->exceptions; + } + + /** + * Clear the buffered exceptions + */ + public function clearExceptions() + { + $this->exceptions = array(); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php new file mode 100644 index 0000000..367b684 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/FlushingBatch.php @@ -0,0 +1,60 @@ +threshold = $threshold; + parent::__construct($decoratedBatch); + } + + /** + * Set the auto-flush threshold + * + * @param int $threshold The auto-flush threshold + * + * @return FlushingBatch + */ + public function setThreshold($threshold) + { + $this->threshold = $threshold; + + return $this; + } + + /** + * Get the auto-flush threshold + * + * @return int + */ + public function getThreshold() + { + return $this->threshold; + } + + public function add($item) + { + $this->decoratedBatch->add($item); + if (++$this->currentTotal >= $this->threshold) { + $this->currentTotal = 0; + $this->decoratedBatch->flush(); + } + + return $this; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php new file mode 100644 index 0000000..e345fdc --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/HistoryBatch.php @@ -0,0 +1,39 @@ +history[] = $item; + $this->decoratedBatch->add($item); + + return $this; + } + + /** + * Get the batch history + * + * @return array + */ + public function getHistory() + { + return $this->history; + } + + /** + * Clear the batch history + */ + public function clearHistory() + { + $this->history = array(); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php new file mode 100644 index 0000000..96d04da --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/NotifyingBatch.php @@ -0,0 +1,38 @@ +callable = $callable; + parent::__construct($decoratedBatch); + } + + public function flush() + { + $items = $this->decoratedBatch->flush(); + call_user_func($this->callable, $items); + + return $items; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json new file mode 100644 index 0000000..12404d3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Batch/composer.json @@ -0,0 +1,31 @@ +{ + "name": "guzzle/batch", + "description": "Guzzle batch component for batching requests, commands, or custom transfers", + "homepage": "http://guzzlephp.org/", + "keywords": ["batch", "HTTP", "REST", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/common": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Batch": "" } + }, + "suggest": { + "guzzle/http": "self.version", + "guzzle/service": "self.version" + }, + "target-dir": "Guzzle/Batch", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php new file mode 100644 index 0000000..a5c5271 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/AbstractCacheAdapter.php @@ -0,0 +1,21 @@ +cache; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php new file mode 100644 index 0000000..94e6234 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterFactory.php @@ -0,0 +1,117 @@ +newInstanceArgs($args); + } + } catch (\Exception $e) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php new file mode 100644 index 0000000..970c9e2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/CacheAdapterInterface.php @@ -0,0 +1,55 @@ +callables = $callables; + } + + public function contains($id, array $options = null) + { + return call_user_func($this->callables['contains'], $id, $options); + } + + public function delete($id, array $options = null) + { + return call_user_func($this->callables['delete'], $id, $options); + } + + public function fetch($id, array $options = null) + { + return call_user_func($this->callables['fetch'], $id, $options); + } + + public function save($id, $data, $lifeTime = false, array $options = null) + { + return call_user_func($this->callables['save'], $id, $data, $lifeTime, $options); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php new file mode 100644 index 0000000..e1aaf9f --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/DoctrineCacheAdapter.php @@ -0,0 +1,41 @@ +cache = $cache; + } + + public function contains($id, array $options = null) + { + return $this->cache->contains($id); + } + + public function delete($id, array $options = null) + { + return $this->cache->delete($id); + } + + public function fetch($id, array $options = null) + { + return $this->cache->fetch($id); + } + + public function save($id, $data, $lifeTime = false, array $options = null) + { + return $this->cache->save($id, $data, $lifeTime !== false ? $lifeTime : 0); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php new file mode 100644 index 0000000..68bd4af --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/NullCacheAdapter.php @@ -0,0 +1,31 @@ +cache = $cache; + } + + public function contains($id, array $options = null) + { + return $this->cache->test($id); + } + + public function delete($id, array $options = null) + { + return $this->cache->remove($id); + } + + public function fetch($id, array $options = null) + { + return $this->cache->load($id); + } + + public function save($id, $data, $lifeTime = false, array $options = null) + { + return $this->cache->save($data, $id, array(), $lifeTime); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php new file mode 100644 index 0000000..1fc18a5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/Zf2CacheAdapter.php @@ -0,0 +1,41 @@ +cache = $cache; + } + + public function contains($id, array $options = null) + { + return $this->cache->hasItem($id); + } + + public function delete($id, array $options = null) + { + return $this->cache->removeItem($id); + } + + public function fetch($id, array $options = null) + { + return $this->cache->getItem($id); + } + + public function save($id, $data, $lifeTime = false, array $options = null) + { + return $this->cache->setItem($id, $data); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json new file mode 100644 index 0000000..a5d999b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Cache/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/cache", + "description": "Guzzle cache adapter component", + "homepage": "http://guzzlephp.org/", + "keywords": ["cache", "adapter", "zf", "doctrine", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/common": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Cache": "" } + }, + "target-dir": "Guzzle/Cache", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php b/source/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php new file mode 100644 index 0000000..d1e842b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Common/AbstractHasDispatcher.php @@ -0,0 +1,49 @@ +eventDispatcher = $eventDispatcher; + + return $this; + } + + public function getEventDispatcher() + { + if (!$this->eventDispatcher) { + $this->eventDispatcher = new EventDispatcher(); + } + + return $this->eventDispatcher; + } + + public function dispatch($eventName, array $context = array()) + { + return $this->getEventDispatcher()->dispatch($eventName, new Event($context)); + } + + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->getEventDispatcher()->addSubscriber($subscriber); + + return $this; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php b/source/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php new file mode 100644 index 0000000..5cb1535 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Common/Collection.php @@ -0,0 +1,403 @@ +data = $data; + } + + /** + * Create a new collection from an array, validate the keys, and add default values where missing + * + * @param array $config Configuration values to apply. + * @param array $defaults Default parameters + * @param array $required Required parameter names + * + * @return self + * @throws InvalidArgumentException if a parameter is missing + */ + public static function fromConfig(array $config = array(), array $defaults = array(), array $required = array()) + { + $data = $config + $defaults; + + if ($missing = array_diff($required, array_keys($data))) { + throw new InvalidArgumentException('Config is missing the following keys: ' . implode(', ', $missing)); + } + + return new self($data); + } + + public function count() + { + return count($this->data); + } + + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + public function toArray() + { + return $this->data; + } + + /** + * Removes all key value pairs + * + * @return Collection + */ + public function clear() + { + $this->data = array(); + + return $this; + } + + /** + * Get all or a subset of matching key value pairs + * + * @param array $keys Pass an array of keys to retrieve only a subset of key value pairs + * + * @return array Returns an array of all matching key value pairs + */ + public function getAll(array $keys = null) + { + return $keys ? array_intersect_key($this->data, array_flip($keys)) : $this->data; + } + + /** + * Get a specific key value. + * + * @param string $key Key to retrieve. + * + * @return mixed|null Value of the key or NULL + */ + public function get($key) + { + return isset($this->data[$key]) ? $this->data[$key] : null; + } + + /** + * Set a key value pair + * + * @param string $key Key to set + * @param mixed $value Value to set + * + * @return Collection Returns a reference to the object + */ + public function set($key, $value) + { + $this->data[$key] = $value; + + return $this; + } + + /** + * Add a value to a key. If a key of the same name has already been added, the key value will be converted into an + * array and the new value will be pushed to the end of the array. + * + * @param string $key Key to add + * @param mixed $value Value to add to the key + * + * @return Collection Returns a reference to the object. + */ + public function add($key, $value) + { + if (!array_key_exists($key, $this->data)) { + $this->data[$key] = $value; + } elseif (is_array($this->data[$key])) { + $this->data[$key][] = $value; + } else { + $this->data[$key] = array($this->data[$key], $value); + } + + return $this; + } + + /** + * Remove a specific key value pair + * + * @param string $key A key to remove + * + * @return Collection + */ + public function remove($key) + { + unset($this->data[$key]); + + return $this; + } + + /** + * Get all keys in the collection + * + * @return array + */ + public function getKeys() + { + return array_keys($this->data); + } + + /** + * Returns whether or not the specified key is present. + * + * @param string $key The key for which to check the existence. + * + * @return bool + */ + public function hasKey($key) + { + return array_key_exists($key, $this->data); + } + + /** + * Case insensitive search the keys in the collection + * + * @param string $key Key to search for + * + * @return bool|string Returns false if not found, otherwise returns the key + */ + public function keySearch($key) + { + foreach (array_keys($this->data) as $k) { + if (!strcasecmp($k, $key)) { + return $k; + } + } + + return false; + } + + /** + * Checks if any keys contains a certain value + * + * @param string $value Value to search for + * + * @return mixed Returns the key if the value was found FALSE if the value was not found. + */ + public function hasValue($value) + { + return array_search($value, $this->data); + } + + /** + * Replace the data of the object with the value of an array + * + * @param array $data Associative array of data + * + * @return Collection Returns a reference to the object + */ + public function replace(array $data) + { + $this->data = $data; + + return $this; + } + + /** + * Add and merge in a Collection or array of key value pair data. + * + * @param Collection|array $data Associative array of key value pair data + * + * @return Collection Returns a reference to the object. + */ + public function merge($data) + { + foreach ($data as $key => $value) { + $this->add($key, $value); + } + + return $this; + } + + /** + * Over write key value pairs in this collection with all of the data from an array or collection. + * + * @param array|\Traversable $data Values to override over this config + * + * @return self + */ + public function overwriteWith($data) + { + if (is_array($data)) { + $this->data = $data + $this->data; + } elseif ($data instanceof Collection) { + $this->data = $data->toArray() + $this->data; + } else { + foreach ($data as $key => $value) { + $this->data[$key] = $value; + } + } + + return $this; + } + + /** + * Returns a Collection containing all the elements of the collection after applying the callback function to each + * one. The Closure should accept three parameters: (string) $key, (string) $value, (array) $context and return a + * modified value + * + * @param \Closure $closure Closure to apply + * @param array $context Context to pass to the closure + * @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection + * + * @return Collection + */ + public function map(\Closure $closure, array $context = array(), $static = true) + { + $collection = $static ? new static() : new self(); + foreach ($this as $key => $value) { + $collection->add($key, $closure($key, $value, $context)); + } + + return $collection; + } + + /** + * Iterates over each key value pair in the collection passing them to the Closure. If the Closure function returns + * true, the current value from input is returned into the result Collection. The Closure must accept three + * parameters: (string) $key, (string) $value and return Boolean TRUE or FALSE for each value. + * + * @param \Closure $closure Closure evaluation function + * @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection + * + * @return Collection + */ + public function filter(\Closure $closure, $static = true) + { + $collection = ($static) ? new static() : new self(); + foreach ($this->data as $key => $value) { + if ($closure($key, $value)) { + $collection->add($key, $value); + } + } + + return $collection; + } + + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + public function offsetGet($offset) + { + return isset($this->data[$offset]) ? $this->data[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + + /** + * Set a value into a nested array key. Keys will be created as needed to set the value. + * + * @param string $path Path to set + * @param mixed $value Value to set at the key + * + * @return self + * @throws RuntimeException when trying to setPath using a nested path that travels through a scalar value + */ + public function setPath($path, $value) + { + $current =& $this->data; + $queue = explode('/', $path); + while (null !== ($key = array_shift($queue))) { + if (!is_array($current)) { + throw new RuntimeException("Trying to setPath {$path}, but {$key} is set and is not an array"); + } elseif (!$queue) { + $current[$key] = $value; + } elseif (isset($current[$key])) { + $current =& $current[$key]; + } else { + $current[$key] = array(); + $current =& $current[$key]; + } + } + + return $this; + } + + /** + * Gets a value from the collection using an array path (e.g. foo/baz/bar would retrieve bar from two nested arrays) + * Allows for wildcard searches which recursively combine matches up to the level at which the wildcard occurs. This + * can be useful for accepting any key of a sub-array and combining matching keys from each diverging path. + * + * @param string $path Path to traverse and retrieve a value from + * @param string $separator Character used to add depth to the search + * @param mixed $data Optional data to descend into (used when wildcards are encountered) + * + * @return mixed|null + */ + public function getPath($path, $separator = '/', $data = null) + { + if ($data === null) { + $data =& $this->data; + } + + $path = is_array($path) ? $path : explode($separator, $path); + while (null !== ($part = array_shift($path))) { + if (!is_array($data)) { + return null; + } elseif (isset($data[$part])) { + $data =& $data[$part]; + } elseif ($part != '*') { + return null; + } else { + // Perform a wildcard search by diverging and merging paths + $result = array(); + foreach ($data as $value) { + if (!$path) { + $result = array_merge_recursive($result, (array) $value); + } elseif (null !== ($test = $this->getPath($path, $separator, $value))) { + $result = array_merge_recursive($result, (array) $test); + } + } + return $result; + } + } + + return $data; + } + + /** + * Inject configuration settings into an input string + * + * @param string $input Input to inject + * + * @return string + * @deprecated + */ + public function inject($input) + { + Version::warn(__METHOD__ . ' is deprecated'); + $replace = array(); + foreach ($this->data as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + return strtr($input, $replace); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php b/source/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php new file mode 100644 index 0000000..fad76a9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Common/Event.php @@ -0,0 +1,52 @@ +context = $context; + } + + public function getIterator() + { + return new \ArrayIterator($this->context); + } + + public function offsetGet($offset) + { + return isset($this->context[$offset]) ? $this->context[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->context[$offset] = $value; + } + + public function offsetExists($offset) + { + return isset($this->context[$offset]); + } + + public function offsetUnset($offset) + { + unset($this->context[$offset]); + } + + public function toArray() + { + return $this->context; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php new file mode 100644 index 0000000..08d1c72 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/BadMethodCallException.php @@ -0,0 +1,5 @@ +shortMessage = $message; + } + + /** + * Set all of the exceptions + * + * @param array $exceptions Array of exceptions + * + * @return self + */ + public function setExceptions(array $exceptions) + { + $this->exceptions = array(); + foreach ($exceptions as $exception) { + $this->add($exception); + } + + return $this; + } + + /** + * Add exceptions to the collection + * + * @param ExceptionCollection|\Exception $e Exception to add + * + * @return ExceptionCollection; + */ + public function add($e) + { + $this->exceptions[] = $e; + if ($this->message) { + $this->message .= "\n"; + } + + $this->message .= $this->getExceptionMessage($e, 0); + + return $this; + } + + /** + * Get the total number of request exceptions + * + * @return int + */ + public function count() + { + return count($this->exceptions); + } + + /** + * Allows array-like iteration over the request exceptions + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->exceptions); + } + + /** + * Get the first exception in the collection + * + * @return \Exception + */ + public function getFirst() + { + return $this->exceptions ? $this->exceptions[0] : null; + } + + private function getExceptionMessage(\Exception $e, $depth = 0) + { + static $sp = ' '; + $prefix = $depth ? str_repeat($sp, $depth) : ''; + $message = "{$prefix}(" . get_class($e) . ') ' . $e->getFile() . ' line ' . $e->getLine() . "\n"; + + if ($e instanceof self) { + if ($e->shortMessage) { + $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->shortMessage) . "\n"; + } + foreach ($e as $ee) { + $message .= "\n" . $this->getExceptionMessage($ee, $depth + 1); + } + } else { + $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->getMessage()) . "\n"; + $message .= "\n{$prefix}{$sp}" . str_replace("\n", "\n{$prefix}{$sp}", $e->getTraceAsString()) . "\n"; + } + + return str_replace(getcwd(), '.', $message); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php new file mode 100644 index 0000000..458e6f2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Common/Exception/GuzzleException.php @@ -0,0 +1,8 @@ +=5.3.2", + "symfony/event-dispatcher": ">=2.1" + }, + "autoload": { + "psr-0": { "Guzzle\\Common": "" } + }, + "target-dir": "Guzzle/Common", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php new file mode 100644 index 0000000..5005a88 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/AbstractEntityBodyDecorator.php @@ -0,0 +1,221 @@ +body = $body; + } + + public function __toString() + { + return (string) $this->body; + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + return call_user_func_array(array($this->body, $method), $args); + } + + public function close() + { + return $this->body->close(); + } + + public function setRewindFunction($callable) + { + $this->body->setRewindFunction($callable); + + return $this; + } + + public function rewind() + { + return $this->body->rewind(); + } + + public function compress($filter = 'zlib.deflate') + { + return $this->body->compress($filter); + } + + public function uncompress($filter = 'zlib.inflate') + { + return $this->body->uncompress($filter); + } + + public function getContentLength() + { + return $this->getSize(); + } + + public function getContentType() + { + return $this->body->getContentType(); + } + + public function getContentMd5($rawOutput = false, $base64Encode = false) + { + $hash = Stream::getHash($this, 'md5', $rawOutput); + + return $hash && $base64Encode ? base64_encode($hash) : $hash; + } + + public function getContentEncoding() + { + return $this->body->getContentEncoding(); + } + + public function getMetaData($key = null) + { + return $this->body->getMetaData($key); + } + + public function getStream() + { + return $this->body->getStream(); + } + + public function setStream($stream, $size = 0) + { + $this->body->setStream($stream, $size); + + return $this; + } + + public function detachStream() + { + $this->body->detachStream(); + + return $this; + } + + public function getWrapper() + { + return $this->body->getWrapper(); + } + + public function getWrapperData() + { + return $this->body->getWrapperData(); + } + + public function getStreamType() + { + return $this->body->getStreamType(); + } + + public function getUri() + { + return $this->body->getUri(); + } + + public function getSize() + { + return $this->body->getSize(); + } + + public function isReadable() + { + return $this->body->isReadable(); + } + + public function isRepeatable() + { + return $this->isSeekable() && $this->isReadable(); + } + + public function isWritable() + { + return $this->body->isWritable(); + } + + public function isConsumed() + { + return $this->body->isConsumed(); + } + + /** + * Alias of isConsumed() + * {@inheritdoc} + */ + public function feof() + { + return $this->isConsumed(); + } + + public function isLocal() + { + return $this->body->isLocal(); + } + + public function isSeekable() + { + return $this->body->isSeekable(); + } + + public function setSize($size) + { + $this->body->setSize($size); + + return $this; + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->body->seek($offset, $whence); + } + + public function read($length) + { + return $this->body->read($length); + } + + public function write($string) + { + return $this->body->write($string); + } + + public function readLine($maxLength = null) + { + return $this->body->readLine($maxLength); + } + + public function ftell() + { + return $this->body->ftell(); + } + + public function getCustomData($key) + { + return $this->body->getCustomData($key); + } + + public function setCustomData($key, $value) + { + $this->body->setCustomData($key, $value); + + return $this; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php new file mode 100644 index 0000000..c65c136 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/CachingEntityBody.php @@ -0,0 +1,229 @@ +remoteStream = $body; + $this->body = new EntityBody(fopen('php://temp', 'r+')); + } + + /** + * Will give the contents of the buffer followed by the exhausted remote stream. + * + * Warning: Loads the entire stream into memory + * + * @return string + */ + public function __toString() + { + $pos = $this->ftell(); + $this->rewind(); + + $str = ''; + while (!$this->isConsumed()) { + $str .= $this->read(16384); + } + + $this->seek($pos); + + return $str; + } + + public function getSize() + { + return max($this->body->getSize(), $this->remoteStream->getSize()); + } + + /** + * {@inheritdoc} + * @throws RuntimeException When seeking with SEEK_END or when seeking past the total size of the buffer stream + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->ftell(); + } else { + throw new RuntimeException(__CLASS__ . ' supports only SEEK_SET and SEEK_CUR seek operations'); + } + + // You cannot skip ahead past where you've read from the remote stream + if ($byte > $this->body->getSize()) { + throw new RuntimeException( + "Cannot seek to byte {$byte} when the buffered stream only contains {$this->body->getSize()} bytes" + ); + } + + return $this->body->seek($byte); + } + + public function rewind() + { + return $this->seek(0); + } + + /** + * Does not support custom rewind functions + * + * @throws RuntimeException + */ + public function setRewindFunction($callable) + { + throw new RuntimeException(__CLASS__ . ' does not support custom stream rewind functions'); + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->body->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->body->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // 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->ftell()) - $this->remoteStream->ftell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->body->write($string); + } + + /** + * {@inheritdoc} + * @link http://php.net/manual/en/function.fgets.php + */ + public function readLine($maxLength = null) + { + $buffer = ''; + $size = 0; + while (!$this->isConsumed()) { + $byte = $this->read(1); + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte == PHP_EOL || ++$size == $maxLength - 1) { + break; + } + } + + return $buffer; + } + + public function isConsumed() + { + return $this->body->isConsumed() && $this->remoteStream->isConsumed(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + return $this->remoteStream->close() && $this->body->close(); + } + + public function setStream($stream, $size = 0) + { + $this->remoteStream->setStream($stream, $size); + } + + public function getContentType() + { + return $this->remoteStream->getContentType(); + } + + public function getContentEncoding() + { + return $this->remoteStream->getContentEncoding(); + } + + public function getMetaData($key = null) + { + return $this->remoteStream->getMetaData($key); + } + + public function getStream() + { + return $this->remoteStream->getStream(); + } + + public function getWrapper() + { + return $this->remoteStream->getWrapper(); + } + + public function getWrapperData() + { + return $this->remoteStream->getWrapperData(); + } + + public function getStreamType() + { + return $this->remoteStream->getStreamType(); + } + + public function getUri() + { + return $this->remoteStream->getUri(); + } + + /** + * Always retrieve custom data from the remote stream + * {@inheritdoc} + */ + public function getCustomData($key) + { + return $this->remoteStream->getCustomData($key); + } + + /** + * Always set custom data on the remote stream + * {@inheritdoc} + */ + public function setCustomData($key, $value) + { + $this->remoteStream->setCustomData($key, $value); + + return $this; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php new file mode 100644 index 0000000..3d7298d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Client.php @@ -0,0 +1,524 @@ +setConfig($config ?: new Collection()); + $this->initSsl(); + $this->setBaseUrl($baseUrl); + $this->defaultHeaders = new Collection(); + $this->setRequestFactory(RequestFactory::getInstance()); + $this->userAgent = $this->getDefaultUserAgent(); + if (!$this->config[self::DISABLE_REDIRECTS]) { + $this->addSubscriber(new RedirectPlugin()); + } + } + + final public function setConfig($config) + { + if ($config instanceof Collection) { + $this->config = $config; + } elseif (is_array($config)) { + $this->config = new Collection($config); + } else { + throw new InvalidArgumentException('Config must be an array or Collection'); + } + + return $this; + } + + final public function getConfig($key = false) + { + return $key ? $this->config[$key] : $this->config; + } + + /** + * Set a default request option on the client that will be used as a default for each request + * + * @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo) + * @param mixed $value Value to set + * + * @return $this + */ + public function setDefaultOption($keyOrPath, $value) + { + $keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath; + $this->config->setPath($keyOrPath, $value); + + return $this; + } + + /** + * Retrieve a default request option from the client + * + * @param string $keyOrPath request.options key (e.g. allow_redirects) or path to a nested key (e.g. headers/foo) + * + * @return mixed|null + */ + public function getDefaultOption($keyOrPath) + { + $keyOrPath = self::REQUEST_OPTIONS . '/' . $keyOrPath; + + return $this->config->getPath($keyOrPath); + } + + final public function setSslVerification($certificateAuthority = true, $verifyPeer = true, $verifyHost = 2) + { + $opts = $this->config[self::CURL_OPTIONS] ?: array(); + + if ($certificateAuthority === true) { + // use bundled CA bundle, set secure defaults + $opts[CURLOPT_CAINFO] = __DIR__ . '/Resources/cacert.pem'; + $opts[CURLOPT_SSL_VERIFYPEER] = true; + $opts[CURLOPT_SSL_VERIFYHOST] = 2; + } elseif ($certificateAuthority === false) { + unset($opts[CURLOPT_CAINFO]); + $opts[CURLOPT_SSL_VERIFYPEER] = false; + $opts[CURLOPT_SSL_VERIFYHOST] = 0; + } elseif ($verifyPeer !== true && $verifyPeer !== false && $verifyPeer !== 1 && $verifyPeer !== 0) { + throw new InvalidArgumentException('verifyPeer must be 1, 0 or boolean'); + } elseif ($verifyHost !== 0 && $verifyHost !== 1 && $verifyHost !== 2) { + throw new InvalidArgumentException('verifyHost must be 0, 1 or 2'); + } else { + $opts[CURLOPT_SSL_VERIFYPEER] = $verifyPeer; + $opts[CURLOPT_SSL_VERIFYHOST] = $verifyHost; + if (is_file($certificateAuthority)) { + unset($opts[CURLOPT_CAPATH]); + $opts[CURLOPT_CAINFO] = $certificateAuthority; + } elseif (is_dir($certificateAuthority)) { + unset($opts[CURLOPT_CAINFO]); + $opts[CURLOPT_CAPATH] = $certificateAuthority; + } else { + throw new RuntimeException( + 'Invalid option passed to ' . self::SSL_CERT_AUTHORITY . ': ' . $certificateAuthority + ); + } + } + + $this->config->set(self::CURL_OPTIONS, $opts); + + return $this; + } + + public function createRequest($method = 'GET', $uri = null, $headers = null, $body = null, array $options = array()) + { + if (!$uri) { + $url = $this->getBaseUrl(); + } else { + if (!is_array($uri)) { + $templateVars = null; + } else { + list($uri, $templateVars) = $uri; + } + if (strpos($uri, '://')) { + // Use absolute URLs as-is + $url = $this->expandTemplate($uri, $templateVars); + } else { + $url = Url::factory($this->getBaseUrl())->combine($this->expandTemplate($uri, $templateVars)); + } + } + + // If default headers are provided, then merge them under any explicitly provided headers for the request + if (count($this->defaultHeaders)) { + if (!$headers) { + $headers = $this->defaultHeaders->toArray(); + } elseif (is_array($headers)) { + $headers += $this->defaultHeaders->toArray(); + } elseif ($headers instanceof Collection) { + $headers = $headers->toArray() + $this->defaultHeaders->toArray(); + } + } + + return $this->prepareRequest($this->requestFactory->create($method, (string) $url, $headers, $body), $options); + } + + public function getBaseUrl($expand = true) + { + return $expand ? $this->expandTemplate($this->baseUrl) : $this->baseUrl; + } + + public function setBaseUrl($url) + { + $this->baseUrl = $url; + + return $this; + } + + public function setUserAgent($userAgent, $includeDefault = false) + { + if ($includeDefault) { + $userAgent .= ' ' . $this->getDefaultUserAgent(); + } + $this->userAgent = $userAgent; + + return $this; + } + + /** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ + public function getDefaultUserAgent() + { + return 'Guzzle/' . Version::VERSION + . ' curl/' . CurlVersion::getInstance()->get('version') + . ' PHP/' . PHP_VERSION; + } + + public function get($uri = null, $headers = null, $options = array()) + { + // BC compat: $options can be a string, resource, etc to specify where the response body is downloaded + return is_array($options) + ? $this->createRequest('GET', $uri, $headers, null, $options) + : $this->createRequest('GET', $uri, $headers, $options); + } + + public function head($uri = null, $headers = null, array $options = array()) + { + return $this->createRequest('HEAD', $uri, $headers, null, $options); + } + + public function delete($uri = null, $headers = null, $body = null, array $options = array()) + { + return $this->createRequest('DELETE', $uri, $headers, $body, $options); + } + + public function put($uri = null, $headers = null, $body = null, array $options = array()) + { + return $this->createRequest('PUT', $uri, $headers, $body, $options); + } + + public function patch($uri = null, $headers = null, $body = null, array $options = array()) + { + return $this->createRequest('PATCH', $uri, $headers, $body, $options); + } + + public function post($uri = null, $headers = null, $postBody = null, array $options = array()) + { + return $this->createRequest('POST', $uri, $headers, $postBody, $options); + } + + public function options($uri = null, array $options = array()) + { + return $this->createRequest('OPTIONS', $uri, $options); + } + + public function send($requests) + { + if (!($requests instanceof RequestInterface)) { + return $this->sendMultiple($requests); + } + + try { + /** @var $requests RequestInterface */ + $this->getCurlMulti()->add($requests)->send(); + return $requests->getResponse(); + } catch (ExceptionCollection $e) { + throw $e->getFirst(); + } + } + + /** + * Set a curl multi object to be used internally by the client for transferring requests. + * + * @param CurlMultiInterface $curlMulti Multi object + * + * @return self + */ + public function setCurlMulti(CurlMultiInterface $curlMulti) + { + $this->curlMulti = $curlMulti; + + return $this; + } + + /** + * @return CurlMultiInterface|CurlMultiProxy + */ + public function getCurlMulti() + { + if (!$this->curlMulti) { + $this->curlMulti = new CurlMultiProxy( + self::MAX_HANDLES, + $this->getConfig('select_timeout') ?: self::DEFAULT_SELECT_TIMEOUT + ); + } + + return $this->curlMulti; + } + + public function setRequestFactory(RequestFactoryInterface $factory) + { + $this->requestFactory = $factory; + + return $this; + } + + /** + * Set the URI template expander to use with the client + * + * @param UriTemplateInterface $uriTemplate URI template expander + * + * @return self + */ + public function setUriTemplate(UriTemplateInterface $uriTemplate) + { + $this->uriTemplate = $uriTemplate; + + return $this; + } + + /** + * Expand a URI template while merging client config settings into the template variables + * + * @param string $template Template to expand + * @param array $variables Variables to inject + * + * @return string + */ + protected function expandTemplate($template, array $variables = null) + { + $expansionVars = $this->getConfig()->toArray(); + if ($variables) { + $expansionVars = $variables + $expansionVars; + } + + return $this->getUriTemplate()->expand($template, $expansionVars); + } + + /** + * Get the URI template expander used by the client + * + * @return UriTemplateInterface + */ + protected function getUriTemplate() + { + if (!$this->uriTemplate) { + $this->uriTemplate = ParserRegistry::getInstance()->getParser('uri_template'); + } + + return $this->uriTemplate; + } + + /** + * Send multiple requests in parallel + * + * @param array $requests Array of RequestInterface objects + * + * @return array Returns an array of Response objects + */ + protected function sendMultiple(array $requests) + { + $curlMulti = $this->getCurlMulti(); + foreach ($requests as $request) { + $curlMulti->add($request); + } + $curlMulti->send(); + + /** @var $request RequestInterface */ + $result = array(); + foreach ($requests as $request) { + $result[] = $request->getResponse(); + } + + return $result; + } + + /** + * Prepare a request to be sent from the Client by adding client specific behaviors and properties to the request. + * + * @param RequestInterface $request Request to prepare for the client + * @param array $options Options to apply to the request + * + * @return RequestInterface + */ + protected function prepareRequest(RequestInterface $request, array $options = array()) + { + $request->setClient($this)->setEventDispatcher(clone $this->getEventDispatcher()); + + if ($curl = $this->config[self::CURL_OPTIONS]) { + $request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($curl)); + } + + if ($params = $this->config[self::REQUEST_PARAMS]) { + Version::warn('request.params is deprecated. Use request.options to add default request options.'); + $request->getParams()->overwriteWith($params); + } + + if ($this->userAgent && !$request->hasHeader('User-Agent')) { + $request->setHeader('User-Agent', $this->userAgent); + } + + if ($defaults = $this->config[self::REQUEST_OPTIONS]) { + $this->requestFactory->applyOptions($request, $defaults, RequestFactoryInterface::OPTIONS_AS_DEFAULTS); + } + + if ($options) { + $this->requestFactory->applyOptions($request, $options); + } + + $this->dispatch('client.create_request', array('client' => $this, 'request' => $request)); + + return $request; + } + + /** + * Initializes SSL settings + */ + protected function initSsl() + { + $authority = $this->config[self::SSL_CERT_AUTHORITY]; + + if ($authority === 'system') { + return; + } + + if ($authority === null) { + $authority = true; + } + + if ($authority === true && substr(__FILE__, 0, 7) == 'phar://') { + $authority = self::extractPharCacert(__DIR__ . '/Resources/cacert.pem'); + } + + $this->setSslVerification($authority); + } + + /** + * @deprecated + */ + public function getDefaultHeaders() + { + Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to retrieve default request options'); + return $this->defaultHeaders; + } + + /** + * @deprecated + */ + public function setDefaultHeaders($headers) + { + Version::warn(__METHOD__ . ' is deprecated. Use the request.options array to specify default request options'); + if ($headers instanceof Collection) { + $this->defaultHeaders = $headers; + } elseif (is_array($headers)) { + $this->defaultHeaders = new Collection($headers); + } else { + throw new InvalidArgumentException('Headers must be an array or Collection'); + } + + return $this; + } + + /** + * @deprecated + */ + public function preparePharCacert($md5Check = true) + { + return sys_get_temp_dir() . '/guzzle-cacert.pem'; + } + + /** + * Copies the phar cacert from a phar into the temp directory. + * + * @param string $pharCacertPath Path to the phar cacert. For example: + * 'phar://aws.phar/Guzzle/Http/Resources/cacert.pem' + * + * @return string Returns the path to the extracted cacert file. + * @throws \RuntimeException Throws if the phar cacert cannot be found or + * the file cannot be copied to the temp dir. + */ + public static function extractPharCacert($pharCacertPath) + { + // Copy the cacert.pem file from the phar if it is not in the temp + // folder. + $certFile = sys_get_temp_dir() . '/guzzle-cacert.pem'; + + if (!file_exists($pharCacertPath)) { + throw new \RuntimeException("Could not find $pharCacertPath"); + } + + if (!file_exists($certFile) || + filesize($certFile) != filesize($pharCacertPath) + ) { + if (!copy($pharCacertPath, $certFile)) { + throw new \RuntimeException( + "Could not copy {$pharCacertPath} to {$certFile}: " + . var_export(error_get_last(), true) + ); + } + } + + return $certFile; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php new file mode 100644 index 0000000..10e4de2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/ClientInterface.php @@ -0,0 +1,223 @@ +getCurlOptions(); + $mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io')); + $tempContentLength = null; + $method = $request->getMethod(); + $bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING); + + // Prepare url + $url = (string)$request->getUrl(); + if(($pos = strpos($url, '#')) !== false ){ + // strip fragment from url + $url = substr($url, 0, $pos); + } + + // Array of default cURL options. + $curlOptions = array( + CURLOPT_URL => $url, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_RETURNTRANSFER => false, + CURLOPT_HEADER => false, + CURLOPT_PORT => $request->getPort(), + CURLOPT_HTTPHEADER => array(), + CURLOPT_WRITEFUNCTION => array($mediator, 'writeResponseBody'), + CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'), + CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0' + ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1, + // Verifies the authenticity of the peer's certificate + CURLOPT_SSL_VERIFYPEER => 1, + // Certificate must indicate that the server is the server to which you meant to connect + CURLOPT_SSL_VERIFYHOST => 2 + ); + + if (defined('CURLOPT_PROTOCOLS')) { + // Allow only HTTP and HTTPS protocols + $curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + // Add CURLOPT_ENCODING if Accept-Encoding header is provided + if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) { + $curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader; + // Let cURL set the Accept-Encoding header, prevents duplicate values + $request->removeHeader('Accept-Encoding'); + } + + // Enable curl debug information if the 'debug' param was set + if ($requestCurlOptions->get('debug')) { + $curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+'); + // @codeCoverageIgnoreStart + if (false === $curlOptions[CURLOPT_STDERR]) { + throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR'); + } + // @codeCoverageIgnoreEnd + $curlOptions[CURLOPT_VERBOSE] = true; + } + + // Specify settings according to the HTTP method + if ($method == 'GET') { + $curlOptions[CURLOPT_HTTPGET] = true; + } elseif ($method == 'HEAD') { + $curlOptions[CURLOPT_NOBODY] = true; + // HEAD requests do not use a write function + unset($curlOptions[CURLOPT_WRITEFUNCTION]); + } elseif (!($request instanceof EntityEnclosingRequest)) { + $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; + } else { + + $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; + + // Handle sending raw bodies in a request + if ($request->getBody()) { + // You can send the body as a string using curl's CURLOPT_POSTFIELDS + if ($bodyAsString) { + $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Allow curl to add the Content-Length for us to account for the times when + // POST redirects are followed by GET requests + if ($tempContentLength = $request->getHeader('Content-Length')) { + $tempContentLength = (int) (string) $tempContentLength; + } + // Remove the curl generated Content-Type header if none was set manually + if (!$request->hasHeader('Content-Type')) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } else { + $curlOptions[CURLOPT_UPLOAD] = true; + // Let cURL handle setting the Content-Length header + if ($tempContentLength = $request->getHeader('Content-Length')) { + $tempContentLength = (int) (string) $tempContentLength; + $curlOptions[CURLOPT_INFILESIZE] = $tempContentLength; + } + // Add a callback for curl to read data to send with the request only if a body was specified + $curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody'); + // Attempt to seek to the start of the stream + $request->getBody()->seek(0); + } + + } else { + + // Special handling for POST specific fields and files + $postFields = false; + if (count($request->getPostFiles())) { + $postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode(); + foreach ($request->getPostFiles() as $key => $data) { + $prefixKeys = count($data) > 1; + foreach ($data as $index => $file) { + // Allow multiple files in the same key + $fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key; + $postFields[$fieldKey] = $file->getCurlValue(); + } + } + } elseif (count($request->getPostFields())) { + $postFields = (string) $request->getPostFields()->useUrlEncoding(true); + } + + if ($postFields !== false) { + if ($method == 'POST') { + unset($curlOptions[CURLOPT_CUSTOMREQUEST]); + $curlOptions[CURLOPT_POST] = true; + } + $curlOptions[CURLOPT_POSTFIELDS] = $postFields; + $request->removeHeader('Content-Length'); + } + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + } + + // If a Content-Length header was specified but we want to allow curl to set one for us + if (null !== $tempContentLength) { + $request->removeHeader('Content-Length'); + } + + // Set custom cURL options + foreach ($requestCurlOptions->toArray() as $key => $value) { + if (is_numeric($key)) { + $curlOptions[$key] = $value; + } + } + + // Do not set an Accept header by default + if (!isset($curlOptions[CURLOPT_ENCODING])) { + $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + + // Add any custom headers to the request. Empty headers will cause curl to not send the header at all. + foreach ($request->getHeaderLines() as $line) { + $curlOptions[CURLOPT_HTTPHEADER][] = $line; + } + + // Add the content-length header back if it was temporarily removed + if (null !== $tempContentLength) { + $request->setHeader('Content-Length', $tempContentLength); + } + + // Apply the options to a new cURL handle. + $handle = curl_init(); + + // Enable the progress function if the 'progress' param was set + if ($requestCurlOptions->get('progress')) { + // Wrap the function in a function that provides the curl handle to the mediator's progress function + // Using this rather than injecting the handle into the mediator prevents a circular reference + $curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) { + $args = func_get_args(); + $args[] = $handle; + + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + + call_user_func_array(array($mediator, 'progress'), $args); + }; + $curlOptions[CURLOPT_NOPROGRESS] = false; + } + + curl_setopt_array($handle, $curlOptions); + + return new static($handle, $curlOptions); + } + + /** + * Construct a new CurlHandle object that wraps a cURL handle + * + * @param resource $handle Configured cURL handle resource + * @param Collection|array $options Curl options to use with the handle + * + * @throws InvalidArgumentException + */ + public function __construct($handle, $options) + { + if (!is_resource($handle)) { + throw new InvalidArgumentException('Invalid handle provided'); + } + if (is_array($options)) { + $this->options = new Collection($options); + } elseif ($options instanceof Collection) { + $this->options = $options; + } else { + throw new InvalidArgumentException('Expected array or Collection'); + } + $this->handle = $handle; + } + + /** + * Destructor + */ + public function __destruct() + { + $this->close(); + } + + /** + * Close the curl handle + */ + public function close() + { + if (is_resource($this->handle)) { + curl_close($this->handle); + } + $this->handle = null; + } + + /** + * Check if the handle is available and still OK + * + * @return bool + */ + public function isAvailable() + { + return is_resource($this->handle); + } + + /** + * Get the last error that occurred on the cURL handle + * + * @return string + */ + public function getError() + { + return $this->isAvailable() ? curl_error($this->handle) : ''; + } + + /** + * Get the last error number that occurred on the cURL handle + * + * @return int + */ + public function getErrorNo() + { + if ($this->errorNo) { + return $this->errorNo; + } + + return $this->isAvailable() ? curl_errno($this->handle) : CURLE_OK; + } + + /** + * Set the curl error number + * + * @param int $error Error number to set + * + * @return CurlHandle + */ + public function setErrorNo($error) + { + $this->errorNo = $error; + + return $this; + } + + /** + * Get cURL curl_getinfo data + * + * @param int $option Option to retrieve. Pass null to retrieve all data as an array. + * + * @return array|mixed + */ + public function getInfo($option = null) + { + if (!is_resource($this->handle)) { + return null; + } + + if (null !== $option) { + return curl_getinfo($this->handle, $option) ?: null; + } + + return curl_getinfo($this->handle) ?: array(); + } + + /** + * Get the stderr output + * + * @param bool $asResource Set to TRUE to get an fopen resource + * + * @return string|resource|null + */ + public function getStderr($asResource = false) + { + $stderr = $this->getOptions()->get(CURLOPT_STDERR); + if (!$stderr) { + return null; + } + + if ($asResource) { + return $stderr; + } + + fseek($stderr, 0); + $e = stream_get_contents($stderr); + fseek($stderr, 0, SEEK_END); + + return $e; + } + + /** + * Get the URL that this handle is connecting to + * + * @return Url + */ + public function getUrl() + { + return Url::factory($this->options->get(CURLOPT_URL)); + } + + /** + * Get the wrapped curl handle + * + * @return resource|null Returns the cURL handle or null if it was closed + */ + public function getHandle() + { + return $this->isAvailable() ? $this->handle : null; + } + + /** + * Get the cURL setopt options of the handle. Changing values in the return object will have no effect on the curl + * handle after it is created. + * + * @return Collection + */ + public function getOptions() + { + return $this->options; + } + + /** + * Update a request based on the log messages of the CurlHandle + * + * @param RequestInterface $request Request to update + */ + public function updateRequestFromTransfer(RequestInterface $request) + { + if (!$request->getResponse()) { + return; + } + + // Update the transfer stats of the response + $request->getResponse()->setInfo($this->getInfo()); + + if (!$log = $this->getStderr(true)) { + return; + } + + // Parse the cURL stderr output for outgoing requests + $headers = ''; + fseek($log, 0); + while (($line = fgets($log)) !== false) { + if ($line && $line[0] == '>') { + $headers = substr(trim($line), 2) . "\r\n"; + while (($line = fgets($log)) !== false) { + if ($line[0] == '*' || $line[0] == '<') { + break; + } else { + $headers .= trim($line) . "\r\n"; + } + } + } + } + + // Add request headers to the request exactly as they were sent + if ($headers) { + $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($headers); + if (!empty($parsed['headers'])) { + $request->setHeaders(array()); + foreach ($parsed['headers'] as $name => $value) { + $request->setHeader($name, $value); + } + } + if (!empty($parsed['version'])) { + $request->setProtocolVersion($parsed['version']); + } + } + } + + /** + * Parse the config and replace curl.* configurators into the constant based values so it can be used elsewhere + * + * @param array|Collection $config The configuration we want to parse + * + * @return array + */ + public static function parseCurlConfig($config) + { + $curlOptions = array(); + foreach ($config as $key => $value) { + if (is_string($key) && defined($key)) { + // Convert constants represented as string to constant int values + $key = constant($key); + } + if (is_string($value) && defined($value)) { + $value = constant($value); + } + $curlOptions[$key] = $value; + } + + return $curlOptions; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php new file mode 100644 index 0000000..9e4e637 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMulti.php @@ -0,0 +1,423 @@ + array('CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'), + CURLM_BAD_EASY_HANDLE => array('CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."), + CURLM_OUT_OF_MEMORY => array('CURLM_OUT_OF_MEMORY', 'You are doomed.'), + CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!') + ); + + /** @var float */ + protected $selectTimeout; + + public function __construct($selectTimeout = 1.0) + { + $this->selectTimeout = $selectTimeout; + $this->multiHandle = curl_multi_init(); + // @codeCoverageIgnoreStart + if ($this->multiHandle === false) { + throw new CurlException('Unable to create multi handle'); + } + // @codeCoverageIgnoreEnd + $this->reset(); + } + + public function __destruct() + { + if (is_resource($this->multiHandle)) { + curl_multi_close($this->multiHandle); + } + } + + public function add(RequestInterface $request) + { + $this->requests[] = $request; + // If requests are currently transferring and this is async, then the + // request must be prepared now as the send() method is not called. + $this->beforeSend($request); + $this->dispatch(self::ADD_REQUEST, array('request' => $request)); + + return $this; + } + + public function all() + { + return $this->requests; + } + + public function remove(RequestInterface $request) + { + $this->removeHandle($request); + if (($index = array_search($request, $this->requests, true)) !== false) { + $request = $this->requests[$index]; + unset($this->requests[$index]); + $this->requests = array_values($this->requests); + $this->dispatch(self::REMOVE_REQUEST, array('request' => $request)); + return true; + } + + return false; + } + + public function reset($hard = false) + { + // Remove each request + if ($this->requests) { + foreach ($this->requests as $request) { + $this->remove($request); + } + } + + $this->handles = new \SplObjectStorage(); + $this->requests = $this->resourceHash = $this->exceptions = $this->successful = array(); + } + + public function send() + { + $this->perform(); + $exceptions = $this->exceptions; + $successful = $this->successful; + $this->reset(); + + if ($exceptions) { + $this->throwMultiException($exceptions, $successful); + } + } + + public function count() + { + return count($this->requests); + } + + /** + * Build and throw a MultiTransferException + * + * @param array $exceptions Exceptions encountered + * @param array $successful Successful requests + * @throws MultiTransferException + */ + protected function throwMultiException(array $exceptions, array $successful) + { + $multiException = new MultiTransferException('Errors during multi transfer'); + + while ($e = array_shift($exceptions)) { + $multiException->addFailedRequestWithException($e['request'], $e['exception']); + } + + // Add successful requests + foreach ($successful as $request) { + if (!$multiException->containsRequest($request)) { + $multiException->addSuccessfulRequest($request); + } + } + + throw $multiException; + } + + /** + * Prepare for sending + * + * @param RequestInterface $request Request to prepare + * @throws \Exception on error preparing the request + */ + protected function beforeSend(RequestInterface $request) + { + try { + $state = $request->setState(RequestInterface::STATE_TRANSFER); + if ($state == RequestInterface::STATE_TRANSFER) { + $this->addHandle($request); + } else { + // Requests might decide they don't need to be sent just before + // transfer (e.g. CachePlugin) + $this->remove($request); + if ($state == RequestInterface::STATE_COMPLETE) { + $this->successful[] = $request; + } + } + } catch (\Exception $e) { + // Queue the exception to be thrown when sent + $this->removeErroredRequest($request, $e); + } + } + + private function addHandle(RequestInterface $request) + { + $handle = $this->createCurlHandle($request)->getHandle(); + $this->checkCurlResult( + curl_multi_add_handle($this->multiHandle, $handle) + ); + } + + /** + * Create a curl handle for a request + * + * @param RequestInterface $request Request + * + * @return CurlHandle + */ + protected function createCurlHandle(RequestInterface $request) + { + $wrapper = CurlHandle::factory($request); + $this->handles[$request] = $wrapper; + $this->resourceHash[(int) $wrapper->getHandle()] = $request; + + return $wrapper; + } + + /** + * Get the data from the multi handle + */ + protected function perform() + { + $event = new Event(array('curl_multi' => $this)); + + while ($this->requests) { + // Notify each request as polling + $blocking = $total = 0; + foreach ($this->requests as $request) { + ++$total; + $event['request'] = $request; + $request->getEventDispatcher()->dispatch(self::POLLING_REQUEST, $event); + // The blocking variable just has to be non-falsey to block the loop + if ($request->getParams()->hasKey(self::BLOCKING)) { + ++$blocking; + } + } + if ($blocking == $total) { + // Sleep to prevent eating CPU because no requests are actually pending a select call + usleep(500); + } else { + $this->executeHandles(); + } + } + } + + /** + * Execute and select curl handles + */ + private function executeHandles() + { + // The first curl_multi_select often times out no matter what, but is usually required for fast transfers + $selectTimeout = 0.001; + $active = false; + do { + while (($mrc = curl_multi_exec($this->multiHandle, $active)) == CURLM_CALL_MULTI_PERFORM); + $this->checkCurlResult($mrc); + $this->processMessages(); + if ($active && curl_multi_select($this->multiHandle, $selectTimeout) === -1) { + // Perform a usleep if a select returns -1: https://bugs.php.net/bug.php?id=61141 + usleep(150); + } + $selectTimeout = $this->selectTimeout; + } while ($active); + } + + /** + * Process any received curl multi messages + */ + private function processMessages() + { + while ($done = curl_multi_info_read($this->multiHandle)) { + $request = $this->resourceHash[(int) $done['handle']]; + try { + $this->processResponse($request, $this->handles[$request], $done); + $this->successful[] = $request; + } catch (\Exception $e) { + $this->removeErroredRequest($request, $e); + } + } + } + + /** + * Remove a request that encountered an exception + * + * @param RequestInterface $request Request to remove + * @param \Exception $e Exception encountered + */ + protected function removeErroredRequest(RequestInterface $request, \Exception $e = null) + { + $this->exceptions[] = array('request' => $request, 'exception' => $e); + $this->remove($request); + $this->dispatch(self::MULTI_EXCEPTION, array('exception' => $e, 'all_exceptions' => $this->exceptions)); + } + + /** + * Check for errors and fix headers of a request based on a curl response + * + * @param RequestInterface $request Request to process + * @param CurlHandle $handle Curl handle object + * @param array $curl Array returned from curl_multi_info_read + * + * @throws CurlException on Curl error + */ + protected function processResponse(RequestInterface $request, CurlHandle $handle, array $curl) + { + // Set the transfer stats on the response + $handle->updateRequestFromTransfer($request); + // Check if a cURL exception occurred, and if so, notify things + $curlException = $this->isCurlException($request, $handle, $curl); + + // Always remove completed curl handles. They can be added back again + // via events if needed (e.g. ExponentialBackoffPlugin) + $this->removeHandle($request); + + if (!$curlException) { + if ($this->validateResponseWasSet($request)) { + $state = $request->setState( + RequestInterface::STATE_COMPLETE, + array('handle' => $handle) + ); + // Only remove the request if it wasn't resent as a result of + // the state change + if ($state != RequestInterface::STATE_TRANSFER) { + $this->remove($request); + } + } + return; + } + + // Set the state of the request to an error + $state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $curlException)); + // Allow things to ignore the error if possible + if ($state != RequestInterface::STATE_TRANSFER) { + $this->remove($request); + } + + // The error was not handled, so fail + if ($state == RequestInterface::STATE_ERROR) { + /** @var CurlException $curlException */ + throw $curlException; + } + } + + /** + * Remove a curl handle from the curl multi object + * + * @param RequestInterface $request Request that owns the handle + */ + protected function removeHandle(RequestInterface $request) + { + if (isset($this->handles[$request])) { + $handle = $this->handles[$request]; + curl_multi_remove_handle($this->multiHandle, $handle->getHandle()); + unset($this->handles[$request]); + unset($this->resourceHash[(int) $handle->getHandle()]); + $handle->close(); + } + } + + /** + * Check if a cURL transfer resulted in what should be an exception + * + * @param RequestInterface $request Request to check + * @param CurlHandle $handle Curl handle object + * @param array $curl Array returned from curl_multi_info_read + * + * @return CurlException|bool + */ + private function isCurlException(RequestInterface $request, CurlHandle $handle, array $curl) + { + if (CURLM_OK == $curl['result'] || CURLM_CALL_MULTI_PERFORM == $curl['result']) { + return false; + } + + $handle->setErrorNo($curl['result']); + $e = new CurlException(sprintf('[curl] %s: %s [url] %s', + $handle->getErrorNo(), $handle->getError(), $handle->getUrl())); + $e->setCurlHandle($handle) + ->setRequest($request) + ->setCurlInfo($handle->getInfo()) + ->setError($handle->getError(), $handle->getErrorNo()); + + return $e; + } + + /** + * Throw an exception for a cURL multi response if needed + * + * @param int $code Curl response code + * @throws CurlException + */ + private function checkCurlResult($code) + { + if ($code != CURLM_OK && $code != CURLM_CALL_MULTI_PERFORM) { + throw new CurlException(isset($this->multiErrors[$code]) + ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}" + : 'Unexpected cURL error: ' . $code + ); + } + } + + /** + * @link https://github.com/guzzle/guzzle/issues/710 + */ + private function validateResponseWasSet(RequestInterface $request) + { + if ($request->getResponse()) { + return true; + } + + $body = $request instanceof EntityEnclosingRequestInterface + ? $request->getBody() + : null; + + if (!$body) { + $rex = new RequestException( + 'No response was received for a request with no body. This' + . ' could mean that you are saturating your network.' + ); + $rex->setRequest($request); + $this->removeErroredRequest($request, $rex); + } elseif (!$body->isSeekable() || !$body->seek(0)) { + // Nothing we can do with this. Sorry! + $rex = new RequestException( + 'The connection was unexpectedly closed. The request would' + . ' have been retried, but attempting to rewind the' + . ' request body failed.' + ); + $rex->setRequest($request); + $this->removeErroredRequest($request, $rex); + } else { + $this->remove($request); + // Add the request back to the batch to retry automatically. + $this->requests[] = $request; + $this->addHandle($request); + } + + return false; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php new file mode 100644 index 0000000..0ead757 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlMultiInterface.php @@ -0,0 +1,58 @@ +maxHandles = $maxHandles; + $this->selectTimeout = $selectTimeout; + // You can get some weird "Too many open files" errors when sending a large amount of requests in parallel. + // These two statements autoload classes before a system runs out of file descriptors so that you can get back + // valuable error messages if you run out. + class_exists('Guzzle\Http\Message\Response'); + class_exists('Guzzle\Http\Exception\CurlException'); + } + + public function add(RequestInterface $request) + { + $this->queued[] = $request; + + return $this; + } + + public function all() + { + $requests = $this->queued; + foreach ($this->handles as $handle) { + $requests = array_merge($requests, $handle->all()); + } + + return $requests; + } + + public function remove(RequestInterface $request) + { + foreach ($this->queued as $i => $r) { + if ($request === $r) { + unset($this->queued[$i]); + return true; + } + } + + foreach ($this->handles as $handle) { + if ($handle->remove($request)) { + return true; + } + } + + return false; + } + + public function reset($hard = false) + { + $this->queued = array(); + $this->groups = array(); + foreach ($this->handles as $handle) { + $handle->reset(); + } + if ($hard) { + $this->handles = array(); + } + + return $this; + } + + public function send() + { + if ($this->queued) { + $group = $this->getAvailableHandle(); + // Add this handle to a list of handles than is claimed + $this->groups[] = $group; + while ($request = array_shift($this->queued)) { + $group->add($request); + } + try { + $group->send(); + array_pop($this->groups); + $this->cleanupHandles(); + } catch (\Exception $e) { + // Remove the group and cleanup if an exception was encountered and no more requests in group + if (!$group->count()) { + array_pop($this->groups); + $this->cleanupHandles(); + } + throw $e; + } + } + } + + public function count() + { + return count($this->all()); + } + + /** + * Get an existing available CurlMulti handle or create a new one + * + * @return CurlMulti + */ + protected function getAvailableHandle() + { + // Grab a handle that is not claimed + foreach ($this->handles as $h) { + if (!in_array($h, $this->groups, true)) { + return $h; + } + } + + // All are claimed, so create one + $handle = new CurlMulti($this->selectTimeout); + $handle->setEventDispatcher($this->getEventDispatcher()); + $this->handles[] = $handle; + + return $handle; + } + + /** + * Trims down unused CurlMulti handles to limit the number of open connections + */ + protected function cleanupHandles() + { + if ($diff = max(0, count($this->handles) - $this->maxHandles)) { + for ($i = count($this->handles) - 1; $i > 0 && $diff > 0; $i--) { + if (!count($this->handles[$i])) { + unset($this->handles[$i]); + $diff--; + } + } + $this->handles = array_values($this->handles); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php new file mode 100644 index 0000000..c3f99dd --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/CurlVersion.php @@ -0,0 +1,66 @@ +version) { + $this->version = curl_version(); + } + + return $this->version; + } + + /** + * Get a specific type of curl information + * + * @param string $type Version information to retrieve. This value is one of: + * - version_number: cURL 24 bit version number + * - version: cURL version number, as a string + * - ssl_version_number: OpenSSL 24 bit version number + * - ssl_version: OpenSSL version number, as a string + * - libz_version: zlib version number, as a string + * - host: Information about the host where cURL was built + * - features: A bitmask of the CURL_VERSION_XXX constants + * - protocols: An array of protocols names supported by cURL + * + * @return string|float|bool if the $type is found, and false if not found + */ + public function get($type) + { + $version = $this->getAll(); + + return isset($version[$type]) ? $version[$type] : false; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php new file mode 100644 index 0000000..5d1a0cd --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Curl/RequestMediator.php @@ -0,0 +1,147 @@ +request = $request; + $this->emitIo = $emitIo; + } + + /** + * Receive a response header from curl + * + * @param resource $curl Curl handle + * @param string $header Received header + * + * @return int + */ + public function receiveResponseHeader($curl, $header) + { + static $normalize = array("\r", "\n"); + $length = strlen($header); + $header = str_replace($normalize, '', $header); + + if (strpos($header, 'HTTP/') === 0) { + + $startLine = explode(' ', $header, 3); + $code = $startLine[1]; + $status = isset($startLine[2]) ? $startLine[2] : ''; + + // Only download the body of the response to the specified response + // body when a successful response is received. + if ($code >= 200 && $code < 300) { + $body = $this->request->getResponseBody(); + } else { + $body = EntityBody::factory(); + } + + $response = new Response($code, null, $body); + $response->setStatus($code, $status); + $this->request->startResponse($response); + + $this->request->dispatch('request.receive.status_line', array( + 'request' => $this, + 'line' => $header, + 'status_code' => $code, + 'reason_phrase' => $status + )); + + } elseif ($pos = strpos($header, ':')) { + $this->request->getResponse()->addHeader( + trim(substr($header, 0, $pos)), + trim(substr($header, $pos + 1)) + ); + } + + return $length; + } + + /** + * Received a progress notification + * + * @param int $downloadSize Total download size + * @param int $downloaded Amount of bytes downloaded + * @param int $uploadSize Total upload size + * @param int $uploaded Amount of bytes uploaded + * @param resource $handle CurlHandle object + */ + public function progress($downloadSize, $downloaded, $uploadSize, $uploaded, $handle = null) + { + $this->request->dispatch('curl.callback.progress', array( + 'request' => $this->request, + 'handle' => $handle, + 'download_size' => $downloadSize, + 'downloaded' => $downloaded, + 'upload_size' => $uploadSize, + 'uploaded' => $uploaded + )); + } + + /** + * Write data to the response body of a request + * + * @param resource $curl Curl handle + * @param string $write Data that was received + * + * @return int + */ + public function writeResponseBody($curl, $write) + { + if ($this->emitIo) { + $this->request->dispatch('curl.callback.write', array( + 'request' => $this->request, + 'write' => $write + )); + } + + if ($response = $this->request->getResponse()) { + return $response->getBody()->write($write); + } else { + // Unexpected data received before response headers - abort transfer + return 0; + } + } + + /** + * Read data from the request body and send it to curl + * + * @param resource $ch Curl handle + * @param resource $fd File descriptor + * @param int $length Amount of data to read + * + * @return string + */ + public function readRequestBody($ch, $fd, $length) + { + if (!($body = $this->request->getBody())) { + return ''; + } + + $read = (string) $body->read($length); + if ($this->emitIo) { + $this->request->dispatch('curl.callback.read', array('request' => $this->request, 'read' => $read)); + } + + return $read; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php new file mode 100644 index 0000000..b60d170 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBody.php @@ -0,0 +1,201 @@ +rewindFunction = $callable; + + return $this; + } + + public function rewind() + { + return $this->rewindFunction ? call_user_func($this->rewindFunction, $this) : parent::rewind(); + } + + /** + * Create a new EntityBody from a string + * + * @param string $string String of data + * + * @return EntityBody + */ + public static function fromString($string) + { + $stream = fopen('php://temp', 'r+'); + if ($string !== '') { + fwrite($stream, $string); + rewind($stream); + } + + return new static($stream); + } + + public function compress($filter = 'zlib.deflate') + { + $result = $this->handleCompression($filter); + $this->contentEncoding = $result ? $filter : false; + + return $result; + } + + public function uncompress($filter = 'zlib.inflate') + { + $offsetStart = 0; + + // When inflating gzipped data, the first 10 bytes must be stripped + // if a gzip header is present + if ($filter == 'zlib.inflate') { + // @codeCoverageIgnoreStart + if (!$this->isReadable() || ($this->isConsumed() && !$this->isSeekable())) { + return false; + } + // @codeCoverageIgnoreEnd + if (stream_get_contents($this->stream, 3, 0) === "\x1f\x8b\x08") { + $offsetStart = 10; + } + } + + $this->contentEncoding = false; + + return $this->handleCompression($filter, $offsetStart); + } + + public function getContentLength() + { + return $this->getSize(); + } + + public function getContentType() + { + return $this->getUri() ? Mimetypes::getInstance()->fromFilename($this->getUri()) : null; + } + + public function getContentMd5($rawOutput = false, $base64Encode = false) + { + if ($hash = self::getHash($this, 'md5', $rawOutput)) { + return $hash && $base64Encode ? base64_encode($hash) : $hash; + } else { + return false; + } + } + + /** + * Calculate the MD5 hash of an entity body + * + * @param EntityBodyInterface $body Entity body to calculate the hash for + * @param bool $rawOutput Whether or not to use raw output + * @param bool $base64Encode Whether or not to base64 encode raw output (only if raw output is true) + * + * @return bool|string Returns an MD5 string on success or FALSE on failure + * @deprecated This will be deprecated soon + * @codeCoverageIgnore + */ + public static function calculateMd5(EntityBodyInterface $body, $rawOutput = false, $base64Encode = false) + { + Version::warn(__CLASS__ . ' is deprecated. Use getContentMd5()'); + return $body->getContentMd5($rawOutput, $base64Encode); + } + + public function setStreamFilterContentEncoding($streamFilterContentEncoding) + { + $this->contentEncoding = $streamFilterContentEncoding; + + return $this; + } + + public function getContentEncoding() + { + return strtr($this->contentEncoding, array( + 'zlib.deflate' => 'gzip', + 'bzip2.compress' => 'compress' + )) ?: false; + } + + protected function handleCompression($filter, $offsetStart = 0) + { + // @codeCoverageIgnoreStart + if (!$this->isReadable() || ($this->isConsumed() && !$this->isSeekable())) { + return false; + } + // @codeCoverageIgnoreEnd + + $handle = fopen('php://temp', 'r+'); + $filter = @stream_filter_append($handle, $filter, STREAM_FILTER_WRITE); + if (!$filter) { + return false; + } + + // Seek to the offset start if possible + $this->seek($offsetStart); + while ($data = fread($this->stream, 8096)) { + fwrite($handle, $data); + } + + fclose($this->stream); + $this->stream = $handle; + stream_filter_remove($filter); + $stat = fstat($this->stream); + $this->size = $stat['size']; + $this->rebuildCache(); + $this->seek(0); + + // Remove any existing rewind function as the underlying stream has been replaced + $this->rewindFunction = null; + + return true; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php new file mode 100644 index 0000000..e640f57 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/EntityBodyInterface.php @@ -0,0 +1,73 @@ +isClientError()) { + $label = 'Client error response'; + $class = __NAMESPACE__ . '\\ClientErrorResponseException'; + } elseif ($response->isServerError()) { + $label = 'Server error response'; + $class = __NAMESPACE__ . '\\ServerErrorResponseException'; + } else { + $label = 'Unsuccessful response'; + $class = __CLASS__; + } + + $message = $label . PHP_EOL . implode(PHP_EOL, array( + '[status code] ' . $response->getStatusCode(), + '[reason phrase] ' . $response->getReasonPhrase(), + '[url] ' . $request->getUrl(), + )); + + $e = new $class($message); + $e->setResponse($response); + $e->setRequest($request); + + return $e; + } + + /** + * Set the response that caused the exception + * + * @param Response $response Response to set + */ + public function setResponse(Response $response) + { + $this->response = $response; + } + + /** + * Get the response that caused the exception + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php new file mode 100644 index 0000000..04d7ddc --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ClientErrorResponseException.php @@ -0,0 +1,8 @@ +curlError = $error; + $this->curlErrorNo = $number; + + return $this; + } + + /** + * Set the associated curl handle + * + * @param CurlHandle $handle Curl handle + * + * @return self + */ + public function setCurlHandle(CurlHandle $handle) + { + $this->handle = $handle; + + return $this; + } + + /** + * Get the associated cURL handle + * + * @return CurlHandle|null + */ + public function getCurlHandle() + { + return $this->handle; + } + + /** + * Get the associated cURL error message + * + * @return string|null + */ + public function getError() + { + return $this->curlError; + } + + /** + * Get the associated cURL error number + * + * @return int|null + */ + public function getErrorNo() + { + return $this->curlErrorNo; + } + + /** + * Returns curl information about the transfer + * + * @return array + */ + public function getCurlInfo() + { + return $this->curlInfo; + } + + /** + * Set curl transfer information + * + * @param array $info Array of curl transfer information + * + * @return self + * @link http://php.net/manual/en/function.curl-getinfo.php + */ + public function setCurlInfo(array $info) + { + $this->curlInfo = $info; + + return $this; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php new file mode 100644 index 0000000..ee87295 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/HttpException.php @@ -0,0 +1,10 @@ +successfulRequests, $this->failedRequests); + } + + /** + * Add to the array of successful requests + * + * @param RequestInterface $request Successful request + * + * @return self + */ + public function addSuccessfulRequest(RequestInterface $request) + { + $this->successfulRequests[] = $request; + + return $this; + } + + /** + * Add to the array of failed requests + * + * @param RequestInterface $request Failed request + * + * @return self + */ + public function addFailedRequest(RequestInterface $request) + { + $this->failedRequests[] = $request; + + return $this; + } + + /** + * Add to the array of failed requests and associate with exceptions + * + * @param RequestInterface $request Failed request + * @param \Exception $exception Exception to add and associate with + * + * @return self + */ + public function addFailedRequestWithException(RequestInterface $request, \Exception $exception) + { + $this->add($exception) + ->addFailedRequest($request) + ->exceptionForRequest[spl_object_hash($request)] = $exception; + + return $this; + } + + /** + * Get the Exception that caused the given $request to fail + * + * @param RequestInterface $request Failed command + * + * @return \Exception|null + */ + public function getExceptionForFailedRequest(RequestInterface $request) + { + $oid = spl_object_hash($request); + + return isset($this->exceptionForRequest[$oid]) ? $this->exceptionForRequest[$oid] : null; + } + + /** + * Set all of the successful requests + * + * @param array Array of requests + * + * @return self + */ + public function setSuccessfulRequests(array $requests) + { + $this->successfulRequests = $requests; + + return $this; + } + + /** + * Set all of the failed requests + * + * @param array Array of requests + * + * @return self + */ + public function setFailedRequests(array $requests) + { + $this->failedRequests = $requests; + + return $this; + } + + /** + * Get an array of successful requests sent in the multi transfer + * + * @return array + */ + public function getSuccessfulRequests() + { + return $this->successfulRequests; + } + + /** + * Get an array of failed requests sent in the multi transfer + * + * @return array + */ + public function getFailedRequests() + { + return $this->failedRequests; + } + + /** + * Check if the exception object contains a request + * + * @param RequestInterface $request Request to check + * + * @return bool + */ + public function containsRequest(RequestInterface $request) + { + return in_array($request, $this->failedRequests, true) || in_array($request, $this->successfulRequests, true); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php new file mode 100644 index 0000000..274df2c --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/RequestException.php @@ -0,0 +1,39 @@ +request = $request; + + return $this; + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php new file mode 100644 index 0000000..f0f7cfe --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Exception/ServerErrorResponseException.php @@ -0,0 +1,8 @@ +eventDispatcher = $eventDispatcher; + + return $this; + } + + public function getEventDispatcher() + { + if (!$this->eventDispatcher) { + $this->eventDispatcher = new EventDispatcher(); + } + + return $this->eventDispatcher; + } + + public function dispatch($eventName, array $context = array()) + { + return $this->getEventDispatcher()->dispatch($eventName, new Event($context)); + } + + /** + * {@inheritdoc} + * @codeCoverageIgnore + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->getEventDispatcher()->addSubscriber($subscriber); + + return $this; + } + + public function read($length) + { + $event = array( + 'body' => $this, + 'length' => $length, + 'read' => $this->body->read($length) + ); + $this->dispatch('body.read', $event); + + return $event['read']; + } + + public function write($string) + { + $event = array( + 'body' => $this, + 'write' => $string, + 'result' => $this->body->write($string) + ); + $this->dispatch('body.write', $event); + + return $event['result']; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php new file mode 100644 index 0000000..0d066ff --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/AbstractMessage.php @@ -0,0 +1,220 @@ +params = new Collection(); + $this->headerFactory = new HeaderFactory(); + $this->headers = new HeaderCollection(); + } + + /** + * Set the header factory to use to create headers + * + * @param HeaderFactoryInterface $factory + * + * @return self + */ + public function setHeaderFactory(HeaderFactoryInterface $factory) + { + $this->headerFactory = $factory; + + return $this; + } + + public function getParams() + { + return $this->params; + } + + public function addHeader($header, $value) + { + if (isset($this->headers[$header])) { + $this->headers[$header]->add($value); + } elseif ($value instanceof HeaderInterface) { + $this->headers[$header] = $value; + } else { + $this->headers[$header] = $this->headerFactory->createHeader($header, $value); + } + + return $this; + } + + public function addHeaders(array $headers) + { + foreach ($headers as $key => $value) { + $this->addHeader($key, $value); + } + + return $this; + } + + public function getHeader($header) + { + return $this->headers[$header]; + } + + public function getHeaders() + { + return $this->headers; + } + + public function getHeaderLines() + { + $headers = array(); + foreach ($this->headers as $value) { + $headers[] = $value->getName() . ': ' . $value; + } + + return $headers; + } + + public function setHeader($header, $value) + { + unset($this->headers[$header]); + $this->addHeader($header, $value); + + return $this; + } + + public function setHeaders(array $headers) + { + $this->headers->clear(); + foreach ($headers as $key => $value) { + $this->addHeader($key, $value); + } + + return $this; + } + + public function hasHeader($header) + { + return isset($this->headers[$header]); + } + + public function removeHeader($header) + { + unset($this->headers[$header]); + + return $this; + } + + /** + * @deprecated Use $message->getHeader()->parseParams() + * @codeCoverageIgnore + */ + public function getTokenizedHeader($header, $token = ';') + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader()->parseParams()'); + if ($this->hasHeader($header)) { + $data = new Collection(); + foreach ($this->getHeader($header)->parseParams() as $values) { + foreach ($values as $key => $value) { + if ($value === '') { + $data->set($data->count(), $key); + } else { + $data->add($key, $value); + } + } + } + return $data; + } + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function setTokenizedHeader($header, $data, $token = ';') + { + Version::warn(__METHOD__ . ' is deprecated.'); + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getCacheControlDirective($directive) + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->getDirective()'); + if (!($header = $this->getHeader('Cache-Control'))) { + return null; + } + + return $header->getDirective($directive); + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function hasCacheControlDirective($directive) + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->hasDirective()'); + if ($header = $this->getHeader('Cache-Control')) { + return $header->hasDirective($directive); + } else { + return false; + } + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function addCacheControlDirective($directive, $value = true) + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->addDirective()'); + if (!($header = $this->getHeader('Cache-Control'))) { + $this->addHeader('Cache-Control', ''); + $header = $this->getHeader('Cache-Control'); + } + + $header->addDirective($directive, $value); + + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function removeCacheControlDirective($directive) + { + Version::warn(__METHOD__ . ' is deprecated. Use $message->getHeader(\'Cache-Control\')->removeDirective()'); + if ($header = $this->getHeader('Cache-Control')) { + $header->removeDirective($directive); + } + + return $this; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php new file mode 100644 index 0000000..212850a --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequest.php @@ -0,0 +1,247 @@ +postFields = new QueryString(); + parent::__construct($method, $url, $headers); + } + + /** + * @return string + */ + public function __toString() + { + // Only attempt to include the POST data if it's only fields + if (count($this->postFields) && empty($this->postFiles)) { + return parent::__toString() . (string) $this->postFields; + } + + return parent::__toString() . $this->body; + } + + public function setState($state, array $context = array()) + { + parent::setState($state, $context); + if ($state == self::STATE_TRANSFER && !$this->body && !count($this->postFields) && !count($this->postFiles)) { + $this->setHeader('Content-Length', 0)->removeHeader('Transfer-Encoding'); + } + + return $this->state; + } + + public function setBody($body, $contentType = null) + { + $this->body = EntityBody::factory($body); + + // Auto detect the Content-Type from the path of the request if possible + if ($contentType === null && !$this->hasHeader('Content-Type')) { + $contentType = $this->body->getContentType(); + } + + if ($contentType) { + $this->setHeader('Content-Type', $contentType); + } + + // Always add the Expect 100-Continue header if the body cannot be rewound. This helps with redirects. + if (!$this->body->isSeekable() && $this->expectCutoff !== false) { + $this->setHeader('Expect', '100-Continue'); + } + + // Set the Content-Length header if it can be determined + $size = $this->body->getContentLength(); + if ($size !== null && $size !== false) { + $this->setHeader('Content-Length', $size); + if ($size > $this->expectCutoff) { + $this->setHeader('Expect', '100-Continue'); + } + } elseif (!$this->hasHeader('Content-Length')) { + if ('1.1' == $this->protocolVersion) { + $this->setHeader('Transfer-Encoding', 'chunked'); + } else { + throw new RequestException( + 'Cannot determine Content-Length and cannot use chunked Transfer-Encoding when using HTTP/1.0' + ); + } + } + + return $this; + } + + public function getBody() + { + return $this->body; + } + + /** + * Set the size that the entity body of the request must exceed before adding the Expect: 100-Continue header. + * + * @param int|bool $size Cutoff in bytes. Set to false to never send the expect header (even with non-seekable data) + * + * @return self + */ + public function setExpectHeaderCutoff($size) + { + $this->expectCutoff = $size; + if ($size === false || !$this->body) { + $this->removeHeader('Expect'); + } elseif ($this->body && $this->body->getSize() && $this->body->getSize() > $size) { + $this->setHeader('Expect', '100-Continue'); + } + + return $this; + } + + public function configureRedirects($strict = false, $maxRedirects = 5) + { + $this->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, $strict); + if ($maxRedirects == 0) { + $this->getParams()->set(RedirectPlugin::DISABLE, true); + } else { + $this->getParams()->set(RedirectPlugin::MAX_REDIRECTS, $maxRedirects); + } + + return $this; + } + + public function getPostField($field) + { + return $this->postFields->get($field); + } + + public function getPostFields() + { + return $this->postFields; + } + + public function setPostField($key, $value) + { + $this->postFields->set($key, $value); + $this->processPostFields(); + + return $this; + } + + public function addPostFields($fields) + { + $this->postFields->merge($fields); + $this->processPostFields(); + + return $this; + } + + public function removePostField($field) + { + $this->postFields->remove($field); + $this->processPostFields(); + + return $this; + } + + public function getPostFiles() + { + return $this->postFiles; + } + + public function getPostFile($fieldName) + { + return isset($this->postFiles[$fieldName]) ? $this->postFiles[$fieldName] : null; + } + + public function removePostFile($fieldName) + { + unset($this->postFiles[$fieldName]); + $this->processPostFields(); + + return $this; + } + + public function addPostFile($field, $filename = null, $contentType = null, $postname = null) + { + $data = null; + + if ($field instanceof PostFileInterface) { + $data = $field; + } elseif (is_array($filename)) { + // Allow multiple values to be set in a single key + foreach ($filename as $file) { + $this->addPostFile($field, $file, $contentType); + } + return $this; + } elseif (!is_string($filename)) { + throw new RequestException('The path to a file must be a string'); + } elseif (!empty($filename)) { + // Adding an empty file will cause cURL to error out + $data = new PostFile($field, $filename, $contentType, $postname); + } + + if ($data) { + if (!isset($this->postFiles[$data->getFieldName()])) { + $this->postFiles[$data->getFieldName()] = array($data); + } else { + $this->postFiles[$data->getFieldName()][] = $data; + } + $this->processPostFields(); + } + + return $this; + } + + public function addPostFiles(array $files) + { + foreach ($files as $key => $file) { + if ($file instanceof PostFileInterface) { + $this->addPostFile($file, null, null, false); + } elseif (is_string($file)) { + // Convert non-associative array keys into 'file' + if (is_numeric($key)) { + $key = 'file'; + } + $this->addPostFile($key, $file, null, false); + } else { + throw new RequestException('File must be a string or instance of PostFileInterface'); + } + } + + return $this; + } + + /** + * Determine what type of request should be sent based on post fields + */ + protected function processPostFields() + { + if (!$this->postFiles) { + $this->removeHeader('Expect')->setHeader('Content-Type', self::URL_ENCODED); + } else { + $this->setHeader('Content-Type', self::MULTIPART); + if ($this->expectCutoff !== false) { + $this->setHeader('Expect', '100-Continue'); + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php new file mode 100644 index 0000000..49ad459 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/EntityEnclosingRequestInterface.php @@ -0,0 +1,137 @@ + filenames where filename can be a string or PostFileInterface + * + * @return self + */ + public function addPostFiles(array $files); + + /** + * Configure how redirects are handled for the request + * + * @param bool $strict Set to true to follow strict RFC compliance when redirecting POST requests. Most + * browsers with follow a 301-302 redirect for a POST request with a GET request. This is + * the default behavior of Guzzle. Enable strict redirects to redirect these responses + * with a POST rather than a GET request. + * @param int $maxRedirects Specify the maximum number of allowed redirects. Set to 0 to disable redirects. + * + * @return self + */ + public function configureRedirects($strict = false, $maxRedirects = 5); +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php new file mode 100644 index 0000000..50597b2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header.php @@ -0,0 +1,182 @@ +header = trim($header); + $this->glue = $glue; + + foreach ((array) $values as $value) { + foreach ((array) $value as $v) { + $this->values[] = $v; + } + } + } + + public function __toString() + { + return implode($this->glue . ' ', $this->toArray()); + } + + public function add($value) + { + $this->values[] = $value; + + return $this; + } + + public function getName() + { + return $this->header; + } + + public function setName($name) + { + $this->header = $name; + + return $this; + } + + public function setGlue($glue) + { + $this->glue = $glue; + + return $this; + } + + public function getGlue() + { + return $this->glue; + } + + /** + * Normalize the header to be a single header with an array of values. + * + * If any values of the header contains the glue string value (e.g. ","), then the value will be exploded into + * multiple entries in the header. + * + * @return self + */ + public function normalize() + { + $values = $this->toArray(); + + for ($i = 0, $total = count($values); $i < $total; $i++) { + if (strpos($values[$i], $this->glue) !== false) { + // Explode on glue when the glue is not inside of a comma + foreach (preg_split('/' . preg_quote($this->glue) . '(?=([^"]*"[^"]*")*[^"]*$)/', $values[$i]) as $v) { + $values[] = trim($v); + } + unset($values[$i]); + } + } + + $this->values = array_values($values); + + return $this; + } + + public function hasValue($searchValue) + { + return in_array($searchValue, $this->toArray()); + } + + public function removeValue($searchValue) + { + $this->values = array_values(array_filter($this->values, function ($value) use ($searchValue) { + return $value != $searchValue; + })); + + return $this; + } + + public function toArray() + { + return $this->values; + } + + public function count() + { + return count($this->toArray()); + } + + public function getIterator() + { + return new \ArrayIterator($this->toArray()); + } + + public function parseParams() + { + $params = $matches = array(); + $callback = array($this, 'trimHeader'); + + // Normalize the header into a single array and iterate over all values + foreach ($this->normalize()->toArray() as $val) { + $part = array(); + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (!preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + continue; + } + $pieces = array_map($callback, $matches[0]); + $part[$pieces[0]] = isset($pieces[1]) ? $pieces[1] : ''; + } + if ($part) { + $params[] = $part; + } + } + + return $params; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function hasExactHeader($header) + { + Version::warn(__METHOD__ . ' is deprecated'); + return $this->header == $header; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function raw() + { + Version::warn(__METHOD__ . ' is deprecated. Use toArray()'); + return $this->toArray(); + } + + /** + * Trim a header by removing excess spaces and wrapping quotes + * + * @param $str + * + * @return string + */ + protected function trimHeader($str) + { + static $trimmed = "\"' \n\t"; + + return trim($str, $trimmed); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php new file mode 100644 index 0000000..77789e5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/CacheControl.php @@ -0,0 +1,121 @@ +directives = null; + } + + public function removeValue($searchValue) + { + parent::removeValue($searchValue); + $this->directives = null; + } + + /** + * Check if a specific cache control directive exists + * + * @param string $param Directive to retrieve + * + * @return bool + */ + public function hasDirective($param) + { + $directives = $this->getDirectives(); + + return isset($directives[$param]); + } + + /** + * Get a specific cache control directive + * + * @param string $param Directive to retrieve + * + * @return string|bool|null + */ + public function getDirective($param) + { + $directives = $this->getDirectives(); + + return isset($directives[$param]) ? $directives[$param] : null; + } + + /** + * Add a cache control directive + * + * @param string $param Directive to add + * @param string $value Value to set + * + * @return self + */ + public function addDirective($param, $value) + { + $directives = $this->getDirectives(); + $directives[$param] = $value; + $this->updateFromDirectives($directives); + + return $this; + } + + /** + * Remove a cache control directive by name + * + * @param string $param Directive to remove + * + * @return self + */ + public function removeDirective($param) + { + $directives = $this->getDirectives(); + unset($directives[$param]); + $this->updateFromDirectives($directives); + + return $this; + } + + /** + * Get an associative array of cache control directives + * + * @return array + */ + public function getDirectives() + { + if ($this->directives === null) { + $this->directives = array(); + foreach ($this->parseParams() as $collection) { + foreach ($collection as $key => $value) { + $this->directives[$key] = $value === '' ? true : $value; + } + } + } + + return $this->directives; + } + + /** + * Updates the header value based on the parsed directives + * + * @param array $directives Array of cache control directives + */ + protected function updateFromDirectives(array $directives) + { + $this->directives = $directives; + $this->values = array(); + + foreach ($directives as $key => $value) { + $this->values[] = $value === true ? $key : "{$key}={$value}"; + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php new file mode 100644 index 0000000..8c7f6ae --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderCollection.php @@ -0,0 +1,108 @@ +headers = $headers; + } + + public function __clone() + { + foreach ($this->headers as &$header) { + $header = clone $header; + } + } + + /** + * Clears the header collection + */ + public function clear() + { + $this->headers = array(); + } + + /** + * Set a header on the collection + * + * @param HeaderInterface $header Header to add + * + * @return self + */ + public function add(HeaderInterface $header) + { + $this->headers[strtolower($header->getName())] = $header; + + return $this; + } + + /** + * Get an array of header objects + * + * @return array + */ + public function getAll() + { + return $this->headers; + } + + /** + * Alias of offsetGet + */ + public function get($key) + { + return $this->offsetGet($key); + } + + public function count() + { + return count($this->headers); + } + + public function offsetExists($offset) + { + return isset($this->headers[strtolower($offset)]); + } + + public function offsetGet($offset) + { + $l = strtolower($offset); + + return isset($this->headers[$l]) ? $this->headers[$l] : null; + } + + public function offsetSet($offset, $value) + { + $this->add($value); + } + + public function offsetUnset($offset) + { + unset($this->headers[strtolower($offset)]); + } + + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + public function toArray() + { + $result = array(); + foreach ($this->headers as $header) { + $result[$header->getName()] = $header->toArray(); + } + + return $result; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php new file mode 100644 index 0000000..0273be5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactory.php @@ -0,0 +1,26 @@ + 'Guzzle\Http\Message\Header\CacheControl', + 'link' => 'Guzzle\Http\Message\Header\Link', + ); + + public function createHeader($header, $value = null) + { + $lowercase = strtolower($header); + + return isset($this->mapping[$lowercase]) + ? new $this->mapping[$lowercase]($header, $value) + : new Header($header, $value); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php new file mode 100644 index 0000000..9457cf6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/Header/HeaderFactoryInterface.php @@ -0,0 +1,19 @@ +", "rel=\"{$rel}\""); + + foreach ($params as $k => $v) { + $values[] = "{$k}=\"{$v}\""; + } + + return $this->add(implode('; ', $values)); + } + + /** + * Check if a specific link exists for a given rel attribute + * + * @param string $rel rel value + * + * @return bool + */ + public function hasLink($rel) + { + return $this->getLink($rel) !== null; + } + + /** + * Get a specific link for a given rel attribute + * + * @param string $rel Rel value + * + * @return array|null + */ + public function getLink($rel) + { + foreach ($this->getLinks() as $link) { + if (isset($link['rel']) && $link['rel'] == $rel) { + return $link; + } + } + + return null; + } + + /** + * Get an associative array of links + * + * For example: + * Link: ; rel=front; type="image/jpeg", ; rel=back; type="image/jpeg" + * + * + * var_export($response->getLinks()); + * array( + * array( + * 'url' => 'http:/.../front.jpeg', + * 'rel' => 'back', + * 'type' => 'image/jpeg', + * ) + * ) + * + * + * @return array + */ + public function getLinks() + { + $links = $this->parseParams(); + + foreach ($links as &$link) { + $key = key($link); + unset($link[$key]); + $link['url'] = trim($key, '<> '); + } + + return $links; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php new file mode 100644 index 0000000..62bcd43 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/MessageInterface.php @@ -0,0 +1,102 @@ +fieldName = $fieldName; + $this->setFilename($filename); + $this->postname = $postname ? $postname : basename($filename); + $this->contentType = $contentType ?: $this->guessContentType(); + } + + public function setFieldName($name) + { + $this->fieldName = $name; + + return $this; + } + + public function getFieldName() + { + return $this->fieldName; + } + + public function setFilename($filename) + { + // Remove leading @ symbol + if (strpos($filename, '@') === 0) { + $filename = substr($filename, 1); + } + + if (!is_readable($filename)) { + throw new InvalidArgumentException("Unable to open {$filename} for reading"); + } + + $this->filename = $filename; + + return $this; + } + + public function setPostname($postname) + { + $this->postname = $postname; + + return $this; + } + + public function getFilename() + { + return $this->filename; + } + + public function getPostname() + { + return $this->postname; + } + + public function setContentType($type) + { + $this->contentType = $type; + + return $this; + } + + public function getContentType() + { + return $this->contentType; + } + + public function getCurlValue() + { + // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax + // See: https://wiki.php.net/rfc/curl-file-upload + if (function_exists('curl_file_create')) { + return curl_file_create($this->filename, $this->contentType, $this->postname); + } + + // Use the old style if using an older version of PHP + $value = "@{$this->filename};filename=" . $this->postname; + if ($this->contentType) { + $value .= ';type=' . $this->contentType; + } + + return $value; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getCurlString() + { + Version::warn(__METHOD__ . ' is deprecated. Use getCurlValue()'); + return $this->getCurlValue(); + } + + /** + * Determine the Content-Type of the file + */ + protected function guessContentType() + { + return Mimetypes::getInstance()->fromFilename($this->filename) ?: 'application/octet-stream'; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php new file mode 100644 index 0000000..7f0779d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/PostFileInterface.php @@ -0,0 +1,83 @@ +method = strtoupper($method); + $this->curlOptions = new Collection(); + $this->setUrl($url); + + if ($headers) { + // Special handling for multi-value headers + foreach ($headers as $key => $value) { + // Deal with collisions with Host and Authorization + if ($key == 'host' || $key == 'Host') { + $this->setHeader($key, $value); + } elseif ($value instanceof HeaderInterface) { + $this->addHeader($key, $value); + } else { + foreach ((array) $value as $v) { + $this->addHeader($key, $v); + } + } + } + } + + $this->setState(self::STATE_NEW); + } + + public function __clone() + { + if ($this->eventDispatcher) { + $this->eventDispatcher = clone $this->eventDispatcher; + } + $this->curlOptions = clone $this->curlOptions; + $this->params = clone $this->params; + $this->url = clone $this->url; + $this->response = $this->responseBody = null; + $this->headers = clone $this->headers; + + $this->setState(RequestInterface::STATE_NEW); + $this->dispatch('request.clone', array('request' => $this)); + } + + /** + * Get the HTTP request as a string + * + * @return string + */ + public function __toString() + { + return $this->getRawHeaders() . "\r\n\r\n"; + } + + /** + * Default method that will throw exceptions if an unsuccessful response is received. + * + * @param Event $event Received + * @throws BadResponseException if the response is not successful + */ + public static function onRequestError(Event $event) + { + $e = BadResponseException::factory($event['request'], $event['response']); + $event['request']->setState(self::STATE_ERROR, array('exception' => $e) + $event->toArray()); + throw $e; + } + + public function setClient(ClientInterface $client) + { + $this->client = $client; + + return $this; + } + + public function getClient() + { + return $this->client; + } + + public function getRawHeaders() + { + $protocolVersion = $this->protocolVersion ?: '1.1'; + + return trim($this->method . ' ' . $this->getResource()) . ' ' + . strtoupper(str_replace('https', 'http', $this->url->getScheme())) + . '/' . $protocolVersion . "\r\n" . implode("\r\n", $this->getHeaderLines()); + } + + public function setUrl($url) + { + if ($url instanceof Url) { + $this->url = $url; + } else { + $this->url = Url::factory($url); + } + + // Update the port and host header + $this->setPort($this->url->getPort()); + + if ($this->url->getUsername() || $this->url->getPassword()) { + $this->setAuth($this->url->getUsername(), $this->url->getPassword()); + // Remove the auth info from the URL + $this->url->setUsername(null); + $this->url->setPassword(null); + } + + return $this; + } + + public function send() + { + if (!$this->client) { + throw new RuntimeException('A client must be set on the request'); + } + + return $this->client->send($this); + } + + public function getResponse() + { + return $this->response; + } + + public function getQuery($asString = false) + { + return $asString + ? (string) $this->url->getQuery() + : $this->url->getQuery(); + } + + public function getMethod() + { + return $this->method; + } + + public function getScheme() + { + return $this->url->getScheme(); + } + + public function setScheme($scheme) + { + $this->url->setScheme($scheme); + + return $this; + } + + public function getHost() + { + return $this->url->getHost(); + } + + public function setHost($host) + { + $this->url->setHost($host); + $this->setPort($this->url->getPort()); + + return $this; + } + + public function getProtocolVersion() + { + return $this->protocolVersion; + } + + public function setProtocolVersion($protocol) + { + $this->protocolVersion = $protocol; + + return $this; + } + + public function getPath() + { + return '/' . ltrim($this->url->getPath(), '/'); + } + + public function setPath($path) + { + $this->url->setPath($path); + + return $this; + } + + public function getPort() + { + return $this->url->getPort(); + } + + public function setPort($port) + { + $this->url->setPort($port); + + // Include the port in the Host header if it is not the default port for the scheme of the URL + $scheme = $this->url->getScheme(); + if ($port && (($scheme == 'http' && $port != 80) || ($scheme == 'https' && $port != 443))) { + $this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost() . ':' . $port); + } else { + $this->headers['host'] = $this->headerFactory->createHeader('Host', $this->url->getHost()); + } + + return $this; + } + + public function getUsername() + { + return $this->username; + } + + public function getPassword() + { + return $this->password; + } + + public function setAuth($user, $password = '', $scheme = CURLAUTH_BASIC) + { + static $authMap = array( + 'basic' => CURLAUTH_BASIC, + 'digest' => CURLAUTH_DIGEST, + 'ntlm' => CURLAUTH_NTLM, + 'any' => CURLAUTH_ANY + ); + + // If we got false or null, disable authentication + if (!$user) { + $this->password = $this->username = null; + $this->removeHeader('Authorization'); + $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH); + return $this; + } + + if (!is_numeric($scheme)) { + $scheme = strtolower($scheme); + if (!isset($authMap[$scheme])) { + throw new InvalidArgumentException($scheme . ' is not a valid authentication type'); + } + $scheme = $authMap[$scheme]; + } + + $this->username = $user; + $this->password = $password; + + // Bypass CURL when using basic auth to promote connection reuse + if ($scheme == CURLAUTH_BASIC) { + $this->getCurlOptions()->remove(CURLOPT_HTTPAUTH); + $this->setHeader('Authorization', 'Basic ' . base64_encode($this->username . ':' . $this->password)); + } else { + $this->getCurlOptions() + ->set(CURLOPT_HTTPAUTH, $scheme) + ->set(CURLOPT_USERPWD, $this->username . ':' . $this->password); + } + + return $this; + } + + public function getResource() + { + $resource = $this->getPath(); + if ($query = (string) $this->url->getQuery()) { + $resource .= '?' . $query; + } + + return $resource; + } + + public function getUrl($asObject = false) + { + return $asObject ? clone $this->url : (string) $this->url; + } + + public function getState() + { + return $this->state; + } + + public function setState($state, array $context = array()) + { + $oldState = $this->state; + $this->state = $state; + + switch ($state) { + case self::STATE_NEW: + $this->response = null; + break; + case self::STATE_TRANSFER: + if ($oldState !== $state) { + // Fix Content-Length and Transfer-Encoding collisions + if ($this->hasHeader('Transfer-Encoding') && $this->hasHeader('Content-Length')) { + $this->removeHeader('Transfer-Encoding'); + } + $this->dispatch('request.before_send', array('request' => $this)); + } + break; + case self::STATE_COMPLETE: + if ($oldState !== $state) { + $this->processResponse($context); + $this->responseBody = null; + } + break; + case self::STATE_ERROR: + if (isset($context['exception'])) { + $this->dispatch('request.exception', array( + 'request' => $this, + 'response' => isset($context['response']) ? $context['response'] : $this->response, + 'exception' => isset($context['exception']) ? $context['exception'] : null + )); + } + } + + return $this->state; + } + + public function getCurlOptions() + { + return $this->curlOptions; + } + + public function startResponse(Response $response) + { + $this->state = self::STATE_TRANSFER; + $response->setEffectiveUrl((string) $this->getUrl()); + $this->response = $response; + + return $this; + } + + public function setResponse(Response $response, $queued = false) + { + $response->setEffectiveUrl((string) $this->url); + + if ($queued) { + $ed = $this->getEventDispatcher(); + $ed->addListener('request.before_send', $f = function ($e) use ($response, &$f, $ed) { + $e['request']->setResponse($response); + $ed->removeListener('request.before_send', $f); + }, -9999); + } else { + $this->response = $response; + // If a specific response body is specified, then use it instead of the response's body + if ($this->responseBody && !$this->responseBody->getCustomData('default') && !$response->isRedirect()) { + $this->getResponseBody()->write((string) $this->response->getBody()); + } else { + $this->responseBody = $this->response->getBody(); + } + $this->setState(self::STATE_COMPLETE); + } + + return $this; + } + + public function setResponseBody($body) + { + // Attempt to open a file for writing if a string was passed + if (is_string($body)) { + // @codeCoverageIgnoreStart + if (!($body = fopen($body, 'w+'))) { + throw new InvalidArgumentException('Could not open ' . $body . ' for writing'); + } + // @codeCoverageIgnoreEnd + } + + $this->responseBody = EntityBody::factory($body); + + return $this; + } + + public function getResponseBody() + { + if ($this->responseBody === null) { + $this->responseBody = EntityBody::factory()->setCustomData('default', true); + } + + return $this->responseBody; + } + + /** + * Determine if the response body is repeatable (readable + seekable) + * + * @return bool + * @deprecated Use getResponseBody()->isSeekable() + * @codeCoverageIgnore + */ + public function isResponseBodyRepeatable() + { + Version::warn(__METHOD__ . ' is deprecated. Use $request->getResponseBody()->isRepeatable()'); + return !$this->responseBody ? true : $this->responseBody->isRepeatable(); + } + + public function getCookies() + { + if ($cookie = $this->getHeader('Cookie')) { + $data = ParserRegistry::getInstance()->getParser('cookie')->parseCookie($cookie); + return $data['cookies']; + } + + return array(); + } + + public function getCookie($name) + { + $cookies = $this->getCookies(); + + return isset($cookies[$name]) ? $cookies[$name] : null; + } + + public function addCookie($name, $value) + { + if (!$this->hasHeader('Cookie')) { + $this->setHeader('Cookie', "{$name}={$value}"); + } else { + $this->getHeader('Cookie')->add("{$name}={$value}"); + } + + // Always use semicolons to separate multiple cookie headers + $this->getHeader('Cookie')->setGlue(';'); + + return $this; + } + + public function removeCookie($name) + { + if ($cookie = $this->getHeader('Cookie')) { + foreach ($cookie as $cookieValue) { + if (strpos($cookieValue, $name . '=') === 0) { + $cookie->removeValue($cookieValue); + } + } + } + + return $this; + } + + public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) + { + $this->eventDispatcher = $eventDispatcher; + $this->eventDispatcher->addListener('request.error', array(__CLASS__, 'onRequestError'), -255); + + return $this; + } + + public function getEventDispatcher() + { + if (!$this->eventDispatcher) { + $this->setEventDispatcher(new EventDispatcher()); + } + + return $this->eventDispatcher; + } + + public function dispatch($eventName, array $context = array()) + { + $context['request'] = $this; + + return $this->getEventDispatcher()->dispatch($eventName, new Event($context)); + } + + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->getEventDispatcher()->addSubscriber($subscriber); + + return $this; + } + + /** + * Get an array containing the request and response for event notifications + * + * @return array + */ + protected function getEventArray() + { + return array( + 'request' => $this, + 'response' => $this->response + ); + } + + /** + * Process a received response + * + * @param array $context Contextual information + * @throws RequestException|BadResponseException on unsuccessful responses + */ + protected function processResponse(array $context = array()) + { + if (!$this->response) { + // If no response, then processResponse shouldn't have been called + $e = new RequestException('Error completing request'); + $e->setRequest($this); + throw $e; + } + + $this->state = self::STATE_COMPLETE; + + // A request was sent, but we don't know if we'll send more or if the final response will be successful + $this->dispatch('request.sent', $this->getEventArray() + $context); + + // Some response processors will remove the response or reset the state (example: ExponentialBackoffPlugin) + if ($this->state == RequestInterface::STATE_COMPLETE) { + + // The request completed, so the HTTP transaction is complete + $this->dispatch('request.complete', $this->getEventArray()); + + // If the response is bad, allow listeners to modify it or throw exceptions. You can change the response by + // modifying the Event object in your listeners or calling setResponse() on the request + if ($this->response->isError()) { + $event = new Event($this->getEventArray()); + $this->getEventDispatcher()->dispatch('request.error', $event); + // Allow events of request.error to quietly change the response + if ($event['response'] !== $this->response) { + $this->response = $event['response']; + } + } + + // If a successful response was received, dispatch an event + if ($this->response->isSuccessful()) { + $this->dispatch('request.success', $this->getEventArray()); + } + } + } + + /** + * @deprecated Use Guzzle\Plugin\Cache\DefaultCanCacheStrategy + * @codeCoverageIgnore + */ + public function canCache() + { + Version::warn(__METHOD__ . ' is deprecated. Use Guzzle\Plugin\Cache\DefaultCanCacheStrategy.'); + if (class_exists('Guzzle\Plugin\Cache\DefaultCanCacheStrategy')) { + $canCache = new \Guzzle\Plugin\Cache\DefaultCanCacheStrategy(); + return $canCache->canCacheRequest($this); + } else { + return false; + } + } + + /** + * @deprecated Use the history plugin (not emitting a warning as this is built-into the RedirectPlugin for now) + * @codeCoverageIgnore + */ + public function setIsRedirect($isRedirect) + { + $this->isRedirect = $isRedirect; + + return $this; + } + + /** + * @deprecated Use the history plugin + * @codeCoverageIgnore + */ + public function isRedirect() + { + Version::warn(__METHOD__ . ' is deprecated. Use the HistoryPlugin to track this.'); + return $this->isRedirect; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php new file mode 100644 index 0000000..ba00a76 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactory.php @@ -0,0 +1,359 @@ +methods = array_flip(get_class_methods(__CLASS__)); + } + + public function fromMessage($message) + { + $parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($message); + + if (!$parsed) { + return false; + } + + $request = $this->fromParts($parsed['method'], $parsed['request_url'], + $parsed['headers'], $parsed['body'], $parsed['protocol'], + $parsed['version']); + + // EntityEnclosingRequest adds an "Expect: 100-Continue" header when using a raw request body for PUT or POST + // requests. This factory method should accurately reflect the message, so here we are removing the Expect + // header if one was not supplied in the message. + if (!isset($parsed['headers']['Expect']) && !isset($parsed['headers']['expect'])) { + $request->removeHeader('Expect'); + } + + return $request; + } + + public function fromParts( + $method, + array $urlParts, + $headers = null, + $body = null, + $protocol = 'HTTP', + $protocolVersion = '1.1' + ) { + return $this->create($method, Url::buildUrl($urlParts), $headers, $body) + ->setProtocolVersion($protocolVersion); + } + + public function create($method, $url, $headers = null, $body = null, array $options = array()) + { + $method = strtoupper($method); + + if ($method == 'GET' || $method == 'HEAD' || $method == 'TRACE') { + // Handle non-entity-enclosing request methods + $request = new $this->requestClass($method, $url, $headers); + if ($body) { + // The body is where the response body will be stored + $type = gettype($body); + if ($type == 'string' || $type == 'resource' || $type == 'object') { + $request->setResponseBody($body); + } + } + } else { + // Create an entity enclosing request by default + $request = new $this->entityEnclosingRequestClass($method, $url, $headers); + if ($body || $body === '0') { + // Add POST fields and files to an entity enclosing request if an array is used + if (is_array($body) || $body instanceof Collection) { + // Normalize PHP style cURL uploads with a leading '@' symbol + foreach ($body as $key => $value) { + if (is_string($value) && substr($value, 0, 1) == '@') { + $request->addPostFile($key, $value); + unset($body[$key]); + } + } + // Add the fields if they are still present and not all files + $request->addPostFields($body); + } else { + // Add a raw entity body body to the request + $request->setBody($body, (string) $request->getHeader('Content-Type')); + if ((string) $request->getHeader('Transfer-Encoding') == 'chunked') { + $request->removeHeader('Content-Length'); + } + } + } + } + + if ($options) { + $this->applyOptions($request, $options); + } + + return $request; + } + + /** + * Clone a request while changing the method. Emulates the behavior of + * {@see Guzzle\Http\Message\Request::clone}, but can change the HTTP method. + * + * @param RequestInterface $request Request to clone + * @param string $method Method to set + * + * @return RequestInterface + */ + public function cloneRequestWithMethod(RequestInterface $request, $method) + { + // Create the request with the same client if possible + if ($request->getClient()) { + $cloned = $request->getClient()->createRequest($method, $request->getUrl(), $request->getHeaders()); + } else { + $cloned = $this->create($method, $request->getUrl(), $request->getHeaders()); + } + + $cloned->getCurlOptions()->replace($request->getCurlOptions()->toArray()); + $cloned->setEventDispatcher(clone $request->getEventDispatcher()); + // Ensure that that the Content-Length header is not copied if changing to GET or HEAD + if (!($cloned instanceof EntityEnclosingRequestInterface)) { + $cloned->removeHeader('Content-Length'); + } elseif ($request instanceof EntityEnclosingRequestInterface) { + $cloned->setBody($request->getBody()); + } + $cloned->getParams()->replace($request->getParams()->toArray()); + $cloned->dispatch('request.clone', array('request' => $cloned)); + + return $cloned; + } + + public function applyOptions(RequestInterface $request, array $options = array(), $flags = self::OPTIONS_NONE) + { + // Iterate over each key value pair and attempt to apply a config using function visitors + foreach ($options as $key => $value) { + $method = "visit_{$key}"; + if (isset($this->methods[$method])) { + $this->{$method}($request, $value, $flags); + } + } + } + + protected function visit_headers(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('headers value must be an array'); + } + + if ($flags & self::OPTIONS_AS_DEFAULTS) { + // Merge headers in but do not overwrite existing values + foreach ($value as $key => $header) { + if (!$request->hasHeader($key)) { + $request->setHeader($key, $header); + } + } + } else { + $request->addHeaders($value); + } + } + + protected function visit_body(RequestInterface $request, $value, $flags) + { + if ($request instanceof EntityEnclosingRequestInterface) { + $request->setBody($value); + } else { + throw new InvalidArgumentException('Attempting to set a body on a non-entity-enclosing request'); + } + } + + protected function visit_allow_redirects(RequestInterface $request, $value, $flags) + { + if ($value === false) { + $request->getParams()->set(RedirectPlugin::DISABLE, true); + } + } + + protected function visit_auth(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('auth value must be an array'); + } + + $request->setAuth($value[0], isset($value[1]) ? $value[1] : null, isset($value[2]) ? $value[2] : 'basic'); + } + + protected function visit_query(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('query value must be an array'); + } + + if ($flags & self::OPTIONS_AS_DEFAULTS) { + // Merge query string values in but do not overwrite existing values + $query = $request->getQuery(); + $query->overwriteWith(array_diff_key($value, $query->toArray())); + } else { + $request->getQuery()->overwriteWith($value); + } + } + + protected function visit_cookies(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('cookies value must be an array'); + } + + foreach ($value as $name => $v) { + $request->addCookie($name, $v); + } + } + + protected function visit_events(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('events value must be an array'); + } + + foreach ($value as $name => $method) { + if (is_array($method)) { + $request->getEventDispatcher()->addListener($name, $method[0], $method[1]); + } else { + $request->getEventDispatcher()->addListener($name, $method); + } + } + } + + protected function visit_plugins(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('plugins value must be an array'); + } + + foreach ($value as $plugin) { + $request->addSubscriber($plugin); + } + } + + protected function visit_exceptions(RequestInterface $request, $value, $flags) + { + if ($value === false || $value === 0) { + $dispatcher = $request->getEventDispatcher(); + foreach ($dispatcher->getListeners('request.error') as $listener) { + if (is_array($listener) && $listener[0] == 'Guzzle\Http\Message\Request' && $listener[1] = 'onRequestError') { + $dispatcher->removeListener('request.error', $listener); + break; + } + } + } + } + + protected function visit_save_to(RequestInterface $request, $value, $flags) + { + $request->setResponseBody($value); + } + + protected function visit_params(RequestInterface $request, $value, $flags) + { + if (!is_array($value)) { + throw new InvalidArgumentException('params value must be an array'); + } + + $request->getParams()->overwriteWith($value); + } + + protected function visit_timeout(RequestInterface $request, $value, $flags) + { + if (defined('CURLOPT_TIMEOUT_MS')) { + $request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, $value * 1000); + } else { + $request->getCurlOptions()->set(CURLOPT_TIMEOUT, $value); + } + } + + protected function visit_connect_timeout(RequestInterface $request, $value, $flags) + { + if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { + $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, $value * 1000); + } else { + $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, $value); + } + } + + protected function visit_debug(RequestInterface $request, $value, $flags) + { + if ($value) { + $request->getCurlOptions()->set(CURLOPT_VERBOSE, true); + } + } + + protected function visit_verify(RequestInterface $request, $value, $flags) + { + $curl = $request->getCurlOptions(); + if ($value === true || is_string($value)) { + $curl[CURLOPT_SSL_VERIFYHOST] = 2; + $curl[CURLOPT_SSL_VERIFYPEER] = true; + if ($value !== true) { + $curl[CURLOPT_CAINFO] = $value; + } + } elseif ($value === false) { + unset($curl[CURLOPT_CAINFO]); + $curl[CURLOPT_SSL_VERIFYHOST] = 0; + $curl[CURLOPT_SSL_VERIFYPEER] = false; + } + } + + protected function visit_proxy(RequestInterface $request, $value, $flags) + { + $request->getCurlOptions()->set(CURLOPT_PROXY, $value, $flags); + } + + protected function visit_cert(RequestInterface $request, $value, $flags) + { + if (is_array($value)) { + $request->getCurlOptions()->set(CURLOPT_SSLCERT, $value[0]); + $request->getCurlOptions()->set(CURLOPT_SSLCERTPASSWD, $value[1]); + } else { + $request->getCurlOptions()->set(CURLOPT_SSLCERT, $value); + } + } + + protected function visit_ssl_key(RequestInterface $request, $value, $flags) + { + if (is_array($value)) { + $request->getCurlOptions()->set(CURLOPT_SSLKEY, $value[0]); + $request->getCurlOptions()->set(CURLOPT_SSLKEYPASSWD, $value[1]); + } else { + $request->getCurlOptions()->set(CURLOPT_SSLKEY, $value); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php new file mode 100644 index 0000000..6088f10 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Message/RequestFactoryInterface.php @@ -0,0 +1,105 @@ + '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', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use 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 Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 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 (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ); + + /** @var EntityBodyInterface The response body */ + protected $body; + + /** @var string The reason phrase of the response (human readable code) */ + protected $reasonPhrase; + + /** @var string The status code of the response */ + protected $statusCode; + + /** @var array Information about the request */ + protected $info = array(); + + /** @var string The effective URL that returned this response */ + protected $effectiveUrl; + + /** @var array Cacheable response codes (see RFC 2616:13.4) */ + protected static $cacheResponseCodes = array(200, 203, 206, 300, 301, 410); + + /** + * Create a new Response based on a raw response message + * + * @param string $message Response message + * + * @return self|bool Returns false on error + */ + public static function fromMessage($message) + { + $data = ParserRegistry::getInstance()->getParser('message')->parseResponse($message); + if (!$data) { + return false; + } + + $response = new static($data['code'], $data['headers'], $data['body']); + $response->setProtocol($data['protocol'], $data['version']) + ->setStatus($data['code'], $data['reason_phrase']); + + // Set the appropriate Content-Length if the one set is inaccurate (e.g. setting to X) + $contentLength = (string) $response->getHeader('Content-Length'); + $actualLength = strlen($data['body']); + if (strlen($data['body']) > 0 && $contentLength != $actualLength) { + $response->setHeader('Content-Length', $actualLength); + } + + return $response; + } + + /** + * Construct the response + * + * @param string $statusCode The response status code (e.g. 200, 404, etc) + * @param ToArrayInterface|array $headers The response headers + * @param string|resource|EntityBodyInterface $body The body of the response + * + * @throws BadResponseException if an invalid response code is given + */ + public function __construct($statusCode, $headers = null, $body = null) + { + parent::__construct(); + $this->setStatus($statusCode); + $this->body = EntityBody::factory($body !== null ? $body : ''); + + if ($headers) { + if (is_array($headers)) { + $this->setHeaders($headers); + } elseif ($headers instanceof ToArrayInterface) { + $this->setHeaders($headers->toArray()); + } else { + throw new BadResponseException('Invalid headers argument received'); + } + } + } + + /** + * @return string + */ + public function __toString() + { + return $this->getMessage(); + } + + public function serialize() + { + return json_encode(array( + 'status' => $this->statusCode, + 'body' => (string) $this->body, + 'headers' => $this->headers->toArray() + )); + } + + public function unserialize($serialize) + { + $data = json_decode($serialize, true); + $this->__construct($data['status'], $data['headers'], $data['body']); + } + + /** + * Get the response entity body + * + * @param bool $asString Set to TRUE to return a string of the body rather than a full body object + * + * @return EntityBodyInterface|string + */ + public function getBody($asString = false) + { + return $asString ? (string) $this->body : $this->body; + } + + /** + * Set the response entity body + * + * @param EntityBodyInterface|string $body Body to set + * + * @return self + */ + public function setBody($body) + { + $this->body = EntityBody::factory($body); + + return $this; + } + + /** + * Set the protocol and protocol version of the response + * + * @param string $protocol Response protocol + * @param string $version Protocol version + * + * @return self + */ + public function setProtocol($protocol, $version) + { + $this->protocol = $protocol; + $this->protocolVersion = $version; + + return $this; + } + + /** + * Get the protocol used for the response (e.g. HTTP) + * + * @return string + */ + public function getProtocol() + { + return $this->protocol; + } + + /** + * Get the HTTP protocol version + * + * @return string + */ + public function getProtocolVersion() + { + return $this->protocolVersion; + } + + /** + * Get a cURL transfer information + * + * @param string $key A single statistic to check + * + * @return array|string|null Returns all stats if no key is set, a single stat if a key is set, or null if a key + * is set and not found + * @link http://www.php.net/manual/en/function.curl-getinfo.php + */ + public function getInfo($key = null) + { + if ($key === null) { + return $this->info; + } elseif (array_key_exists($key, $this->info)) { + return $this->info[$key]; + } else { + return null; + } + } + + /** + * Set the transfer information + * + * @param array $info Array of cURL transfer stats + * + * @return self + */ + public function setInfo(array $info) + { + $this->info = $info; + + return $this; + } + + /** + * Set the response status + * + * @param int $statusCode Response status code to set + * @param string $reasonPhrase Response reason phrase + * + * @return self + * @throws BadResponseException when an invalid response code is received + */ + public function setStatus($statusCode, $reasonPhrase = '') + { + $this->statusCode = (int) $statusCode; + + if (!$reasonPhrase && isset(self::$statusTexts[$this->statusCode])) { + $this->reasonPhrase = self::$statusTexts[$this->statusCode]; + } else { + $this->reasonPhrase = $reasonPhrase; + } + + return $this; + } + + /** + * Get the response status code + * + * @return integer + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Get the entire response as a string + * + * @return string + */ + public function getMessage() + { + $message = $this->getRawHeaders(); + + // Only include the body in the message if the size is < 2MB + $size = $this->body->getSize(); + if ($size < 2097152) { + $message .= (string) $this->body; + } + + return $message; + } + + /** + * Get the the raw message headers as a string + * + * @return string + */ + public function getRawHeaders() + { + $headers = 'HTTP/1.1 ' . $this->statusCode . ' ' . $this->reasonPhrase . "\r\n"; + $lines = $this->getHeaderLines(); + if (!empty($lines)) { + $headers .= implode("\r\n", $lines) . "\r\n"; + } + + return $headers . "\r\n"; + } + + /** + * Get the response reason phrase- a human readable version of the numeric + * status code + * + * @return string + */ + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + /** + * Get the Accept-Ranges HTTP header + * + * @return string Returns what partial content range types this server supports. + */ + public function getAcceptRanges() + { + return (string) $this->getHeader('Accept-Ranges'); + } + + /** + * Calculate the age of the response + * + * @return integer + */ + public function calculateAge() + { + $age = $this->getHeader('Age'); + + if ($age === null && $this->getDate()) { + $age = time() - strtotime($this->getDate()); + } + + return $age === null ? null : (int) (string) $age; + } + + /** + * Get the Age HTTP header + * + * @return integer|null Returns the age the object has been in a proxy cache in seconds. + */ + public function getAge() + { + return (string) $this->getHeader('Age'); + } + + /** + * Get the Allow HTTP header + * + * @return string|null Returns valid actions for a specified resource. To be used for a 405 Method not allowed. + */ + public function getAllow() + { + return (string) $this->getHeader('Allow'); + } + + /** + * Check if an HTTP method is allowed by checking the Allow response header + * + * @param string $method Method to check + * + * @return bool + */ + public function isMethodAllowed($method) + { + $allow = $this->getHeader('Allow'); + if ($allow) { + foreach (explode(',', $allow) as $allowable) { + if (!strcasecmp(trim($allowable), $method)) { + return true; + } + } + } + + return false; + } + + /** + * Get the Cache-Control HTTP header + * + * @return string + */ + public function getCacheControl() + { + return (string) $this->getHeader('Cache-Control'); + } + + /** + * Get the Connection HTTP header + * + * @return string + */ + public function getConnection() + { + return (string) $this->getHeader('Connection'); + } + + /** + * Get the Content-Encoding HTTP header + * + * @return string|null + */ + public function getContentEncoding() + { + return (string) $this->getHeader('Content-Encoding'); + } + + /** + * Get the Content-Language HTTP header + * + * @return string|null Returns the language the content is in. + */ + public function getContentLanguage() + { + return (string) $this->getHeader('Content-Language'); + } + + /** + * Get the Content-Length HTTP header + * + * @return integer Returns the length of the response body in bytes + */ + public function getContentLength() + { + return (int) (string) $this->getHeader('Content-Length'); + } + + /** + * Get the Content-Location HTTP header + * + * @return string|null Returns an alternate location for the returned data (e.g /index.htm) + */ + public function getContentLocation() + { + return (string) $this->getHeader('Content-Location'); + } + + /** + * Get the Content-Disposition HTTP header + * + * @return string|null Returns the Content-Disposition header + */ + public function getContentDisposition() + { + return (string) $this->getHeader('Content-Disposition'); + } + + /** + * Get the Content-MD5 HTTP header + * + * @return string|null Returns a Base64-encoded binary MD5 sum of the content of the response. + */ + public function getContentMd5() + { + return (string) $this->getHeader('Content-MD5'); + } + + /** + * Get the Content-Range HTTP header + * + * @return string Returns where in a full body message this partial message belongs (e.g. bytes 21010-47021/47022). + */ + public function getContentRange() + { + return (string) $this->getHeader('Content-Range'); + } + + /** + * Get the Content-Type HTTP header + * + * @return string Returns the mime type of this content. + */ + public function getContentType() + { + return (string) $this->getHeader('Content-Type'); + } + + /** + * Checks if the Content-Type is of a certain type. This is useful if the + * Content-Type header contains charset information and you need to know if + * the Content-Type matches a particular type. + * + * @param string $type Content type to check against + * + * @return bool + */ + public function isContentType($type) + { + return stripos($this->getHeader('Content-Type'), $type) !== false; + } + + /** + * Get the Date HTTP header + * + * @return string|null Returns the date and time that the message was sent. + */ + public function getDate() + { + return (string) $this->getHeader('Date'); + } + + /** + * Get the ETag HTTP header + * + * @return string|null Returns an identifier for a specific version of a resource, often a Message digest. + */ + public function getEtag() + { + return (string) $this->getHeader('ETag'); + } + + /** + * Get the Expires HTTP header + * + * @return string|null Returns the date/time after which the response is considered stale. + */ + public function getExpires() + { + return (string) $this->getHeader('Expires'); + } + + /** + * Get the Last-Modified HTTP header + * + * @return string|null Returns the last modified date for the requested object, in RFC 2822 format + * (e.g. Tue, 15 Nov 1994 12:45:26 GMT) + */ + public function getLastModified() + { + return (string) $this->getHeader('Last-Modified'); + } + + /** + * Get the Location HTTP header + * + * @return string|null Used in redirection, or when a new resource has been created. + */ + public function getLocation() + { + return (string) $this->getHeader('Location'); + } + + /** + * Get the Pragma HTTP header + * + * @return Header|null Returns the implementation-specific headers that may have various effects anywhere along + * the request-response chain. + */ + public function getPragma() + { + return (string) $this->getHeader('Pragma'); + } + + /** + * Get the Proxy-Authenticate HTTP header + * + * @return string|null Authentication to access the proxy (e.g. Basic) + */ + public function getProxyAuthenticate() + { + return (string) $this->getHeader('Proxy-Authenticate'); + } + + /** + * Get the Retry-After HTTP header + * + * @return int|null If an entity is temporarily unavailable, this instructs the client to try again after a + * specified period of time. + */ + public function getRetryAfter() + { + return (string) $this->getHeader('Retry-After'); + } + + /** + * Get the Server HTTP header + * + * @return string|null A name for the server + */ + public function getServer() + { + return (string) $this->getHeader('Server'); + } + + /** + * Get the Set-Cookie HTTP header + * + * @return string|null An HTTP cookie. + */ + public function getSetCookie() + { + return (string) $this->getHeader('Set-Cookie'); + } + + /** + * Get the Trailer HTTP header + * + * @return string|null The Trailer general field value indicates that the given set of header fields is present in + * the trailer of a message encoded with chunked transfer-coding. + */ + public function getTrailer() + { + return (string) $this->getHeader('Trailer'); + } + + /** + * Get the Transfer-Encoding HTTP header + * + * @return string|null The form of encoding used to safely transfer the entity to the user + */ + public function getTransferEncoding() + { + return (string) $this->getHeader('Transfer-Encoding'); + } + + /** + * Get the Vary HTTP header + * + * @return string|null Tells downstream proxies how to match future request headers to decide whether the cached + * response can be used rather than requesting a fresh one from the origin server. + */ + public function getVary() + { + return (string) $this->getHeader('Vary'); + } + + /** + * Get the Via HTTP header + * + * @return string|null Informs the client of proxies through which the response was sent. + */ + public function getVia() + { + return (string) $this->getHeader('Via'); + } + + /** + * Get the Warning HTTP header + * + * @return string|null A general warning about possible problems with the entity body + */ + public function getWarning() + { + return (string) $this->getHeader('Warning'); + } + + /** + * Get the WWW-Authenticate HTTP header + * + * @return string|null Indicates the authentication scheme that should be used to access the requested entity + */ + public function getWwwAuthenticate() + { + return (string) $this->getHeader('WWW-Authenticate'); + } + + /** + * Checks if HTTP Status code is a Client Error (4xx) + * + * @return bool + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Checks if HTTP Status code is Server OR Client Error (4xx or 5xx) + * + * @return boolean + */ + public function isError() + { + return $this->isClientError() || $this->isServerError(); + } + + /** + * Checks if HTTP Status code is Information (1xx) + * + * @return bool + */ + public function isInformational() + { + return $this->statusCode < 200; + } + + /** + * Checks if HTTP Status code is a Redirect (3xx) + * + * @return bool + */ + public function isRedirect() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Checks if HTTP Status code is Server Error (5xx) + * + * @return bool + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Checks if HTTP Status code is Successful (2xx | 304) + * + * @return bool + */ + public function isSuccessful() + { + return ($this->statusCode >= 200 && $this->statusCode < 300) || $this->statusCode == 304; + } + + /** + * Check if the response can be cached based on the response headers + * + * @return bool Returns TRUE if the response can be cached or false if not + */ + public function canCache() + { + // Check if the response is cacheable based on the code + if (!in_array((int) $this->getStatusCode(), self::$cacheResponseCodes)) { + return false; + } + + // Make sure a valid body was returned and can be cached + if ((!$this->getBody()->isReadable() || !$this->getBody()->isSeekable()) + && ($this->getContentLength() > 0 || $this->getTransferEncoding() == 'chunked')) { + return false; + } + + // Never cache no-store resources (this is a private cache, so private + // can be cached) + if ($this->getHeader('Cache-Control') && $this->getHeader('Cache-Control')->hasDirective('no-store')) { + return false; + } + + return $this->isFresh() || $this->getFreshness() === null || $this->canValidate(); + } + + /** + * Gets the number of seconds from the current time in which this response is still considered fresh + * + * @return int|null Returns the number of seconds + */ + public function getMaxAge() + { + if ($header = $this->getHeader('Cache-Control')) { + // s-max-age, then max-age, then Expires + if ($age = $header->getDirective('s-maxage')) { + return $age; + } + if ($age = $header->getDirective('max-age')) { + return $age; + } + } + + if ($this->getHeader('Expires')) { + return strtotime($this->getExpires()) - time(); + } + + return null; + } + + /** + * Check if the response is considered fresh. + * + * A response is considered fresh when its age is less than or equal to the freshness lifetime (maximum age) of the + * response. + * + * @return bool|null + */ + public function isFresh() + { + $fresh = $this->getFreshness(); + + return $fresh === null ? null : $fresh >= 0; + } + + /** + * Check if the response can be validated against the origin server using a conditional GET request. + * + * @return bool + */ + public function canValidate() + { + return $this->getEtag() || $this->getLastModified(); + } + + /** + * Get the freshness of the response by returning the difference of the maximum lifetime of the response and the + * age of the response (max-age - age). + * + * Freshness values less than 0 mean that the response is no longer fresh and is ABS(freshness) seconds expired. + * Freshness values of greater than zero is the number of seconds until the response is no longer fresh. A NULL + * result means that no freshness information is available. + * + * @return int + */ + public function getFreshness() + { + $maxAge = $this->getMaxAge(); + $age = $this->calculateAge(); + + return $maxAge && $age ? ($maxAge - $age) : null; + } + + /** + * Parse the JSON response body and return an array + * + * @return array|string|int|bool|float + * @throws RuntimeException if the response body is not in JSON format + */ + public function json() + { + $data = json_decode((string) $this->body, true); + if (JSON_ERROR_NONE !== json_last_error()) { + throw new RuntimeException('Unable to parse response body into JSON: ' . json_last_error()); + } + + return $data === null ? array() : $data; + } + + /** + * Parse the XML response body and return a \SimpleXMLElement. + * + * In order to prevent XXE attacks, this method disables loading external + * entities. If you rely on external entities, then you must parse the + * XML response manually by accessing the response body directly. + * + * @return \SimpleXMLElement + * @throws RuntimeException if the response body is not in XML format + * @link http://websec.io/2012/08/27/Preventing-XXE-in-PHP.html + */ + public function xml() + { + $errorMessage = null; + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + libxml_clear_errors(); + + try { + $xml = new \SimpleXMLElement((string) $this->body ?: '', LIBXML_NONET); + if ($error = libxml_get_last_error()) { + $errorMessage = $error->message; + } + } catch (\Exception $e) { + $errorMessage = $e->getMessage(); + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + if ($errorMessage) { + throw new RuntimeException('Unable to parse response body into XML: ' . $errorMessage); + } + + return $xml; + } + + /** + * Get the redirect count of this response + * + * @return int + */ + public function getRedirectCount() + { + return (int) $this->params->get(RedirectPlugin::REDIRECT_COUNT); + } + + /** + * Set the effective URL that resulted in this response (e.g. the last redirect URL) + * + * @param string $url The effective URL + * + * @return self + */ + public function setEffectiveUrl($url) + { + $this->effectiveUrl = $url; + + return $this; + } + + /** + * Get the effective URL that resulted in this response (e.g. the last redirect URL) + * + * @return string + */ + public function getEffectiveUrl() + { + return $this->effectiveUrl; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getPreviousResponse() + { + Version::warn(__METHOD__ . ' is deprecated. Use the HistoryPlugin.'); + return null; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function setRequest($request) + { + Version::warn(__METHOD__ . ' is deprecated'); + return $this; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function getRequest() + { + Version::warn(__METHOD__ . ' is deprecated'); + return null; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php new file mode 100644 index 0000000..d71586a --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Mimetypes.php @@ -0,0 +1,962 @@ + 'text/vnd.in3d.3dml', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + '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', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + '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', + 'apk' => 'application/vnd.android.package-archive', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'asa' => 'text/plain', + 'asax' => 'application/octet-stream', + 'asc' => 'application/pgp-signature', + 'ascx' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'ashx' => 'text/plain', + 'asm' => 'text/x-asm', + 'asmx' => 'text/plain', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asp' => 'text/plain', + 'aspx' => 'text/plain', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'axd' => 'text/plain', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + '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', + 'cab' => 'application/vnd.ms-cab-compressed', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + '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', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfc' => 'application/x-coldfusion', + 'cfm' => 'application/x-coldfusion', + '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', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + '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', + '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', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'cs' => 'text/plain', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + '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', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.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', + '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', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + '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', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + '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/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + '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', + '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', + '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', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + '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', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxt' => 'application/vnd.geonext', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hta' => 'application/octet-stream', + 'htc' => 'text/html', + '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', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/octet-stream', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + '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', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + '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', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/mp4', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + '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', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + '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', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + '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', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + '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', + 'nsf' => 'application/vnd.lotus-notes', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + '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', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-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', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + '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', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + '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/vnd.palm', + 'pdf' => 'application/pdf', + '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-encrypted', + 'php' => 'text/x-php', + 'phps' => 'application/x-httpd-phps', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + '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.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + '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/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + '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-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rb' => 'text/plain', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'resx' => 'text/xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 'saf' => 'application/vnd.yamaha.smaf-audio', + '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', + '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', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + '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', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sig' => 'application/pgp-signature', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + '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', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + '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', + 'src' => 'application/x-wais-source', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + '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' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'image/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', + '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', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + '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', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + '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', + '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', + 'uvx' => 'application/vnd.dece.unspecified', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + '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', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + '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' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-ms-wmz', + 'woff' => 'application/x-font-woff', + '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', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'application/vnd.hzn-3d-crossword', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + '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', + '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', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + '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', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + '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', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml' + ); + + /** + * Get a singleton instance of the class + * + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Get a mimetype value from a file extension + * + * @param string $extension File extension + * + * @return string|null + * + */ + public function fromExtension($extension) + { + $extension = strtolower($extension); + + return isset($this->mimetypes[$extension]) ? $this->mimetypes[$extension] : null; + } + + /** + * Get a mimetype from a filename + * + * @param string $filename Filename to generate a mimetype from + * + * @return string|null + */ + public function fromFilename($filename) + { + return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php new file mode 100644 index 0000000..4b4e49d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/CommaAggregator.php @@ -0,0 +1,20 @@ +isUrlEncoding()) { + return array($query->encodeValue($key) => implode(',', array_map(array($query, 'encodeValue'), $value))); + } else { + return array($key => implode(',', $value)); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php new file mode 100644 index 0000000..1bf1730 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/DuplicateAggregator.php @@ -0,0 +1,22 @@ +isUrlEncoding()) { + return array($query->encodeValue($key) => array_map(array($query, 'encodeValue'), $value)); + } else { + return array($key => $value); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php new file mode 100644 index 0000000..133ea2b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/PhpAggregator.php @@ -0,0 +1,27 @@ + $v) { + $k = "{$key}[{$k}]"; + if (is_array($v)) { + $ret = array_merge($ret, self::aggregate($k, $v, $query)); + } else { + $ret[$query->encodeValue($k)] = $query->encodeValue($v); + } + } + + return $ret; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php new file mode 100644 index 0000000..72bee62 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/QueryAggregator/QueryAggregatorInterface.php @@ -0,0 +1,22 @@ +add($key, $value); + $foundDuplicates = true; + } elseif ($paramIsPhpStyleArray) { + $q[$key] = array($value); + } else { + $q[$key] = $value; + } + } else { + // Uses false by default to represent keys with no trailing "=" sign. + $q->add($key, false); + } + } + + // Use the duplicate aggregator if duplicates were found and not using PHP style arrays + if ($foundDuplicates && !$foundPhpStyle) { + $q->setAggregator(new DuplicateAggregator()); + } + + return $q; + } + + /** + * Convert the query string parameters to a query string string + * + * @return string + * @throws RuntimeException + */ + public function __toString() + { + if (!$this->data) { + return ''; + } + + $queryList = array(); + foreach ($this->prepareData($this->data) as $name => $value) { + $queryList[] = $this->convertKvp($name, $value); + } + + return implode($this->fieldSeparator, $queryList); + } + + /** + * Get the query string field separator + * + * @return string + */ + public function getFieldSeparator() + { + return $this->fieldSeparator; + } + + /** + * Get the query string value separator + * + * @return string + */ + public function getValueSeparator() + { + return $this->valueSeparator; + } + + /** + * Returns the type of URL encoding used by the query string + * + * One of: false, "RFC 3986", or "application/x-www-form-urlencoded" + * + * @return bool|string + */ + public function getUrlEncoding() + { + return $this->urlEncode; + } + + /** + * Returns true or false if using URL encoding + * + * @return bool + */ + public function isUrlEncoding() + { + return $this->urlEncode !== false; + } + + /** + * Provide a function for combining multi-valued query string parameters into a single or multiple fields + * + * @param null|QueryAggregatorInterface $aggregator Pass in a QueryAggregatorInterface object to handle converting + * deeply nested query string variables into a flattened array. + * Pass null to use the default PHP style aggregator. For legacy + * reasons, this function accepts a callable that must accepts a + * $key, $value, and query object. + * @return self + * @see \Guzzle\Http\QueryString::aggregateUsingComma() + */ + public function setAggregator(QueryAggregatorInterface $aggregator = null) + { + // Use the default aggregator if none was set + if (!$aggregator) { + if (!self::$defaultAggregator) { + self::$defaultAggregator = new PhpAggregator(); + } + $aggregator = self::$defaultAggregator; + } + + $this->aggregator = $aggregator; + + return $this; + } + + /** + * Set whether or not field names and values should be rawurlencoded + * + * @param bool|string $encode Set to TRUE to use RFC 3986 encoding (rawurlencode), false to disable encoding, or + * form_urlencoding to use application/x-www-form-urlencoded encoding (urlencode) + * @return self + */ + public function useUrlEncoding($encode) + { + $this->urlEncode = ($encode === true) ? self::RFC_3986 : $encode; + + return $this; + } + + /** + * Set the query string separator + * + * @param string $separator The query string separator that will separate fields + * + * @return self + */ + public function setFieldSeparator($separator) + { + $this->fieldSeparator = $separator; + + return $this; + } + + /** + * Set the query string value separator + * + * @param string $separator The query string separator that will separate values from fields + * + * @return self + */ + public function setValueSeparator($separator) + { + $this->valueSeparator = $separator; + + return $this; + } + + /** + * Returns an array of url encoded field names and values + * + * @return array + */ + public function urlEncode() + { + return $this->prepareData($this->data); + } + + /** + * URL encodes a value based on the url encoding type of the query string object + * + * @param string $value Value to encode + * + * @return string + */ + public function encodeValue($value) + { + if ($this->urlEncode == self::RFC_3986) { + return rawurlencode($value); + } elseif ($this->urlEncode == self::FORM_URLENCODED) { + return urlencode($value); + } else { + return (string) $value; + } + } + + /** + * Url encode parameter data and convert nested query strings into a flattened hash. + * + * @param array $data The data to encode + * + * @return array Returns an array of encoded values and keys + */ + protected function prepareData(array $data) + { + // If no aggregator is present then set the default + if (!$this->aggregator) { + $this->setAggregator(null); + } + + $temp = array(); + foreach ($data as $key => $value) { + if ($value === false || $value === null) { + // False and null will not include the "=". Use an empty string to include the "=". + $temp[$this->encodeValue($key)] = $value; + } elseif (is_array($value)) { + $temp = array_merge($temp, $this->aggregator->aggregate($key, $value, $this)); + } else { + $temp[$this->encodeValue($key)] = $this->encodeValue($value); + } + } + + return $temp; + } + + /** + * Converts a key value pair that can contain strings, nulls, false, or arrays + * into a single string. + * + * @param string $name Name of the field + * @param mixed $value Value of the field + * @return string + */ + private function convertKvp($name, $value) + { + if ($value === self::BLANK || $value === null || $value === false) { + return $name; + } elseif (!is_array($value)) { + return $name . $this->valueSeparator . $value; + } + + $result = ''; + foreach ($value as $v) { + $result .= $this->convertKvp($name, $v) . $this->fieldSeparator; + } + + return rtrim($result, $this->fieldSeparator); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php new file mode 100644 index 0000000..ef28273 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/ReadLimitEntityBody.php @@ -0,0 +1,122 @@ +setLimit($limit)->setOffset($offset); + } + + /** + * Returns only a subset of the decorated entity body when cast as a string + * {@inheritdoc} + */ + public function __toString() + { + if (!$this->body->isReadable() || + (!$this->body->isSeekable() && $this->body->isConsumed()) + ) { + return ''; + } + + $originalPos = $this->body->ftell(); + $this->body->seek($this->offset); + $data = ''; + while (!$this->feof()) { + $data .= $this->read(1048576); + } + $this->body->seek($originalPos); + + return (string) $data ?: ''; + } + + public function isConsumed() + { + return $this->body->isConsumed() || + ($this->body->ftell() >= $this->offset + $this->limit); + } + + /** + * Returns the Content-Length of the limited subset of data + * {@inheritdoc} + */ + public function getContentLength() + { + $length = $this->body->getContentLength(); + + return $length === false + ? $this->limit + : min($this->limit, min($length, $this->offset + $this->limit) - $this->offset); + } + + /** + * Allow for a bounded seek on the read limited entity body + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + return $whence === SEEK_SET + ? $this->body->seek(max($this->offset, min($this->offset + $this->limit, $offset))) + : false; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @return self + */ + public function setOffset($offset) + { + $this->body->seek($offset); + $this->offset = $offset; + + return $this; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the stream + * + * @param int $limit Total number of bytes to allow to be read from the stream + * + * @return self + */ + public function setLimit($limit) + { + $this->limit = $limit; + + return $this; + } + + public function read($length) + { + // Check if the current position is less than the total allowed bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->body->ftell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte limit is not exceeded + return $this->body->read(min($remaining, $length)); + } else { + return false; + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php new file mode 100644 index 0000000..1a824b8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/RedirectPlugin.php @@ -0,0 +1,250 @@ + array('onRequestSent', 100), + 'request.clone' => 'cleanupRequest', + 'request.before_send' => 'cleanupRequest' + ); + } + + /** + * Clean up the parameters of a request when it is cloned + * + * @param Event $event Event emitted + */ + public function cleanupRequest(Event $event) + { + $params = $event['request']->getParams(); + unset($params[self::REDIRECT_COUNT]); + unset($params[self::PARENT_REQUEST]); + } + + /** + * Called when a request receives a redirect response + * + * @param Event $event Event emitted + */ + public function onRequestSent(Event $event) + { + $response = $event['response']; + $request = $event['request']; + + // Only act on redirect requests with Location headers + if (!$response || $request->getParams()->get(self::DISABLE)) { + return; + } + + // Trace the original request based on parameter history + $original = $this->getOriginalRequest($request); + + // Terminating condition to set the effective response on the original request + if (!$response->isRedirect() || !$response->hasHeader('Location')) { + if ($request !== $original) { + // This is a terminating redirect response, so set it on the original request + $response->getParams()->set(self::REDIRECT_COUNT, $original->getParams()->get(self::REDIRECT_COUNT)); + $original->setResponse($response); + $response->setEffectiveUrl($request->getUrl()); + } + return; + } + + $this->sendRedirectRequest($original, $request, $response); + } + + /** + * Get the original request that initiated a series of redirects + * + * @param RequestInterface $request Request to get the original request from + * + * @return RequestInterface + */ + protected function getOriginalRequest(RequestInterface $request) + { + $original = $request; + // The number of redirects is held on the original request, so determine which request that is + while ($parent = $original->getParams()->get(self::PARENT_REQUEST)) { + $original = $parent; + } + + return $original; + } + + /** + * Create a redirect request for a specific request object + * + * Takes into account strict RFC compliant redirection (e.g. redirect POST with POST) vs doing what most clients do + * (e.g. redirect POST with GET). + * + * @param RequestInterface $request Request being redirected + * @param RequestInterface $original Original request + * @param int $statusCode Status code of the redirect + * @param string $location Location header of the redirect + * + * @return RequestInterface Returns a new redirect request + * @throws CouldNotRewindStreamException If the body needs to be rewound but cannot + */ + protected function createRedirectRequest( + RequestInterface $request, + $statusCode, + $location, + RequestInterface $original + ) { + $redirectRequest = null; + $strict = $original->getParams()->get(self::STRICT_REDIRECTS); + + // Switch method to GET for 303 redirects. 301 and 302 redirects also switch to GET unless we are forcing RFC + // compliance to emulate what most browsers do. NOTE: IE only switches methods on 301/302 when coming from a POST. + if ($request instanceof EntityEnclosingRequestInterface && ($statusCode == 303 || (!$strict && $statusCode <= 302))) { + $redirectRequest = RequestFactory::getInstance()->cloneRequestWithMethod($request, 'GET'); + } else { + $redirectRequest = clone $request; + } + + $redirectRequest->setIsRedirect(true); + // Always use the same response body when redirecting + $redirectRequest->setResponseBody($request->getResponseBody()); + + $location = Url::factory($location); + // If the location is not absolute, then combine it with the original URL + if (!$location->isAbsolute()) { + $originalUrl = $redirectRequest->getUrl(true); + // Remove query string parameters and just take what is present on the redirect Location header + $originalUrl->getQuery()->clear(); + $location = $originalUrl->combine((string) $location, true); + } + + $redirectRequest->setUrl($location); + + // Add the parent request to the request before it sends (make sure it's before the onRequestClone event too) + $redirectRequest->getEventDispatcher()->addListener( + 'request.before_send', + $func = function ($e) use (&$func, $request, $redirectRequest) { + $redirectRequest->getEventDispatcher()->removeListener('request.before_send', $func); + $e['request']->getParams()->set(RedirectPlugin::PARENT_REQUEST, $request); + } + ); + + // Rewind the entity body of the request if needed + if ($redirectRequest instanceof EntityEnclosingRequestInterface && $redirectRequest->getBody()) { + $body = $redirectRequest->getBody(); + // Only rewind the body if some of it has been read already, and throw an exception if the rewind fails + if ($body->ftell() && !$body->rewind()) { + throw new CouldNotRewindStreamException( + 'Unable to rewind the non-seekable entity body of the request after redirecting. cURL probably ' + . 'sent part of body before the redirect occurred. Try adding acustom rewind function using on the ' + . 'entity body of the request using setRewindFunction().' + ); + } + } + + return $redirectRequest; + } + + /** + * Prepare the request for redirection and enforce the maximum number of allowed redirects per client + * + * @param RequestInterface $original Original request + * @param RequestInterface $request Request to prepare and validate + * @param Response $response The current response + * + * @return RequestInterface + */ + protected function prepareRedirection(RequestInterface $original, RequestInterface $request, Response $response) + { + $params = $original->getParams(); + // This is a new redirect, so increment the redirect counter + $current = $params[self::REDIRECT_COUNT] + 1; + $params[self::REDIRECT_COUNT] = $current; + // Use a provided maximum value or default to a max redirect count of 5 + $max = isset($params[self::MAX_REDIRECTS]) ? $params[self::MAX_REDIRECTS] : $this->defaultMaxRedirects; + + // Throw an exception if the redirect count is exceeded + if ($current > $max) { + $this->throwTooManyRedirectsException($original, $max); + return false; + } else { + // Create a redirect request based on the redirect rules set on the request + return $this->createRedirectRequest( + $request, + $response->getStatusCode(), + trim($response->getLocation()), + $original + ); + } + } + + /** + * Send a redirect request and handle any errors + * + * @param RequestInterface $original The originating request + * @param RequestInterface $request The current request being redirected + * @param Response $response The response of the current request + * + * @throws BadResponseException|\Exception + */ + protected function sendRedirectRequest(RequestInterface $original, RequestInterface $request, Response $response) + { + // Validate and create a redirect request based on the original request and current response + if ($redirectRequest = $this->prepareRedirection($original, $request, $response)) { + try { + $redirectRequest->send(); + } catch (BadResponseException $e) { + $e->getResponse(); + if (!$e->getResponse()) { + throw $e; + } + } + } + } + + /** + * Throw a too many redirects exception for a request + * + * @param RequestInterface $original Request + * @param int $max Max allowed redirects + * + * @throws TooManyRedirectsException when too many redirects have been issued + */ + protected function throwTooManyRedirectsException(RequestInterface $original, $max) + { + $original->getEventDispatcher()->addListener( + 'request.complete', + $func = function ($e) use (&$func, $original, $max) { + $original->getEventDispatcher()->removeListener('request.complete', $func); + $str = "{$max} redirects were issued for this request:\n" . $e['request']->getRawHeaders(); + throw new TooManyRedirectsException($str); + } + ); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem new file mode 100644 index 0000000..18ce703 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Resources/cacert.pem @@ -0,0 +1,3870 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla downloaded on: Wed Aug 13 21:49:32 2014 +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## http://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl verison 1.22. +## SHA1: bf2c15b3019e696660321d2227d942936dc50aa7 +## + + +GTE CyberTrust Global Root +========================== +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg +Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG +A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz +MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL +Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 +IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u +sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql +HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID +AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW +M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF +NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +Thawte Server CA +================ +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE +AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j +b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV +BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u +c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG +A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0 +ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl +/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7 +1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J +GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ +GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +Thawte Premium Server CA +======================== +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs +dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE +AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl +ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT +AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU +VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2 +aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ +cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2 +aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh +Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/ +qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm +SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf +8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t +UCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +Equifax Secure CA +================= +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE +ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT +B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR +fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW +8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG +A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE +CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG +A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS +spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB +Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 +zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB +BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 +70+sB3c4 +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA +TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah +WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf +Tqj/ZA1k +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G2 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT +MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO +FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71 +lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB +MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT +1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD +Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9 +-----END CERTIFICATE----- + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +GlobalSign Root CA - R2 +======================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 +ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp +s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN +S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL +TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C +ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i +YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN +BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp +9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu +01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 +9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +ValiCert Class 1 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy +MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi +GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm +DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG +lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX +icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP +Orf1LXLI +-----END CERTIFICATE----- + +ValiCert Class 2 VA +=================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC +CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf +ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ +SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV +UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8 +W9ViH0Pd +-----END CERTIFICATE----- + +RSA Root Certificate 1 +====================== +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp +b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh +bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw +MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0 +d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg +UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0 +LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td +3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H +BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs +3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF +V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r +on+jjBXu +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 +EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc +cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw +EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj +055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f +j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 +xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa +t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS +tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM +8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW +Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX +Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt +mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd +RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG +UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +Entrust.net Secure Server CA +============================ +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV +BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg +cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl +ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG +A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi +eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p +dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ +aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5 +gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw +ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw +CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l +dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw +NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow +HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA +BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN +Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9 +n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Equifax Secure Global eBusiness CA +================================== +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp +bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx +HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds +b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV +PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN +qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn +hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs +MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN +I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY +NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 1 +============================= +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB +LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE +ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz +IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ +1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a +IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk +MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW +Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF +AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5 +lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+ +KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +AddTrust Low-Value Services Root +================================ +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU +cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw +CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO +ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 +54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr +oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 +Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui +GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w +HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT +RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw +HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt +ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph +iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr +mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj +ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +AddTrust External Root +====================== +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD +VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw +NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU +cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg +Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 ++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw +Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo +aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy +2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 +7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL +VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk +VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl +j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 +e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u +G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +AddTrust Public Services Root +============================= +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU +cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ +BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l +dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu +nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i +d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG +Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw +HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G +A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G +A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 +JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL ++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 +Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H +EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +AddTrust Qualified Certificates Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU +cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx +CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ +IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx +64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 +KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o +L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR +wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU +MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE +BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y +azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG +GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze +RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB +iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +RSA Security 2048 v3 +==================== +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK +ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy +MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb +BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 +Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb +WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH +KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP ++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E +FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY +v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj +0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj +VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 +nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA +pKnXwiJPZ9d37CAFYd4= +-----END CERTIFICATE----- + +GeoTrust Global CA +================== +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw +MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo +BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet +8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc +T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU +vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk +DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q +zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 +d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 +mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p +XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm +Mw== +-----END CERTIFICATE----- + +GeoTrust Global CA 2 +==================== +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw +MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ +NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k +LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA +Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b +HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH +K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 +srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh +ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL +OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC +x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF +H4z1Ir+rzoPz4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +GeoTrust Universal CA +===================== +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 +MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu +Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t +JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e +RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs +7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d +8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V +qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga +Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB +Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu +KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 +ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 +XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 +qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL +oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK +xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF +KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 +DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK +xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU +p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI +P/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +GeoTrust Universal CA 2 +======================= +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 +MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg +SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 +DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 +j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q +JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a +QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 +WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP +20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn +ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC +SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG +8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 ++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ +4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ +mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq +A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg +Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP +pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d +FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp +gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm +X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +America Online Root Certification Authority 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG +v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z +DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh +sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP +8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z +o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf +GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF +VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft +3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g +Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds +sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 +-----END CERTIFICATE----- + +America Online Root Certification Authority 2 +============================================= +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT +QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG +A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg +T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en +fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8 +f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO +qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN +RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0 +gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn +6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid +FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6 +Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj +B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op +aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY +T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p ++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg +JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy +zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO +ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh +1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf +GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff +Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP +cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk= +-----END CERTIFICATE----- + +Visa eCommerce Root +=================== +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG +EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug +QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 +WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm +VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL +F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b +RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 +TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI +/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs +GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc +CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW +YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz +zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu +YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +Certum Root CA +============== +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK +ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla +Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u +by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x +wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL +kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ +89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K +Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P +NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ +GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg +GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ +0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS +qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +Comodo Secure Services root +=========================== +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw +MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu +Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi +BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP +9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc +rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC +oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V +p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E +FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj +YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm +aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm +4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL +DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw +pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H +RR3B7Hzs/Sk= +-----END CERTIFICATE----- + +Comodo Trusted Services root +============================ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw +MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h +bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw +IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 +3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y +/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 +juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS +ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud +DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp +ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl +cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw +uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA +BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l +R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O +9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +QuoVadis Root CA +================ +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE +ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz +MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp +cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD +EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk +J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL +F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL +YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen +AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w +PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y +ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7 +MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj +YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs +ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW +Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu +BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw +FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6 +tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo +fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul +LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x +gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi +5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi +5nrQNiOKSnQ2+Q== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +Sonera Class 2 Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG +U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw +NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh +IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3 +/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT +dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG +f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P +tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH +nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT +XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt +0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI +cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph +Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx +EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH +llpwrN9M +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA +============================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE +ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w +HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh +bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt +vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P +jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca +C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth +vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 +22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV +HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v +dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN +BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR +EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw +MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y +nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR +iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +-----END CERTIFICATE----- + +TDC Internet Root CA +==================== +-----BEGIN CERTIFICATE----- +MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE +ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx +NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu +ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j +xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL +znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc +5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6 +otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI +AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM +VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM +MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC +AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe +UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G +CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m +gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ +2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb +O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU +Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l +-----END CERTIFICATE----- + +UTN DATACorp SGC Root CA +======================== +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ +BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa +MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w +HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy +dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys +raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo +wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA +9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv +33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud +DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 +BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD +LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 +DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 +I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx +EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP +DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +UTN USERFirst Hardware Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd +BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx +OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 +eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz +ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI +wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd +tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 +i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf +Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw +gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF +lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF +UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF +BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW +XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 +lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn +iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 +nfhmqA== +-----END CERTIFICATE----- + +Camerfirma Chambers of Commerce Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx +NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp +cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn +MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC +AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU +xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH +NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW +DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV +d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud +EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v +cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P +AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh +bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD +VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi +fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD +L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN +UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n +ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 +erfutGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +Camerfirma Global Chambersign Root +================================== +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx +NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt +YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg +MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw +ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J +1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O +by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl +6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c +8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ +BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j +aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B +Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj +aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y +ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA +PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y +gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ +PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 +IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes +t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +NetLock Notary (Class A) Root +============================= +-----BEGIN CERTIFICATE----- +MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI +EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j +ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX +DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH +EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD +VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz +cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM +D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ +z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC +/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 +tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 +4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG +A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC +Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv +bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu +IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn +LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 +ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz +IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh +IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu +b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh +bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg +Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp +bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 +ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP +ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB +CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr +KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM +8CgHrTwXZoi1/baI +-----END CERTIFICATE----- + +NetLock Business (Class B) Root +=============================== +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg +VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD +VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv +bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg +VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S +o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr +1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV +HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ +RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh +dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0 +ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv +c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg +YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz +Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA +bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl +IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2 +YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj +cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM +43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR +stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +NetLock Express (Class C) Root +============================== +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT +CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV +BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD +KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ +BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j +ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z +W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63 +euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw +DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN +RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn +YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB +IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i +aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0 +ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y +emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k +IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ +UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg +YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2 +xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW +gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj +YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH +AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw +Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg +U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 +LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh +cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT +dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC +AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh +3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm +vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk +fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 +fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ +EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl +1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ +lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro +g14= +-----END CERTIFICATE----- + +Taiwan GRCA +=========== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG +EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X +DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv +dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN +w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 +BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O +1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO +htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov +J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 +Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t +B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB +O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 +lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV +HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 +09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj +Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 +Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU +D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz +DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk +Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk +7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ +CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy ++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS +-----END CERTIFICATE----- + +Swisscom Root CA 1 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 +MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM +MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF +NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe +AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC +b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn +7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN +cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp +WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 +haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY +MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j +BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 +MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn +jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ +MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H +VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl +vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl +OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 +1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq +nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy +x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW +NY6E0F/6MBr1mmz0DlP5OlvRHA== +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +Certplus Class 2 Primary CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE +BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN +OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy +dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR +5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ +Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO +YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e +e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME +CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ +YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t +L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD +P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R +TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ +7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW +//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +DST Root CA X3 +============== +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK +ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X +DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 +cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT +rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 +UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy +xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d +utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ +MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug +dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE +GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw +RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS +fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +DST ACES CA X6 +============== +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT +MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha +MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE +CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI +DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa +pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow +GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy +MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu +Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy +dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU +CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 +5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t +Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq +nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs +vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 +oKfN5XozNmr6mis= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 1 +============================================== +-----BEGIN CERTIFICATE----- +MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP +MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0 +acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx +MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg +U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB +TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC +aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX +yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i +Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ +8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4 +W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME +BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46 +sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE +q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy +B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY +nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2 +============================================== +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN +MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr +dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G +A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls +acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe +LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI +x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g +QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr +5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB +AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt +Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 +Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ +hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P +9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 +UrbnBEI= +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ +cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN +b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 +nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge +RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt +tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI +hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K +Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN +NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa +Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG +1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +thawte Primary Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 +MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg +SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv +KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT +FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs +oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ +1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc +q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K +aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p +afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF +AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE +uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 +jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH +z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G5 +============================================================ +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln +biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh +dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz +j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD +Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ +Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r +fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv +Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG +SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ +X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE +KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC +Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE +ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +Network Solutions Certificate Authority +======================================= +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr +IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx +MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx +jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT +aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT +crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc +/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB +AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv +bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q +4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ +GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD +ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +WellsSecure Public Root Certificate Authority +============================================= +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM +F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw +NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN +MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl +bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD +VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 +iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 +i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 +bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB +K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB +AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu +cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm +lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB +i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww +GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI +K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 +bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj +qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es +E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ +tylv2G0xffX8oRAHh84vWdw+WNs= +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +IGC/A +===== +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD +VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE +Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy +MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI +EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT +STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 +TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW +So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy +HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd +frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ +tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB +egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC +iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK +q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q +MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI +lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF +0mBWWg== +-----END CERTIFICATE----- + +Security Communication EV RootCA1 +================================= +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE +BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl +Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO +/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX +WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z +ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 +bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK +9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm +iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG +Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW +mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW +T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GA CA +=============================== +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE +BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG +A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH +bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD +VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw +IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 +IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 +Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg +Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD +d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ +/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R +LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm +MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 ++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY +okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE +BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL +EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 +MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz +dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT +GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG +d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N +oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc +QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ +PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb +MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG +IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD +VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 +LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A +dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn +AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA +4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg +AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA +egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 +Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO +PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv +c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h +cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw +IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT +WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV +MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER +MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp +Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal +HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT +nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE +aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a +86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK +yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB +S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +AC Ra\xC3\xADz Certic\xC3\xA1mara S.A. +====================================== +-----BEGIN CERTIFICATE----- +MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT +AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg +LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w +HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+ +U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh +IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN +yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU +2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3 +4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP +2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm +8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf +HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa +Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK +5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b +czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g +ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF +BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug +cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf +AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX +EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v +/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3 +MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4 +3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk +eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f +/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h +RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU +Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 2 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw +MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw +IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2 +xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ +Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u +SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G +dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ +KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj +TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP +JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk +vQ== +-----END CERTIFICATE----- + +TC TrustCenter Class 3 CA II +============================ +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy +IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw +MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1 +c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE +AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W +yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo +6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ +uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk +2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB +7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90 +Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU +cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i +SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE +O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8 +yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9 +IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal +092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc +5A== +-----END CERTIFICATE----- + +TC TrustCenter Universal CA I +============================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC +REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy +IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN +MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg +VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw +JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC +qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv +xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw +ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O +gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j +BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG +1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy +vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3 +ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT +ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a +7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY +-----END CERTIFICATE----- + +Deutsche Telekom Root CA 2 +========================== +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT +RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG +A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 +MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G +A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS +b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 +bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI +KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY +AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK +Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV +jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV +HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr +E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy +zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 +rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G +dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +ComSign Secured CA +================== +-----BEGIN CERTIFICATE----- +MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE +AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w +NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD +QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs +49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH +7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB +kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1 +9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw +AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t +U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA +j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC +AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a +BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp +FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP +51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz +OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== +-----END CERTIFICATE----- + +Cybertrust Global Root +====================== +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li +ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 +MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD +ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA ++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW +0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL +AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin +89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT +8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 +MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G +A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO +lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi +5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 +hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T +X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 +============================================================================================================================= +-----BEGIN CERTIFICATE----- +MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH +DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q +aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry +b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV +BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg +S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4 +MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl +IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF +n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl +IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft +dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl +cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO +Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1 +xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR +6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL +hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd +BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4 +N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT +y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh +LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M +dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI= +-----END CERTIFICATE----- + +Buypass Class 2 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2 +MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M +cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83 +0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4 +0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R +uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV +1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt +7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2 +fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w +wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho +-----END CERTIFICATE----- + +Buypass Class 3 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1 +MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx +ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0 +n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia +AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c +1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7 +pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA +EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5 +htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj +el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 +-----END CERTIFICATE----- + +EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 +========================================================================== +-----BEGIN CERTIFICATE----- +MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg +QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe +Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p +ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt +IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by +X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b +gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr +eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ +TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy +Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn +uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI +qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm +ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0 +Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW +Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t +FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm +zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k +XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT +bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU +RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK +1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt +2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ +Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9 +AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +CNNIC ROOT +========== +-----BEGIN CERTIFICATE----- +MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE +ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw +OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD +o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz +VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT +VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or +czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK +y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC +wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S +lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5 +Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM +O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8 +BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2 +G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m +mxE= +-----END CERTIFICATE----- + +ApplicationCA - Japanese Government +=================================== +-----BEGIN CERTIFICATE----- +MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT +SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw +MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl +cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4 +fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN +wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE +jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu +nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU +WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV +BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD +vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs +o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g +/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD +io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW +dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL +rosot4LKGAfmt1t06SAZf7IbiVQ= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G3 +============================================= +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0 +IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz +NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo +YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT +LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j +K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE +c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C +IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu +dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr +2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9 +cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE +Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s +t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +thawte Primary Root CA - G2 +=========================== +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC +VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu +IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg +Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV +MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG +b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt +IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS +LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5 +8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN +G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K +rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +thawte Primary Root CA - G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w +ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD +VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG +A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At +P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC ++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY +7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW +vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ +KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK +A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC +8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm +er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G2 +============================================= +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu +Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 +OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl +b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG +BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc +KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ +EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m +ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 +npaqBA+K +-----END CERTIFICATE----- + +VeriSign Universal Root Certification Authority +=============================================== +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj +1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP +MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 +9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I +AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR +tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G +CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O +a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 +Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx +Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx +P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P +wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 +mJO37M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G4 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC +VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3 +b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz +ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU +cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo +b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8 +Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz +rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw +HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u +Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD +A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx +AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +============================================ +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA - G2 +================================== +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC +TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l +ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ +5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn +vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj +CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil +e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR +OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI +CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 +48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi +trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 +qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB +AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC +ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA +A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz ++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj +f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN +kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk +CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF +URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb +CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h +oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV +IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm +66+KAQ== +-----END CERTIFICATE----- + +CA Disig +======== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK +QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw +MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz +bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm +GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD +Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo +hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt +ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w +gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P +AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz +aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff +ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa +BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t +WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3 +mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ +CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K +ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA +4Z7CRneC9VkGjCFMhwnN5ag= +-----END CERTIFICATE----- + +Juur-SK +======= +-----BEGIN CERTIFICATE----- +MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA +c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw +DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG +SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy +aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf +TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC ++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw +UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa +Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF +MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD +HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh +AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA +cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr +AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw +cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE +FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G +A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo +ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL +abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678 +IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh +Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2 +yyqcjg== +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +ACEDICOM Root +============= +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD +T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4 +MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG +A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk +WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD +YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew +MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb +m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk +HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT +xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2 +3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9 +2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq +TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz +4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU +9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv +bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg +aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP +eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk +zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1 +ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI +KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq +nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE +I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp +MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o +tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow +XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94 +f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol +hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky +CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX +bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/ +D/xwzoiQ +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi +=================================================== +-----BEGIN CERTIFICATE----- +MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz +ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3 +MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0 +cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u +aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY +8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y +jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI +JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk +9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG +SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d +F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq +D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4 +Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq +fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Chambers of Commerce Root - 2008 +================================ +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy +Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl +ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF +EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl +cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA +XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj +h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ +ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk +NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g +D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331 +lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ +0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2 +EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI +G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ +BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh +bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh +bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC +CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH +AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1 +wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH +3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU +RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6 +M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1 +YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF +9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK +zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG +nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ +-----END CERTIFICATE----- + +Global Chambersign Root - 2008 +============================== +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx +NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg +Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ +QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf +VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf +XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0 +ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB +/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA +TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M +H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe +Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF +HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB +AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT +BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE +BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm +aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm +aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp +1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0 +dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG +/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6 +ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s +dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg +9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH +foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du +qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr +P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq +c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +Certinomis - Autorité Racine +============================= +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK +Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg +LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG +A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw +JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa +wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly +Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw +2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N +jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q +c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC +lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb +xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g +530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna +4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ +KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x +WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva +R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40 +nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B +CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv +JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE +qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b +WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE +wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/ +vgt2Fl43N+bYdJeimUV5 +-----END CERTIFICATE----- + +Root CA Generalitat Valenciana +============================== +-----BEGIN CERTIFICATE----- +MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE +ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290 +IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3 +WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE +CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2 +F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B +ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ +D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte +JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB +AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n +dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB +ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl +AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA +YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy +AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA +aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt +AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA +YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu +AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA +OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0 +dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV +BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G +A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S +b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh +TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz +Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63 +NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH +iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt ++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= +-----END CERTIFICATE----- + +A-Trust-nQual-03 +================ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE +Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy +a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R +dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw +RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0 +ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1 +c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA +zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n +yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE +SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4 +iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V +cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV +eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40 +ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr +sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd +JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS +mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6 +ahq97BvIxYSazQ== +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +EC-ACC +====== +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE +BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w +ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD +VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE +CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT +BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7 +MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt +SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl +Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh +cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK +w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT +ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4 +HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a +E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw +0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD +VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0 +Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l +dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ +lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa +Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe +l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 +E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D +5EI= +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2011 +======================================================= +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT +O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y +aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT +AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo +IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI +1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa +71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u +8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH +3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 +MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu +b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt +XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD +/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N +7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Trustis FPS Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290 +IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ +RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk +H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa +cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt +o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA +AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd +BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c +GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC +yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P +8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV +l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl +iB6XzCGcKQENZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ +Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 +dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu +c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv +bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 +aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t +L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG +cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 +fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm +N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN +Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T +tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX +e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA +2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs +HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib +D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +StartCom Certification Authority G2 +=================================== +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE +ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O +o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG +4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi +Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul +Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs +O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H +vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L +nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS +FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa +z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ +KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk +J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ +JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG +/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc +nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld +blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc +l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm +7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm +obp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +EE Certification Centre Root CA +=============================== +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy +dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw +MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB +UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy +ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM +TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 +rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw +93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN +P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ +MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF +BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj +xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM +lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU +3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM +dcGWxZ0= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2007 +================================================= +-----BEGIN CERTIFICATE----- +MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X +DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl +a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN +BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp +bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N +YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv +KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya +KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT +rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC +AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s +Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I +aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO +Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb +BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK +poRq0Tl9 +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +PSCProcert +========== +-----BEGIN CERTIFICATE----- +MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk +ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ +MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz +dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl +cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw +IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw +MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w +DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD +ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp +Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC +wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA +3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh +RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO +EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2 +0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH +0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU +td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw +Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp +r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/ +AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz +Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId +xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp +ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH +EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h +Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k +ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG +9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG +MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG +LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52 +ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy +YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v +Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o +dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq +T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN +g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q +uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1 +n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn +FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo +5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq +3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5 +poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y +eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km +-----END CERTIFICATE----- + +China Internet Network Information Center EV Certificates Root +============================================================== +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D +aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg +Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG +A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM +PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl +cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y +jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV +98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H +klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23 +KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC +7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD +glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5 +0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM +7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws +ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0 +5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8= +-----END CERTIFICATE----- + +Swisscom Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2 +MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM +LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo +ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ +wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH +Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a +SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS +NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab +mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY +Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3 +qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O +BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu +MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO +v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ +82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz +o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs +a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx +OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW +mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o ++sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC +rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX +5OfNeOI5wSsSnqaeG8XmDtkx2Q== +-----END CERTIFICATE----- + +Swisscom Root EV CA 2 +===================== +-----BEGIN CERTIFICATE----- +MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE +BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl +cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN +MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT +HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg +Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz +o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy +Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti +GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li +qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH +Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG +alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa +m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox +bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi +xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED +MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB +bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL +j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU +wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7 +XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH +59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/ +23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq +J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA +HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi +uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW +l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc= +-----END CERTIFICATE----- + +CA Disig Root R1 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy +3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8 +u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2 +m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk +CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa +YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6 +vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL +LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX +ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is +XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ +04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR +xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B +LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM +CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb +VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85 +YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS +ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix +lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N +UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ +a7+h89n07eLw4+1knj0vllJPgFOL +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php new file mode 100644 index 0000000..dbd4c18 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/StaticClient.php @@ -0,0 +1,157 @@ +createRequest($method, $url, null, null, $options); + + if (isset($options['stream'])) { + if ($options['stream'] instanceof StreamRequestFactoryInterface) { + return $options['stream']->fromRequest($request); + } elseif ($options['stream'] == true) { + $streamFactory = new PhpStreamRequestFactory(); + return $streamFactory->fromRequest($request); + } + } + + return $request->send(); + } + + /** + * Send a GET request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function get($url, $options = array()) + { + return self::request('GET', $url, $options); + } + + /** + * Send a HEAD request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function head($url, $options = array()) + { + return self::request('HEAD', $url, $options); + } + + /** + * Send a DELETE request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function delete($url, $options = array()) + { + return self::request('DELETE', $url, $options); + } + + /** + * Send a POST request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function post($url, $options = array()) + { + return self::request('POST', $url, $options); + } + + /** + * Send a PUT request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function put($url, $options = array()) + { + return self::request('PUT', $url, $options); + } + + /** + * Send a PATCH request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function patch($url, $options = array()) + { + return self::request('PATCH', $url, $options); + } + + /** + * Send an OPTIONS request + * + * @param string $url URL of the request + * @param array $options Array of request options + * + * @return \Guzzle\Http\Message\Response + * @see Guzzle::request for a list of available options + */ + public static function options($url, $options = array()) + { + return self::request('OPTIONS', $url, $options); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php new file mode 100644 index 0000000..6a4e772 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/Url.php @@ -0,0 +1,554 @@ + null, 'host' => null, 'path' => null, 'port' => null, 'query' => null, + 'user' => null, 'pass' => null, 'fragment' => null); + + if (false === ($parts = parse_url($url))) { + throw new InvalidArgumentException('Was unable to parse malformed url: ' . $url); + } + + $parts += $defaults; + + // Convert the query string into a QueryString object + if ($parts['query'] || 0 !== strlen($parts['query'])) { + $parts['query'] = QueryString::fromString($parts['query']); + } + + return new static($parts['scheme'], $parts['host'], $parts['user'], + $parts['pass'], $parts['port'], $parts['path'], $parts['query'], + $parts['fragment']); + } + + /** + * Build a URL from parse_url parts. The generated URL will be a relative URL if a scheme or host are not provided. + * + * @param array $parts Array of parse_url parts + * + * @return string + */ + public static function buildUrl(array $parts) + { + $url = $scheme = ''; + + if (isset($parts['scheme'])) { + $scheme = $parts['scheme']; + $url .= $scheme . ':'; + } + + if (isset($parts['host'])) { + $url .= '//'; + if (isset($parts['user'])) { + $url .= $parts['user']; + if (isset($parts['pass'])) { + $url .= ':' . $parts['pass']; + } + $url .= '@'; + } + + $url .= $parts['host']; + + // Only include the port if it is not the default port of the scheme + if (isset($parts['port']) + && !(($scheme == 'http' && $parts['port'] == 80) || ($scheme == 'https' && $parts['port'] == 443)) + ) { + $url .= ':' . $parts['port']; + } + } + + // Add the path component if present + if (isset($parts['path']) && 0 !== strlen($parts['path'])) { + // Always ensure that the path begins with '/' if set and something is before the path + if ($url && $parts['path'][0] != '/' && substr($url, -1) != '/') { + $url .= '/'; + } + $url .= $parts['path']; + } + + // Add the query string if present + if (isset($parts['query'])) { + $url .= '?' . $parts['query']; + } + + // Ensure that # is only added to the url if fragment contains anything. + if (isset($parts['fragment'])) { + $url .= '#' . $parts['fragment']; + } + + return $url; + } + + /** + * Create a new URL from URL parts + * + * @param string $scheme Scheme of the URL + * @param string $host Host of the URL + * @param string $username Username of the URL + * @param string $password Password of the URL + * @param int $port Port of the URL + * @param string $path Path of the URL + * @param QueryString|array|string $query Query string of the URL + * @param string $fragment Fragment of the URL + */ + public function __construct($scheme, $host, $username = null, $password = null, $port = null, $path = null, QueryString $query = null, $fragment = null) + { + $this->scheme = $scheme; + $this->host = $host; + $this->port = $port; + $this->username = $username; + $this->password = $password; + $this->fragment = $fragment; + if (!$query) { + $this->query = new QueryString(); + } else { + $this->setQuery($query); + } + $this->setPath($path); + } + + /** + * Clone the URL + */ + public function __clone() + { + $this->query = clone $this->query; + } + + /** + * Returns the URL as a URL string + * + * @return string + */ + public function __toString() + { + return self::buildUrl($this->getParts()); + } + + /** + * Get the parts of the URL as an array + * + * @return array + */ + public function getParts() + { + $query = (string) $this->query; + + return array( + 'scheme' => $this->scheme, + 'user' => $this->username, + 'pass' => $this->password, + 'host' => $this->host, + 'port' => $this->port, + 'path' => $this->getPath(), + 'query' => $query !== '' ? $query : null, + 'fragment' => $this->fragment, + ); + } + + /** + * Set the host of the request. + * + * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com) + * + * @return Url + */ + public function setHost($host) + { + if (strpos($host, ':') === false) { + $this->host = $host; + } else { + list($host, $port) = explode(':', $host); + $this->host = $host; + $this->setPort($port); + } + + return $this; + } + + /** + * Get the host part of the URL + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the scheme part of the URL (http, https, ftp, etc) + * + * @param string $scheme Scheme to set + * + * @return Url + */ + public function setScheme($scheme) + { + if ($this->scheme == 'http' && $this->port == 80) { + $this->port = null; + } elseif ($this->scheme == 'https' && $this->port == 443) { + $this->port = null; + } + + $this->scheme = $scheme; + + return $this; + } + + /** + * Get the scheme part of the URL + * + * @return string + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Set the port part of the URL + * + * @param int $port Port to set + * + * @return Url + */ + public function setPort($port) + { + $this->port = $port; + + return $this; + } + + /** + * Get the port part of the URl. Will return the default port for a given scheme if no port has been set. + * + * @return int|null + */ + public function getPort() + { + if ($this->port) { + return $this->port; + } elseif ($this->scheme == 'http') { + return 80; + } elseif ($this->scheme == 'https') { + return 443; + } + + return null; + } + + /** + * Set the path part of the URL + * + * @param array|string $path Path string or array of path segments + * + * @return Url + */ + public function setPath($path) + { + static $pathReplace = array(' ' => '%20', '?' => '%3F'); + if (is_array($path)) { + $path = '/' . implode('/', $path); + } + + $this->path = strtr($path, $pathReplace); + + return $this; + } + + /** + * Normalize the URL so that double slashes and relative paths are removed + * + * @return Url + */ + public function normalizePath() + { + if (!$this->path || $this->path == '/' || $this->path == '*') { + return $this; + } + + $results = array(); + $segments = $this->getPathSegments(); + foreach ($segments as $segment) { + if ($segment == '..') { + array_pop($results); + } elseif ($segment != '.' && $segment != '') { + $results[] = $segment; + } + } + + // Combine the normalized parts and add the leading slash if needed + $this->path = ($this->path[0] == '/' ? '/' : '') . implode('/', $results); + + // Add the trailing slash if necessary + if ($this->path != '/' && end($segments) == '') { + $this->path .= '/'; + } + + return $this; + } + + /** + * Add a relative path to the currently set path. + * + * @param string $relativePath Relative path to add + * + * @return Url + */ + public function addPath($relativePath) + { + if ($relativePath != '/' && is_string($relativePath) && strlen($relativePath) > 0) { + // Add a leading slash if needed + if ($relativePath[0] != '/') { + $relativePath = '/' . $relativePath; + } + $this->setPath(str_replace('//', '/', $this->path . $relativePath)); + } + + return $this; + } + + /** + * Get the path part of the URL + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Get the path segments of the URL as an array + * + * @return array + */ + public function getPathSegments() + { + return array_slice(explode('/', $this->getPath()), 1); + } + + /** + * Set the password part of the URL + * + * @param string $password Password to set + * + * @return Url + */ + public function setPassword($password) + { + $this->password = $password; + + return $this; + } + + /** + * Get the password part of the URL + * + * @return null|string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set the username part of the URL + * + * @param string $username Username to set + * + * @return Url + */ + public function setUsername($username) + { + $this->username = $username; + + return $this; + } + + /** + * Get the username part of the URl + * + * @return null|string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Get the query part of the URL as a QueryString object + * + * @return QueryString + */ + public function getQuery() + { + return $this->query; + } + + /** + * Set the query part of the URL + * + * @param QueryString|string|array $query Query to set + * + * @return Url + */ + public function setQuery($query) + { + if (is_string($query)) { + $output = null; + parse_str($query, $output); + $this->query = new QueryString($output); + } elseif (is_array($query)) { + $this->query = new QueryString($query); + } elseif ($query instanceof QueryString) { + $this->query = $query; + } + + return $this; + } + + /** + * Get the fragment part of the URL + * + * @return null|string + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * Set the fragment part of the URL + * + * @param string $fragment Fragment to set + * + * @return Url + */ + public function setFragment($fragment) + { + $this->fragment = $fragment; + + return $this; + } + + /** + * Check if this is an absolute URL + * + * @return bool + */ + public function isAbsolute() + { + return $this->scheme && $this->host; + } + + /** + * Combine the URL with another URL. Follows the rules specific in RFC 3986 section 5.4. + * + * @param string $url Relative URL to combine with + * @param bool $strictRfc3986 Set to true to use strict RFC 3986 compliance when merging paths. When first + * released, Guzzle used an incorrect algorithm for combining relative URL paths. In + * order to not break users, we introduced this flag to allow the merging of URLs based + * on strict RFC 3986 section 5.4.1. This means that "http://a.com/foo/baz" merged with + * "bar" would become "http://a.com/foo/bar". When this value is set to false, it would + * become "http://a.com/foo/baz/bar". + * @return Url + * @throws InvalidArgumentException + * @link http://tools.ietf.org/html/rfc3986#section-5.4 + */ + public function combine($url, $strictRfc3986 = false) + { + $url = self::factory($url); + + // Use the more absolute URL as the base URL + if (!$this->isAbsolute() && $url->isAbsolute()) { + $url = $url->combine($this); + } + + // Passing a URL with a scheme overrides everything + if ($buffer = $url->getScheme()) { + $this->scheme = $buffer; + $this->host = $url->getHost(); + $this->port = $url->getPort(); + $this->username = $url->getUsername(); + $this->password = $url->getPassword(); + $this->path = $url->getPath(); + $this->query = $url->getQuery(); + $this->fragment = $url->getFragment(); + return $this; + } + + // Setting a host overrides the entire rest of the URL + if ($buffer = $url->getHost()) { + $this->host = $buffer; + $this->port = $url->getPort(); + $this->username = $url->getUsername(); + $this->password = $url->getPassword(); + $this->path = $url->getPath(); + $this->query = $url->getQuery(); + $this->fragment = $url->getFragment(); + return $this; + } + + $path = $url->getPath(); + $query = $url->getQuery(); + + if (!$path) { + if (count($query)) { + $this->addQuery($query, $strictRfc3986); + } + } else { + if ($path[0] == '/') { + $this->path = $path; + } elseif ($strictRfc3986) { + $this->path .= '/../' . $path; + } else { + $this->path .= '/' . $path; + } + $this->normalizePath(); + $this->addQuery($query, $strictRfc3986); + } + + $this->fragment = $url->getFragment(); + + return $this; + } + + private function addQuery(QueryString $new, $strictRfc386) + { + if (!$strictRfc386) { + $new->merge($this->query); + } + + $this->query = $new; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json new file mode 100644 index 0000000..9384a5b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Http/composer.json @@ -0,0 +1,32 @@ +{ + "name": "guzzle/http", + "description": "HTTP libraries used by Guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": ["http client", "http", "client", "Guzzle", "curl"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/common": "self.version", + "guzzle/parser": "self.version", + "guzzle/stream": "self.version" + }, + "suggest": { + "ext-curl": "*" + }, + "autoload": { + "psr-0": { "Guzzle\\Http": "" } + }, + "target-dir": "Guzzle/Http", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php b/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php new file mode 100644 index 0000000..c699773 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/Inflector.php @@ -0,0 +1,38 @@ + array(), + 'camel' => array() + ); + + /** @var int Max entries per cache */ + protected $maxCacheSize; + + /** @var InflectorInterface Decorated inflector */ + protected $decoratedInflector; + + /** + * @param InflectorInterface $inflector Inflector being decorated + * @param int $maxCacheSize Maximum number of cached items to hold per cache + */ + public function __construct(InflectorInterface $inflector, $maxCacheSize = 500) + { + $this->decoratedInflector = $inflector; + $this->maxCacheSize = $maxCacheSize; + } + + public function snake($word) + { + if (!isset($this->cache['snake'][$word])) { + $this->pruneCache('snake'); + $this->cache['snake'][$word] = $this->decoratedInflector->snake($word); + } + + return $this->cache['snake'][$word]; + } + + /** + * Converts strings from snake_case to upper CamelCase + * + * @param string $word Value to convert into upper CamelCase + * + * @return string + */ + public function camel($word) + { + if (!isset($this->cache['camel'][$word])) { + $this->pruneCache('camel'); + $this->cache['camel'][$word] = $this->decoratedInflector->camel($word); + } + + return $this->cache['camel'][$word]; + } + + /** + * Prune one of the named caches by removing 20% of the cache if it is full + * + * @param string $cache Type of cache to prune + */ + protected function pruneCache($cache) + { + if (count($this->cache[$cache]) == $this->maxCacheSize) { + $this->cache[$cache] = array_slice($this->cache[$cache], $this->maxCacheSize * 0.2); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php b/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php new file mode 100644 index 0000000..db37e4f --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/PreComputedInflector.php @@ -0,0 +1,59 @@ + array(), + 'camel' => array() + ); + + /** @var InflectorInterface Decorated inflector */ + protected $decoratedInflector; + + /** + * @param InflectorInterface $inflector Inflector being decorated + * @param array $snake Hash of pre-computed camel to snake + * @param array $camel Hash of pre-computed snake to camel + * @param bool $mirror Mirror snake and camel reflections + */ + public function __construct(InflectorInterface $inflector, array $snake = array(), array $camel = array(), $mirror = false) + { + if ($mirror) { + $camel = array_merge(array_flip($snake), $camel); + $snake = array_merge(array_flip($camel), $snake); + } + + $this->decoratedInflector = $inflector; + $this->mapping = array( + 'snake' => $snake, + 'camel' => $camel + ); + } + + public function snake($word) + { + return isset($this->mapping['snake'][$word]) + ? $this->mapping['snake'][$word] + : $this->decoratedInflector->snake($word); + } + + /** + * Converts strings from snake_case to upper CamelCase + * + * @param string $word Value to convert into upper CamelCase + * + * @return string + */ + public function camel($word) + { + return isset($this->mapping['camel'][$word]) + ? $this->mapping['camel'][$word] + : $this->decoratedInflector->camel($word); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json new file mode 100644 index 0000000..93f9e7b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Inflection/composer.json @@ -0,0 +1,26 @@ +{ + "name": "guzzle/inflection", + "description": "Guzzle inflection component", + "homepage": "http://guzzlephp.org/", + "keywords": ["inflection", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { "Guzzle\\Inflection": "" } + }, + "target-dir": "Guzzle/Inflection", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php new file mode 100644 index 0000000..1b6bd7e --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/AppendIterator.php @@ -0,0 +1,19 @@ +getArrayIterator()->append($iterator); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php new file mode 100644 index 0000000..d76cdd4 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/ChunkedIterator.php @@ -0,0 +1,56 @@ +chunkSize = $chunkSize; + } + + public function rewind() + { + parent::rewind(); + $this->next(); + } + + public function next() + { + $this->chunk = array(); + for ($i = 0; $i < $this->chunkSize && parent::valid(); $i++) { + $this->chunk[] = parent::current(); + parent::next(); + } + } + + public function current() + { + return $this->chunk; + } + + public function valid() + { + return (bool) $this->chunk; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php new file mode 100644 index 0000000..b103367 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/FilterIterator.php @@ -0,0 +1,36 @@ +callback = $callback; + } + + public function accept() + { + return call_user_func($this->callback, $this->current()); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php new file mode 100644 index 0000000..7e586bd --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/MapIterator.php @@ -0,0 +1,34 @@ +callback = $callback; + } + + public function current() + { + return call_user_func($this->callback, parent::current()); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php new file mode 100644 index 0000000..de4ab03 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/MethodProxyIterator.php @@ -0,0 +1,27 @@ +getInnerIterator(); + while ($i instanceof \OuterIterator) { + $i = $i->getInnerIterator(); + } + + return call_user_func_array(array($i, $name), $args); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md new file mode 100644 index 0000000..8bb7e08 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/README.md @@ -0,0 +1,25 @@ +Guzzle Iterator +=============== + +Provides useful Iterators and Iterator decorators + +- ChunkedIterator: Pulls out chunks from an inner iterator and yields the chunks as arrays +- FilterIterator: Used when PHP 5.4's CallbackFilterIterator is not available +- MapIterator: Maps values before yielding +- MethodProxyIterator: Proxies missing method calls to the innermost iterator + +### Installing via Composer + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php + +# Add Guzzle as a dependency +php composer.phar require guzzle/iterator:~3.0 +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json new file mode 100644 index 0000000..ee17379 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Iterator/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/iterator", + "description": "Provides helpful iterators and iterator decorators", + "keywords": ["iterator", "guzzle"], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/common": ">=2.8.0" + }, + "autoload": { + "psr-0": { "Guzzle\\Iterator": "/" } + }, + "target-dir": "Guzzle/Iterator", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php new file mode 100644 index 0000000..7f6271b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/AbstractLogAdapter.php @@ -0,0 +1,16 @@ +log; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php new file mode 100644 index 0000000..a70fc8d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/ArrayLogAdapter.php @@ -0,0 +1,34 @@ +logs[] = array('message' => $message, 'priority' => $priority, 'extras' => $extras); + } + + /** + * Get logged entries + * + * @return array + */ + public function getLogs() + { + return $this->logs; + } + + /** + * Clears logged entries + */ + public function clearLogs() + { + $this->logs = array(); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php new file mode 100644 index 0000000..d4bb73f --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/ClosureLogAdapter.php @@ -0,0 +1,23 @@ +log = $logObject; + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + call_user_func($this->log, $message, $priority, $extras); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php new file mode 100644 index 0000000..d7ac4ea --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/LogAdapterInterface.php @@ -0,0 +1,18 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{curl_stderr}"; + const SHORT_FORMAT = '[{ts}] "{method} {resource} {protocol}/{version}" {code}'; + + /** + * @var string Template used to format log messages + */ + protected $template; + + /** + * @param string $template Log message template + */ + public function __construct($template = self::DEFAULT_FORMAT) + { + $this->template = $template ?: self::DEFAULT_FORMAT; + } + + /** + * Set the template to use for logging + * + * @param string $template Log message template + * + * @return self + */ + public function setTemplate($template) + { + $this->template = $template; + + return $this; + } + + /** + * Returns a formatted message + * + * @param RequestInterface $request Request that was sent + * @param Response $response Response that was received + * @param CurlHandle $handle Curl handle associated with the message + * @param array $customData Associative array of custom template data + * + * @return string + */ + public function format( + RequestInterface $request, + Response $response = null, + CurlHandle $handle = null, + array $customData = array() + ) { + $cache = $customData; + + return preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $handle, &$cache) { + + if (array_key_exists($matches[1], $cache)) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = (string) $request; + break; + case 'response': + $result = (string) $response; + break; + case 'req_body': + $result = $request instanceof EntityEnclosingRequestInterface + ? (string) $request->getBody() : ''; + break; + case 'res_body': + $result = $response ? $response->getBody(true) : ''; + break; + case 'ts': + $result = gmdate('c'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'url': + $result = (string) $request->getUrl(); + break; + case 'resource': + $result = $request->getResource(); + break; + case 'protocol': + $result = 'HTTP'; + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'host': + $result = $request->getHost(); + break; + case 'hostname': + $result = gethostname(); + break; + case 'port': + $result = $request->getPort(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : ''; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : ''; + break; + case 'connect_time': + $result = $handle && $handle->getInfo(CURLINFO_CONNECT_TIME) + ? $handle->getInfo(CURLINFO_CONNECT_TIME) + : ($response ? $response->getInfo('connect_time') : ''); + break; + case 'total_time': + $result = $handle && $handle->getInfo(CURLINFO_TOTAL_TIME) + ? $handle->getInfo(CURLINFO_TOTAL_TIME) + : ($response ? $response->getInfo('total_time') : ''); + break; + case 'curl_error': + $result = $handle ? $handle->getError() : ''; + break; + case 'curl_code': + $result = $handle ? $handle->getErrorNo() : ''; + break; + case 'curl_stderr': + $result = $handle ? $handle->getStderr() : ''; + break; + default: + if (strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeader(substr($matches[1], 11)); + } elseif ($response && strpos($matches[1], 'res_header_') === 0) { + $result = $response->getHeader(substr($matches[1], 11)); + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php new file mode 100644 index 0000000..6afe7b6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/MonologLogAdapter.php @@ -0,0 +1,34 @@ + Logger::DEBUG, + LOG_INFO => Logger::INFO, + LOG_WARNING => Logger::WARNING, + LOG_ERR => Logger::ERROR, + LOG_CRIT => Logger::CRITICAL, + LOG_ALERT => Logger::ALERT + ); + + public function __construct(Logger $logObject) + { + $this->log = $logObject; + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + $this->log->addRecord(self::$mapping[$priority], $message, $extras); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php new file mode 100644 index 0000000..38a2b60 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/PsrLogAdapter.php @@ -0,0 +1,36 @@ + LogLevel::DEBUG, + LOG_INFO => LogLevel::INFO, + LOG_WARNING => LogLevel::WARNING, + LOG_ERR => LogLevel::ERROR, + LOG_CRIT => LogLevel::CRITICAL, + LOG_ALERT => LogLevel::ALERT + ); + + public function __construct(LoggerInterface $logObject) + { + $this->log = $logObject; + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + $this->log->log(self::$mapping[$priority], $message, $extras); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php new file mode 100644 index 0000000..0ea8e3b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/Zf1LogAdapter.php @@ -0,0 +1,24 @@ +log = $logObject; + Version::warn(__CLASS__ . ' is deprecated'); + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + $this->log->log($message, $priority, $extras); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php new file mode 100644 index 0000000..863f6a1 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/Zf2LogAdapter.php @@ -0,0 +1,21 @@ +log = $logObject; + } + + public function log($message, $priority = LOG_INFO, $extras = array()) + { + $this->log->log($priority, $message, $extras); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json new file mode 100644 index 0000000..a8213e8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Log/composer.json @@ -0,0 +1,29 @@ +{ + "name": "guzzle/log", + "description": "Guzzle log adapter component", + "homepage": "http://guzzlephp.org/", + "keywords": ["log", "adapter", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { "Guzzle\\Log": "" } + }, + "suggest": { + "guzzle/http": "self.version" + }, + "target-dir": "Guzzle/Log", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php new file mode 100644 index 0000000..4349eeb --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParser.php @@ -0,0 +1,131 @@ + 'Domain', + 'path' => 'Path', + 'max_age' => 'Max-Age', + 'expires' => 'Expires', + 'version' => 'Version', + 'secure' => 'Secure', + 'port' => 'Port', + 'discard' => 'Discard', + 'comment' => 'Comment', + 'comment_url' => 'Comment-Url', + 'http_only' => 'HttpOnly' + ); + + public function parseCookie($cookie, $host = null, $path = null, $decode = false) + { + // 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 include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return false; + } + + // Create the default return array + $data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array( + 'cookies' => array(), + 'data' => array(), + 'path' => null, + 'http_only' => false, + 'discard' => false, + 'domain' => $host + )); + $foundNonCookies = 0; + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + + if (count($cookieParts) == 1) { + // Can be a single value (e.g. secure, httpOnly) + $value = true; + } else { + // Be sure to strip wrapping quotes + $value = trim($cookieParts[1], " \n\r\t\0\x0B\""); + if ($decode) { + $value = urldecode($value); + } + } + + // Only check for non-cookies when cookies have been found + if (!empty($data['cookies'])) { + foreach (self::$cookieParts as $mapValue => $search) { + if (!strcasecmp($search, $key)) { + $data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value; + $foundNonCookies++; + continue 2; + } + } + } + + // If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a + // cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data. + $data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value; + } + + // Calculate the expires date + if (!$data['expires'] && $data['max_age']) { + $data['expires'] = time() + (int) $data['max_age']; + } + + // Check path attribute according RFC6265 http://tools.ietf.org/search/rfc6265#section-5.2.4 + // "If the attribute-value is empty or if the first character of the + // attribute-value is not %x2F ("/"): + // Let cookie-path be the default-path. + // Otherwise: + // Let cookie-path be the attribute-value." + if (!$data['path'] || substr($data['path'], 0, 1) !== '/') { + $data['path'] = $this->getDefaultPath($path); + } + + return $data; + } + + /** + * Get default cookie path according to RFC 6265 + * http://tools.ietf.org/search/rfc6265#section-5.1.4 Paths and Path-Match + * + * @param string $path Request uri-path + * + * @return string + */ + protected function getDefaultPath($path) { + // "The user agent MUST use an algorithm equivalent to the following algorithm + // to compute the default-path of a cookie:" + + // "2. If the uri-path is empty or if the first character of the uri-path is not + // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps. + if (empty($path) || substr($path, 0, 1) !== '/') { + return '/'; + } + + // "3. If the uri-path contains no more than one %x2F ("/") character, output + // %x2F ("/") and skip the remaining step." + if ($path === "/") { + return $path; + } + + $rightSlashPos = strrpos($path, '/'); + if ($rightSlashPos === 0) { + return "/"; + } + + // "4. Output the characters of the uri-path from the first character up to, + // but not including, the right-most %x2F ("/")." + return substr($path, 0, $rightSlashPos); + + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php new file mode 100644 index 0000000..d21ffe2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Cookie/CookieParserInterface.php @@ -0,0 +1,33 @@ + $requestUrl, + 'scheme' => 'http' + ); + + // Check for the Host header + if (isset($parts['headers']['Host'])) { + $urlParts['host'] = $parts['headers']['Host']; + } elseif (isset($parts['headers']['host'])) { + $urlParts['host'] = $parts['headers']['host']; + } else { + $urlParts['host'] = null; + } + + if (false === strpos($urlParts['host'], ':')) { + $urlParts['port'] = ''; + } else { + $hostParts = explode(':', $urlParts['host']); + $urlParts['host'] = trim($hostParts[0]); + $urlParts['port'] = (int) trim($hostParts[1]); + if ($urlParts['port'] == 443) { + $urlParts['scheme'] = 'https'; + } + } + + // Check if a query is present + $path = $urlParts['path']; + $qpos = strpos($path, '?'); + if ($qpos) { + $urlParts['query'] = substr($path, $qpos + 1); + $urlParts['path'] = substr($path, 0, $qpos); + } else { + $urlParts['query'] = ''; + } + + return $urlParts; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php new file mode 100644 index 0000000..efc1aa3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParser.php @@ -0,0 +1,110 @@ +parseMessage($message); + + // Parse the protocol and protocol version + if (isset($parts['start_line'][2])) { + $startParts = explode('/', $parts['start_line'][2]); + $protocol = strtoupper($startParts[0]); + $version = isset($startParts[1]) ? $startParts[1] : '1.1'; + } else { + $protocol = 'HTTP'; + $version = '1.1'; + } + + $parsed = array( + 'method' => strtoupper($parts['start_line'][0]), + 'protocol' => $protocol, + 'version' => $version, + 'headers' => $parts['headers'], + 'body' => $parts['body'] + ); + + $parsed['request_url'] = $this->getUrlPartsFromMessage(isset($parts['start_line'][1]) ? $parts['start_line'][1] : '' , $parsed); + + return $parsed; + } + + public function parseResponse($message) + { + if (!$message) { + return false; + } + + $parts = $this->parseMessage($message); + list($protocol, $version) = explode('/', trim($parts['start_line'][0])); + + return array( + 'protocol' => $protocol, + 'version' => $version, + 'code' => $parts['start_line'][1], + 'reason_phrase' => isset($parts['start_line'][2]) ? $parts['start_line'][2] : '', + 'headers' => $parts['headers'], + 'body' => $parts['body'] + ); + } + + /** + * Parse a message into parts + * + * @param string $message Message to parse + * + * @return array + */ + protected function parseMessage($message) + { + $startLine = null; + $headers = array(); + $body = ''; + + // Iterate over each line in the message, accounting for line endings + $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { + + $line = $lines[$i]; + + // If two line breaks were encountered, then this is the end of body + if (empty($line)) { + if ($i < $totalLines - 1) { + $body = implode('', array_slice($lines, $i + 2)); + } + break; + } + + // Parse message headers + if (!$startLine) { + $startLine = explode(' ', $line, 3); + } elseif (strpos($line, ':')) { + $parts = explode(':', $line, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : ''; + if (!isset($headers[$key])) { + $headers[$key] = $value; + } elseif (!is_array($headers[$key])) { + $headers[$key] = array($headers[$key], $value); + } else { + $headers[$key][] = $value; + } + } + } + + return array( + 'start_line' => $startLine, + 'headers' => $headers, + 'body' => $body + ); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php new file mode 100644 index 0000000..cc44808 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Message/MessageParserInterface.php @@ -0,0 +1,27 @@ + $parts->requestMethod, + 'protocol' => 'HTTP', + 'version' => number_format($parts->httpVersion, 1), + 'headers' => $parts->headers, + 'body' => $parts->body + ); + + $parsed['request_url'] = $this->getUrlPartsFromMessage($parts->requestUrl, $parsed); + + return $parsed; + } + + public function parseResponse($message) + { + if (!$message) { + return false; + } + + $parts = http_parse_message($message); + + return array( + 'protocol' => 'HTTP', + 'version' => number_format($parts->httpVersion, 1), + 'code' => $parts->responseCode, + 'reason_phrase' => $parts->responseStatus, + 'headers' => $parts->headers, + 'body' => $parts->body + ); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php new file mode 100644 index 0000000..f838683 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/ParserRegistry.php @@ -0,0 +1,75 @@ + 'Guzzle\\Parser\\Message\\MessageParser', + 'cookie' => 'Guzzle\\Parser\\Cookie\\CookieParser', + 'url' => 'Guzzle\\Parser\\Url\\UrlParser', + 'uri_template' => 'Guzzle\\Parser\\UriTemplate\\UriTemplate', + ); + + /** + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new static; + } + + return self::$instance; + } + + public function __construct() + { + // Use the PECL URI template parser if available + if (extension_loaded('uri_template')) { + $this->mapping['uri_template'] = 'Guzzle\\Parser\\UriTemplate\\PeclUriTemplate'; + } + } + + /** + * Get a parser by name from an instance + * + * @param string $name Name of the parser to retrieve + * + * @return mixed|null + */ + public function getParser($name) + { + if (!isset($this->instances[$name])) { + if (!isset($this->mapping[$name])) { + return null; + } + $class = $this->mapping[$name]; + $this->instances[$name] = new $class(); + } + + return $this->instances[$name]; + } + + /** + * Register a custom parser by name with the register + * + * @param string $name Name or handle of the parser to register + * @param mixed $parser Instantiated parser to register + */ + public function registerParser($name, $parser) + { + $this->instances[$name] = $parser; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php new file mode 100644 index 0000000..b0764e8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/PeclUriTemplate.php @@ -0,0 +1,26 @@ + true, '#' => true, '.' => true, '/' => true, ';' => true, '?' => true, '&' => true + ); + + /** @var array Delimiters */ + private static $delims = array( + ':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' + ); + + /** @var array Percent encoded delimiters */ + private static $delimsPct = array( + '%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D' + ); + + public function expand($template, array $variables) + { + if ($this->regex == self::DEFAULT_PATTERN && false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback($this->regex, array($this, 'expandMatch'), $this->template); + } + + /** + * Set the regex patten used to expand URI templates + * + * @param string $regexPattern + */ + public function setRegex($regexPattern) + { + $this->regex = $regexPattern; + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + // Check for URI operators + $operator = ''; + + if (isset(self::$operatorHash[$expression[0]])) { + $operator = $expression[0]; + $expression = substr($expression, 1); + } + + $values = explode(',', $expression); + foreach ($values as &$value) { + $value = trim($value); + $varspec = array(); + $substrPos = strpos($value, ':'); + if ($substrPos) { + $varspec['value'] = substr($value, 0, $substrPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $substrPos + 1); + } elseif (substr($value, -1) == '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $value = $varspec; + } + + return array( + 'operator' => $operator, + 'values' => $values + ); + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = array( + '+' => '%20', + '%7e' => '~' + ); + + $parsed = self::parseExpression($matches[1]); + $replacements = array(); + + $prefix = $parsed['operator']; + $joiner = $parsed['operator']; + $useQueryString = false; + if ($parsed['operator'] == '?') { + $joiner = '&'; + $useQueryString = true; + } elseif ($parsed['operator'] == '&') { + $useQueryString = true; + } elseif ($parsed['operator'] == '#') { + $joiner = ','; + } elseif ($parsed['operator'] == ';') { + $useQueryString = true; + } elseif ($parsed['operator'] == '' || $parsed['operator'] == '+') { + $joiner = ','; + $prefix = ''; + } + + foreach ($parsed['values'] as $value) { + + if (!array_key_exists($value['value'], $this->variables) || $this->variables[$value['value']] === null) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQueryString = $useQueryString; + $expanded = ''; + + if (is_array($variable)) { + + $isAssoc = $this->isAssoc($variable); + $kvp = array(); + foreach ($variable as $key => $var) { + + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] == '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested structures + $var = strtr(http_build_query(array($key => $var)), $rfc1738to3986); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQueryString) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQueryString = false; + } elseif ($value['modifier'] == '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode modifier with an associative array + $actuallyUseQueryString = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the explode modifier is not set, then the + // result must be a comma separated list of keys followed by their respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + + } else { + if ($value['modifier'] == ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQueryString) { + if (!$expanded && $joiner != '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } + + /** + * Removes percent encoding on reserved characters (used with + and # modifiers) + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php new file mode 100644 index 0000000..c81d515 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/UriTemplate/UriTemplateInterface.php @@ -0,0 +1,21 @@ +utf8 = $utf8; + } + + public function parseUrl($url) + { + Version::warn(__CLASS__ . ' is deprecated. Just use parse_url()'); + + static $defaults = array('scheme' => null, 'host' => null, 'path' => null, 'port' => null, 'query' => null, + 'user' => null, 'pass' => null, 'fragment' => null); + + $parts = parse_url($url); + + // Need to handle query parsing specially for UTF-8 requirements + if ($this->utf8 && isset($parts['query'])) { + $queryPos = strpos($url, '?'); + if (isset($parts['fragment'])) { + $parts['query'] = substr($url, $queryPos + 1, strpos($url, '#') - $queryPos - 1); + } else { + $parts['query'] = substr($url, $queryPos + 1); + } + } + + return $parts + $defaults; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php new file mode 100644 index 0000000..89ac4b3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Parser/Url/UrlParserInterface.php @@ -0,0 +1,19 @@ +=5.3.2" + }, + "autoload": { + "psr-0": { "Guzzle\\Parser": "" } + }, + "target-dir": "Guzzle/Parser", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php new file mode 100644 index 0000000..ae59418 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php @@ -0,0 +1,84 @@ + 'onBeforeSend', + 'request.exception' => 'onRequestTimeout', + 'request.sent' => 'onRequestSent', + 'curl.callback.progress' => 'onCurlProgress' + ); + } + + /** + * Event used to ensure that progress callback are emitted from the curl handle's request mediator. + * + * @param Event $event + */ + public function onBeforeSend(Event $event) + { + // Ensure that progress callbacks are dispatched + $event['request']->getCurlOptions()->set('progress', true); + } + + /** + * Event emitted when a curl progress function is called. When the amount of data uploaded == the amount of data to + * upload OR any bytes have been downloaded, then time the request out after 1ms because we're done with + * transmitting the request, and tell curl not download a body. + * + * @param Event $event + */ + public function onCurlProgress(Event $event) + { + if ($event['handle'] && + ($event['downloaded'] || (isset($event['uploaded']) && $event['upload_size'] === $event['uploaded'])) + ) { + // Timeout after 1ms + curl_setopt($event['handle'], CURLOPT_TIMEOUT_MS, 1); + // Even if the response is quick, tell curl not to download the body. + // - Note that we can only perform this shortcut if the request transmitted a body so as to ensure that the + // request method is not converted to a HEAD request before the request was sent via curl. + if ($event['uploaded']) { + curl_setopt($event['handle'], CURLOPT_NOBODY, true); + } + } + } + + /** + * Event emitted when a curl exception occurs. Ignore the exception and set a mock response. + * + * @param Event $event + */ + public function onRequestTimeout(Event $event) + { + if ($event['exception'] instanceof CurlException) { + $event['request']->setResponse(new Response(200, array( + 'X-Guzzle-Async' => 'Did not wait for the response' + ))); + } + } + + /** + * Event emitted when a request completes because it took less than 1ms. Add an X-Guzzle-Async header to notify the + * caller that there is no body in the message. + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + // Let the caller know this was meant to be async + $event['request']->getResponse()->setHeader('X-Guzzle-Async', 'Did not wait for the response'); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json new file mode 100644 index 0000000..dc3fc5b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Async/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-async", + "description": "Guzzle async request plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Async": "" } + }, + "target-dir": "Guzzle/Plugin/Async", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php new file mode 100644 index 0000000..0a85983 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractBackoffStrategy.php @@ -0,0 +1,91 @@ +next = $next; + } + + /** + * Get the next backoff strategy in the chain + * + * @return AbstractBackoffStrategy|null + */ + public function getNext() + { + return $this->next; + } + + public function getBackoffPeriod( + $retries, + RequestInterface $request, + Response $response = null, + HttpException $e = null + ) { + $delay = $this->getDelay($retries, $request, $response, $e); + if ($delay === false) { + // The strategy knows that this must not be retried + return false; + } elseif ($delay === null) { + // If the strategy is deferring a decision and the next strategy will not make a decision then return false + return !$this->next || !$this->next->makesDecision() + ? false + : $this->next->getBackoffPeriod($retries, $request, $response, $e); + } elseif ($delay === true) { + // if the strategy knows that it must retry but is deferring to the next to determine the delay + if (!$this->next) { + return 0; + } else { + $next = $this->next; + while ($next->makesDecision() && $next->getNext()) { + $next = $next->getNext(); + } + return !$next->makesDecision() ? $next->getBackoffPeriod($retries, $request, $response, $e) : 0; + } + } else { + return $delay; + } + } + + /** + * Check if the strategy does filtering and makes decisions on whether or not to retry. + * + * Strategies that return false will never retry if all of the previous strategies in a chain defer on a backoff + * decision. + * + * @return bool + */ + abstract public function makesDecision(); + + /** + * Implement the concrete strategy + * + * @param int $retries Number of retries of the request + * @param RequestInterface $request Request that was sent + * @param Response $response Response that was received. Note that there may not be a response + * @param HttpException $e Exception that was encountered if any + * + * @return bool|int|null Returns false to not retry or the number of seconds to delay between retries. Return true + * or null to defer to the next strategy if available, and if not, return 0. + */ + abstract protected function getDelay( + $retries, + RequestInterface $request, + Response $response = null, + HttpException $e = null + ); +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php new file mode 100644 index 0000000..6ebee6c --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/AbstractErrorCodeBackoffStrategy.php @@ -0,0 +1,40 @@ +errorCodes = array_fill_keys($codes ?: static::$defaultErrorCodes, 1); + $this->next = $next; + } + + /** + * Get the default failure codes to retry + * + * @return array + */ + public static function getDefaultFailureCodes() + { + return static::$defaultErrorCodes; + } + + public function makesDecision() + { + return true; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php new file mode 100644 index 0000000..ec54c28 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffLogger.php @@ -0,0 +1,76 @@ +logger = $logger; + $this->formatter = $formatter ?: new MessageFormatter(self::DEFAULT_FORMAT); + } + + public static function getSubscribedEvents() + { + return array(BackoffPlugin::RETRY_EVENT => 'onRequestRetry'); + } + + /** + * Set the template to use for logging + * + * @param string $template Log message template + * + * @return self + */ + public function setTemplate($template) + { + $this->formatter->setTemplate($template); + + return $this; + } + + /** + * Called when a request is being retried + * + * @param Event $event Event emitted + */ + public function onRequestRetry(Event $event) + { + $this->logger->log($this->formatter->format( + $event['request'], + $event['response'], + $event['handle'], + array( + 'retries' => $event['retries'], + 'delay' => $event['delay'] + ) + )); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php new file mode 100644 index 0000000..99ace05 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffPlugin.php @@ -0,0 +1,126 @@ +strategy = $strategy; + } + + /** + * Retrieve a basic truncated exponential backoff plugin that will retry HTTP errors and cURL errors + * + * @param int $maxRetries Maximum number of retries + * @param array $httpCodes HTTP response codes to retry + * @param array $curlCodes cURL error codes to retry + * + * @return self + */ + public static function getExponentialBackoff( + $maxRetries = 3, + array $httpCodes = null, + array $curlCodes = null + ) { + return new self(new TruncatedBackoffStrategy($maxRetries, + new HttpBackoffStrategy($httpCodes, + new CurlBackoffStrategy($curlCodes, + new ExponentialBackoffStrategy() + ) + ) + )); + } + + public static function getAllEvents() + { + return array(self::RETRY_EVENT); + } + + public static function getSubscribedEvents() + { + return array( + 'request.sent' => 'onRequestSent', + 'request.exception' => 'onRequestSent', + CurlMultiInterface::POLLING_REQUEST => 'onRequestPoll' + ); + } + + /** + * Called when a request has been sent and isn't finished processing + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + $request = $event['request']; + $response = $event['response']; + $exception = $event['exception']; + + $params = $request->getParams(); + $retries = (int) $params->get(self::RETRY_PARAM); + $delay = $this->strategy->getBackoffPeriod($retries, $request, $response, $exception); + + if ($delay !== false) { + // Calculate how long to wait until the request should be retried + $params->set(self::RETRY_PARAM, ++$retries) + ->set(self::DELAY_PARAM, microtime(true) + $delay); + // Send the request again + $request->setState(RequestInterface::STATE_TRANSFER); + $this->dispatch(self::RETRY_EVENT, array( + 'request' => $request, + 'response' => $response, + 'handle' => ($exception && $exception instanceof CurlException) ? $exception->getCurlHandle() : null, + 'retries' => $retries, + 'delay' => $delay + )); + } + } + + /** + * Called when a request is polling in the curl multi object + * + * @param Event $event + */ + public function onRequestPoll(Event $event) + { + $request = $event['request']; + $delay = $request->getParams()->get(self::DELAY_PARAM); + + // If the duration of the delay has passed, retry the request using the pool + if (null !== $delay && microtime(true) >= $delay) { + // Remove the request from the pool and then add it back again. This is required for cURL to know that we + // want to retry sending the easy handle. + $request->getParams()->remove(self::DELAY_PARAM); + // Rewind the request body if possible + if ($request instanceof EntityEnclosingRequestInterface && $request->getBody()) { + $request->getBody()->seek(0); + } + $multi = $event['curl_multi']; + $multi->remove($request); + $multi->add($request); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php new file mode 100644 index 0000000..4e590db --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/BackoffStrategyInterface.php @@ -0,0 +1,30 @@ +callback = $callback; + $this->decision = (bool) $decision; + $this->next = $next; + } + + public function makesDecision() + { + return $this->decision; + } + + protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null) + { + return call_user_func($this->callback, $retries, $request, $response, $e); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php new file mode 100644 index 0000000..061d2a4 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ConstantBackoffStrategy.php @@ -0,0 +1,34 @@ +delay = $delay; + } + + public function makesDecision() + { + return false; + } + + protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null) + { + return $this->delay; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php new file mode 100644 index 0000000..a584ed4 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/CurlBackoffStrategy.php @@ -0,0 +1,28 @@ +errorCodes[$e->getErrorNo()]) ? true : null; + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php new file mode 100644 index 0000000..fb2912d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ExponentialBackoffStrategy.php @@ -0,0 +1,25 @@ +isSuccessful()) { + return false; + } else { + return isset($this->errorCodes[$response->getStatusCode()]) ? true : null; + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php new file mode 100644 index 0000000..b35e8a4 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/LinearBackoffStrategy.php @@ -0,0 +1,36 @@ +step = $step; + } + + public function makesDecision() + { + return false; + } + + protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null) + { + return $retries * $this->step; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php new file mode 100644 index 0000000..4fd73fe --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/ReasonPhraseBackoffStrategy.php @@ -0,0 +1,25 @@ +errorCodes[$response->getReasonPhrase()]) ? true : null; + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php new file mode 100644 index 0000000..3608f35 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/TruncatedBackoffStrategy.php @@ -0,0 +1,36 @@ +max = $maxRetries; + $this->next = $next; + } + + public function makesDecision() + { + return true; + } + + protected function getDelay($retries, RequestInterface $request, Response $response = null, HttpException $e = null) + { + return $retries < $this->max ? null : false; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json new file mode 100644 index 0000000..91c122c --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Backoff/composer.json @@ -0,0 +1,28 @@ +{ + "name": "guzzle/plugin-backoff", + "description": "Guzzle backoff retry plugins", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version", + "guzzle/log": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Backoff": "" } + }, + "target-dir": "Guzzle/Plugin/Backoff", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php new file mode 100644 index 0000000..7790f88 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheKeyProviderInterface.php @@ -0,0 +1,11 @@ + new DefaultCacheStorage($options)); + } elseif ($options instanceof CacheStorageInterface) { + $options = array('storage' => $options); + } elseif ($options) { + $options = array('storage' => new DefaultCacheStorage(CacheAdapterFactory::fromCache($options))); + } elseif (!class_exists('Doctrine\Common\Cache\ArrayCache')) { + // @codeCoverageIgnoreStart + throw new InvalidArgumentException('No cache was provided and Doctrine is not installed'); + // @codeCoverageIgnoreEnd + } + } + + $this->autoPurge = isset($options['auto_purge']) ? $options['auto_purge'] : false; + + // Add a cache storage if a cache adapter was provided + $this->storage = isset($options['storage']) + ? $options['storage'] + : new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache())); + + if (!isset($options['can_cache'])) { + $this->canCache = new DefaultCanCacheStrategy(); + } else { + $this->canCache = is_callable($options['can_cache']) + ? new CallbackCanCacheStrategy($options['can_cache']) + : $options['can_cache']; + } + + // Use the provided revalidation strategy or the default + $this->revalidation = isset($options['revalidation']) + ? $options['revalidation'] + : new DefaultRevalidation($this->storage, $this->canCache); + } + + public static function getSubscribedEvents() + { + return array( + 'request.before_send' => array('onRequestBeforeSend', -255), + 'request.sent' => array('onRequestSent', 255), + 'request.error' => array('onRequestError', 0), + 'request.exception' => array('onRequestException', 0), + ); + } + + /** + * Check if a response in cache will satisfy the request before sending + * + * @param Event $event + */ + public function onRequestBeforeSend(Event $event) + { + $request = $event['request']; + $request->addHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION)); + + if (!$this->canCache->canCacheRequest($request)) { + switch ($request->getMethod()) { + case 'PURGE': + $this->purge($request); + $request->setResponse(new Response(200, array(), 'purged')); + break; + case 'PUT': + case 'POST': + case 'DELETE': + case 'PATCH': + if ($this->autoPurge) { + $this->purge($request); + } + } + return; + } + + if ($response = $this->storage->fetch($request)) { + $params = $request->getParams(); + $params['cache.lookup'] = true; + $response->setHeader( + 'Age', + time() - strtotime($response->getDate() ? : $response->getLastModified() ?: 'now') + ); + // Validate that the response satisfies the request + if ($this->canResponseSatisfyRequest($request, $response)) { + if (!isset($params['cache.hit'])) { + $params['cache.hit'] = true; + } + $request->setResponse($response); + } + } + } + + /** + * If possible, store a response in cache after sending + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + $request = $event['request']; + $response = $event['response']; + + if ($request->getParams()->get('cache.hit') === null && + $this->canCache->canCacheRequest($request) && + $this->canCache->canCacheResponse($response) + ) { + $this->storage->cache($request, $response); + } + + $this->addResponseHeaders($request, $response); + } + + /** + * If possible, return a cache response on an error + * + * @param Event $event + */ + public function onRequestError(Event $event) + { + $request = $event['request']; + + if (!$this->canCache->canCacheRequest($request)) { + return; + } + + if ($response = $this->storage->fetch($request)) { + $response->setHeader( + 'Age', + time() - strtotime($response->getLastModified() ? : $response->getDate() ?: 'now') + ); + + if ($this->canResponseSatisfyFailedRequest($request, $response)) { + $request->getParams()->set('cache.hit', 'error'); + $this->addResponseHeaders($request, $response); + $event['response'] = $response; + $event->stopPropagation(); + } + } + } + + /** + * If possible, set a cache response on a cURL exception + * + * @param Event $event + * + * @return null + */ + public function onRequestException(Event $event) + { + if (!$event['exception'] instanceof CurlException) { + return; + } + + $request = $event['request']; + if (!$this->canCache->canCacheRequest($request)) { + return; + } + + if ($response = $this->storage->fetch($request)) { + $response->setHeader('Age', time() - strtotime($response->getDate() ? : 'now')); + if (!$this->canResponseSatisfyFailedRequest($request, $response)) { + return; + } + $request->getParams()->set('cache.hit', 'error'); + $request->setResponse($response); + $this->addResponseHeaders($request, $response); + $event->stopPropagation(); + } + } + + /** + * Check if a cache response satisfies a request's caching constraints + * + * @param RequestInterface $request Request to validate + * @param Response $response Response to validate + * + * @return bool + */ + public function canResponseSatisfyRequest(RequestInterface $request, Response $response) + { + $responseAge = $response->calculateAge(); + $reqc = $request->getHeader('Cache-Control'); + $resc = $response->getHeader('Cache-Control'); + + // Check the request's max-age header against the age of the response + if ($reqc && $reqc->hasDirective('max-age') && + $responseAge > $reqc->getDirective('max-age')) { + return false; + } + + // Check the response's max-age header + if ($response->isFresh() === false) { + $maxStale = $reqc ? $reqc->getDirective('max-stale') : null; + if (null !== $maxStale) { + if ($maxStale !== true && $response->getFreshness() < (-1 * $maxStale)) { + return false; + } + } elseif ($resc && $resc->hasDirective('max-age') + && $responseAge > $resc->getDirective('max-age') + ) { + return false; + } + } + + if ($this->revalidation->shouldRevalidate($request, $response)) { + try { + return $this->revalidation->revalidate($request, $response); + } catch (CurlException $e) { + $request->getParams()->set('cache.hit', 'error'); + return $this->canResponseSatisfyFailedRequest($request, $response); + } + } + + return true; + } + + /** + * Check if a cache response satisfies a failed request's caching constraints + * + * @param RequestInterface $request Request to validate + * @param Response $response Response to validate + * + * @return bool + */ + public function canResponseSatisfyFailedRequest(RequestInterface $request, Response $response) + { + $reqc = $request->getHeader('Cache-Control'); + $resc = $response->getHeader('Cache-Control'); + $requestStaleIfError = $reqc ? $reqc->getDirective('stale-if-error') : null; + $responseStaleIfError = $resc ? $resc->getDirective('stale-if-error') : null; + + if (!$requestStaleIfError && !$responseStaleIfError) { + return false; + } + + if (is_numeric($requestStaleIfError) && $response->getAge() - $response->getMaxAge() > $requestStaleIfError) { + return false; + } + + if (is_numeric($responseStaleIfError) && $response->getAge() - $response->getMaxAge() > $responseStaleIfError) { + return false; + } + + return true; + } + + /** + * Purge all cache entries for a given URL + * + * @param string $url URL to purge + */ + public function purge($url) + { + // BC compatibility with previous version that accepted a Request object + $url = $url instanceof RequestInterface ? $url->getUrl() : $url; + $this->storage->purge($url); + } + + /** + * Add the plugin's headers to a response + * + * @param RequestInterface $request Request + * @param Response $response Response to add headers to + */ + protected function addResponseHeaders(RequestInterface $request, Response $response) + { + $params = $request->getParams(); + $response->setHeader('Via', sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION)); + + $lookup = ($params['cache.lookup'] === true ? 'HIT' : 'MISS') . ' from GuzzleCache'; + if ($header = $response->getHeader('X-Cache-Lookup')) { + // Don't add duplicates + $values = $header->toArray(); + $values[] = $lookup; + $response->setHeader('X-Cache-Lookup', array_unique($values)); + } else { + $response->setHeader('X-Cache-Lookup', $lookup); + } + + if ($params['cache.hit'] === true) { + $xcache = 'HIT from GuzzleCache'; + } elseif ($params['cache.hit'] == 'error') { + $xcache = 'HIT_ERROR from GuzzleCache'; + } else { + $xcache = 'MISS from GuzzleCache'; + } + + if ($header = $response->getHeader('X-Cache')) { + // Don't add duplicates + $values = $header->toArray(); + $values[] = $xcache; + $response->setHeader('X-Cache', array_unique($values)); + } else { + $response->setHeader('X-Cache', $xcache); + } + + if ($response->isFresh() === false) { + $response->addHeader('Warning', sprintf('110 GuzzleCache/%s "Response is stale"', Version::VERSION)); + if ($params['cache.hit'] === 'error') { + $response->addHeader('Warning', sprintf('111 GuzzleCache/%s "Revalidation failed"', Version::VERSION)); + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php new file mode 100644 index 0000000..f3d9154 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CacheStorageInterface.php @@ -0,0 +1,43 @@ +requestCallback = $requestCallback; + $this->responseCallback = $responseCallback; + } + + public function canCacheRequest(RequestInterface $request) + { + return $this->requestCallback + ? call_user_func($this->requestCallback, $request) + : parent::canCacheRequest($request); + } + + public function canCacheResponse(Response $response) + { + return $this->responseCallback + ? call_user_func($this->responseCallback, $response) + : parent::canCacheResponse($response); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php new file mode 100644 index 0000000..6e01a8e --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/CanCacheStrategyInterface.php @@ -0,0 +1,30 @@ +getParams()->get(self::CACHE_KEY); + + if (!$key) { + + $cloned = clone $request; + $cloned->removeHeader('Cache-Control'); + + // Check to see how and if the key should be filtered + foreach (explode(';', $request->getParams()->get(self::CACHE_KEY_FILTER)) as $part) { + $pieces = array_map('trim', explode('=', $part)); + if (isset($pieces[1])) { + foreach (array_map('trim', explode(',', $pieces[1])) as $remove) { + if ($pieces[0] == 'header') { + $cloned->removeHeader($remove); + } elseif ($pieces[0] == 'query') { + $cloned->getQuery()->remove($remove); + } + } + } + } + + $raw = (string) $cloned; + $key = 'GZ' . md5($raw); + $request->getParams()->set(self::CACHE_KEY, $key)->set(self::CACHE_KEY_RAW, $raw); + } + + return $key; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php new file mode 100644 index 0000000..26d7a8b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCacheStorage.php @@ -0,0 +1,266 @@ +cache = CacheAdapterFactory::fromCache($cache); + $this->defaultTtl = $defaultTtl; + $this->keyPrefix = $keyPrefix; + } + + public function cache(RequestInterface $request, Response $response) + { + $currentTime = time(); + + $overrideTtl = $request->getParams()->get('cache.override_ttl'); + if ($overrideTtl) { + $ttl = $overrideTtl; + } else { + $maxAge = $response->getMaxAge(); + if ($maxAge !== null) { + $ttl = $maxAge; + } else { + $ttl = $this->defaultTtl; + } + } + + if ($cacheControl = $response->getHeader('Cache-Control')) { + $stale = $cacheControl->getDirective('stale-if-error'); + if ($stale === true) { + $ttl += $ttl; + } else if (is_numeric($stale)) { + $ttl += $stale; + } + } + + // Determine which manifest key should be used + $key = $this->getCacheKey($request); + $persistedRequest = $this->persistHeaders($request); + $entries = array(); + + if ($manifest = $this->cache->fetch($key)) { + // Determine which cache entries should still be in the cache + $vary = $response->getVary(); + foreach (unserialize($manifest) as $entry) { + // Check if the entry is expired + if ($entry[4] < $currentTime) { + continue; + } + $entry[1]['vary'] = isset($entry[1]['vary']) ? $entry[1]['vary'] : ''; + if ($vary != $entry[1]['vary'] || !$this->requestsMatch($vary, $entry[0], $persistedRequest)) { + $entries[] = $entry; + } + } + } + + // Persist the response body if needed + $bodyDigest = null; + if ($response->getBody() && $response->getBody()->getContentLength() > 0) { + $bodyDigest = $this->getBodyKey($request->getUrl(), $response->getBody()); + $this->cache->save($bodyDigest, (string) $response->getBody(), $ttl); + } + + array_unshift($entries, array( + $persistedRequest, + $this->persistHeaders($response), + $response->getStatusCode(), + $bodyDigest, + $currentTime + $ttl + )); + + $this->cache->save($key, serialize($entries)); + } + + public function delete(RequestInterface $request) + { + $key = $this->getCacheKey($request); + if ($entries = $this->cache->fetch($key)) { + // Delete each cached body + foreach (unserialize($entries) as $entry) { + if ($entry[3]) { + $this->cache->delete($entry[3]); + } + } + $this->cache->delete($key); + } + } + + public function purge($url) + { + foreach (array('GET', 'HEAD', 'POST', 'PUT', 'DELETE') as $method) { + $this->delete(new Request($method, $url)); + } + } + + public function fetch(RequestInterface $request) + { + $key = $this->getCacheKey($request); + if (!($entries = $this->cache->fetch($key))) { + return null; + } + + $match = null; + $headers = $this->persistHeaders($request); + $entries = unserialize($entries); + foreach ($entries as $index => $entry) { + if ($this->requestsMatch(isset($entry[1]['vary']) ? $entry[1]['vary'] : '', $headers, $entry[0])) { + $match = $entry; + break; + } + } + + if (!$match) { + return null; + } + + // Ensure that the response is not expired + $response = null; + if ($match[4] < time()) { + $response = -1; + } else { + $response = new Response($match[2], $match[1]); + if ($match[3]) { + if ($body = $this->cache->fetch($match[3])) { + $response->setBody($body); + } else { + // The response is not valid because the body was somehow deleted + $response = -1; + } + } + } + + if ($response === -1) { + // Remove the entry from the metadata and update the cache + unset($entries[$index]); + if ($entries) { + $this->cache->save($key, serialize($entries)); + } else { + $this->cache->delete($key); + } + return null; + } + + return $response; + } + + /** + * Hash a request URL into a string that returns cache metadata + * + * @param RequestInterface $request + * + * @return string + */ + protected function getCacheKey(RequestInterface $request) + { + // Allow cache.key_filter to trim down the URL cache key by removing generate query string values (e.g. auth) + if ($filter = $request->getParams()->get('cache.key_filter')) { + $url = $request->getUrl(true); + foreach (explode(',', $filter) as $remove) { + $url->getQuery()->remove(trim($remove)); + } + } else { + $url = $request->getUrl(); + } + + return $this->keyPrefix . md5($request->getMethod() . ' ' . $url); + } + + /** + * Create a cache key for a response's body + * + * @param string $url URL of the entry + * @param EntityBodyInterface $body Response body + * + * @return string + */ + protected function getBodyKey($url, EntityBodyInterface $body) + { + return $this->keyPrefix . md5($url) . $body->getContentMd5(); + } + + /** + * Determines whether two Request HTTP header sets are non-varying + * + * @param string $vary Response vary header + * @param array $r1 HTTP header array + * @param array $r2 HTTP header array + * + * @return bool + */ + private function requestsMatch($vary, $r1, $r2) + { + if ($vary) { + foreach (explode(',', $vary) as $header) { + $key = trim(strtolower($header)); + $v1 = isset($r1[$key]) ? $r1[$key] : null; + $v2 = isset($r2[$key]) ? $r2[$key] : null; + if ($v1 !== $v2) { + return false; + } + } + } + + return true; + } + + /** + * Creates an array of cacheable and normalized message headers + * + * @param MessageInterface $message + * + * @return array + */ + private function persistHeaders(MessageInterface $message) + { + // Headers are excluded from the caching (see RFC 2616:13.5.1) + static $noCache = array( + 'age' => true, + 'connection' => true, + 'keep-alive' => true, + 'proxy-authenticate' => true, + 'proxy-authorization' => true, + 'te' => true, + 'trailers' => true, + 'transfer-encoding' => true, + 'upgrade' => true, + 'set-cookie' => true, + 'set-cookie2' => true + ); + + // Clone the response to not destroy any necessary headers when caching + $headers = $message->getHeaders()->getAll(); + $headers = array_diff_key($headers, $noCache); + // Cast the headers to a string + $headers = array_map(function ($h) { return (string) $h; }, $headers); + + return $headers; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php new file mode 100644 index 0000000..3ca1fbf --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultCanCacheStrategy.php @@ -0,0 +1,32 @@ +getMethod() != RequestInterface::GET && $request->getMethod() != RequestInterface::HEAD) { + return false; + } + + // Never cache requests when using no-store + if ($request->hasHeader('Cache-Control') && $request->getHeader('Cache-Control')->hasDirective('no-store')) { + return false; + } + + return true; + } + + public function canCacheResponse(Response $response) + { + return $response->isSuccessful() && $response->canCache(); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php new file mode 100644 index 0000000..af33234 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DefaultRevalidation.php @@ -0,0 +1,174 @@ +storage = $cache; + $this->canCache = $canCache ?: new DefaultCanCacheStrategy(); + } + + public function revalidate(RequestInterface $request, Response $response) + { + try { + $revalidate = $this->createRevalidationRequest($request, $response); + $validateResponse = $revalidate->send(); + if ($validateResponse->getStatusCode() == 200) { + return $this->handle200Response($request, $validateResponse); + } elseif ($validateResponse->getStatusCode() == 304) { + return $this->handle304Response($request, $validateResponse, $response); + } + } catch (BadResponseException $e) { + $this->handleBadResponse($e); + } + + // Other exceptions encountered in the revalidation request are ignored + // in hopes that sending a request to the origin server will fix it + return false; + } + + public function shouldRevalidate(RequestInterface $request, Response $response) + { + if ($request->getMethod() != RequestInterface::GET) { + return false; + } + + $reqCache = $request->getHeader('Cache-Control'); + $resCache = $response->getHeader('Cache-Control'); + + $revalidate = $request->getHeader('Pragma') == 'no-cache' || + ($reqCache && ($reqCache->hasDirective('no-cache') || $reqCache->hasDirective('must-revalidate'))) || + ($resCache && ($resCache->hasDirective('no-cache') || $resCache->hasDirective('must-revalidate'))); + + // Use the strong ETag validator if available and the response contains no Cache-Control directive + if (!$revalidate && !$resCache && $response->hasHeader('ETag')) { + $revalidate = true; + } + + return $revalidate; + } + + /** + * Handles a bad response when attempting to revalidate + * + * @param BadResponseException $e Exception encountered + * + * @throws BadResponseException + */ + protected function handleBadResponse(BadResponseException $e) + { + // 404 errors mean the resource no longer exists, so remove from + // cache, and prevent an additional request by throwing the exception + if ($e->getResponse()->getStatusCode() == 404) { + $this->storage->delete($e->getRequest()); + throw $e; + } + } + + /** + * Creates a request to use for revalidation + * + * @param RequestInterface $request Request + * @param Response $response Response to revalidate + * + * @return RequestInterface returns a revalidation request + */ + protected function createRevalidationRequest(RequestInterface $request, Response $response) + { + $revalidate = clone $request; + $revalidate->removeHeader('Pragma')->removeHeader('Cache-Control'); + + if ($response->getLastModified()) { + $revalidate->setHeader('If-Modified-Since', $response->getLastModified()); + } + + if ($response->getEtag()) { + $revalidate->setHeader('If-None-Match', $response->getEtag()); + } + + // Remove any cache plugins that might be on the request to prevent infinite recursive revalidations + $dispatcher = $revalidate->getEventDispatcher(); + foreach ($dispatcher->getListeners() as $eventName => $listeners) { + foreach ($listeners as $listener) { + if (is_array($listener) && $listener[0] instanceof CachePlugin) { + $dispatcher->removeListener($eventName, $listener); + } + } + } + + return $revalidate; + } + + /** + * Handles a 200 response response from revalidating. The server does not support validation, so use this response. + * + * @param RequestInterface $request Request that was sent + * @param Response $validateResponse Response received + * + * @return bool Returns true if valid, false if invalid + */ + protected function handle200Response(RequestInterface $request, Response $validateResponse) + { + $request->setResponse($validateResponse); + if ($this->canCache->canCacheResponse($validateResponse)) { + $this->storage->cache($request, $validateResponse); + } + + return false; + } + + /** + * Handle a 304 response and ensure that it is still valid + * + * @param RequestInterface $request Request that was sent + * @param Response $validateResponse Response received + * @param Response $response Original cached response + * + * @return bool Returns true if valid, false if invalid + */ + protected function handle304Response(RequestInterface $request, Response $validateResponse, Response $response) + { + static $replaceHeaders = array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified'); + + // Make sure that this response has the same ETag + if ($validateResponse->getEtag() != $response->getEtag()) { + return false; + } + + // Replace cached headers with any of these headers from the + // origin server that might be more up to date + $modified = false; + foreach ($replaceHeaders as $name) { + if ($validateResponse->hasHeader($name)) { + $modified = true; + $response->setHeader($name, $validateResponse->getHeader($name)); + } + } + + // Store the updated response in cache + if ($modified && $this->canCache->canCacheResponse($response)) { + $this->storage->cache($request, $response); + } + + return true; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php new file mode 100644 index 0000000..88b86f3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cache/DenyRevalidation.php @@ -0,0 +1,19 @@ +=5.3.2", + "guzzle/http": "self.version", + "guzzle/cache": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Cache": "" } + }, + "target-dir": "Guzzle/Plugin/Cache", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php new file mode 100644 index 0000000..5218e5f --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Cookie.php @@ -0,0 +1,538 @@ + '', + 'value' => '', + 'domain' => '', + 'path' => '/', + 'expires' => null, + 'max_age' => 0, + 'comment' => null, + 'comment_url' => null, + 'port' => array(), + 'version' => null, + 'secure' => false, + 'discard' => false, + 'http_only' => false + ); + + $this->data = array_merge($defaults, $data); + // 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() + (int) $this->getMaxAge()); + } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires(strtotime($this->getExpires())); + } + } + + /** + * Get the cookie as an array + * + * @return array + */ + public function toArray() + { + 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 + * + * @return Cookie + */ + public function setName($name) + { + return $this->setData('name', $name); + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + * + * @return Cookie + */ + public function setValue($value) + { + return $this->setData('value', $value); + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + * + * @return Cookie + */ + public function setDomain($domain) + { + return $this->setData('domain', $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 + * + * @return Cookie + */ + public function setPath($path) + { + return $this->setData('path', $path); + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['max_age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + * + * @return Cookie + */ + public function setMaxAge($maxAge) + { + return $this->setData('max_age', $maxAge); + } + + /** + * The UNIX timestamp when the cookie expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + * + * @return Cookie + */ + public function setExpires($timestamp) + { + return $this->setData('expires', $timestamp); + } + + /** + * Version of the cookie specification. RFC 2965 is 1 + * + * @return mixed + */ + public function getVersion() + { + return $this->data['version']; + } + + /** + * Set the cookie version + * + * @param string|int $version Version to set + * + * @return Cookie + */ + public function setVersion($version) + { + return $this->setData('version', $version); + } + + /** + * Get whether or not this is a secure cookie + * + * @return null|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 + * + * @return Cookie + */ + public function setSecure($secure) + { + return $this->setData('secure', (bool) $secure); + } + + /** + * Get whether or not this is a session cookie + * + * @return null|bool + */ + 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 + * + * @return Cookie + */ + public function setDiscard($discard) + { + return $this->setData('discard', $discard); + } + + /** + * Get the comment + * + * @return string|null + */ + public function getComment() + { + return $this->data['comment']; + } + + /** + * Set the comment of the cookie + * + * @param string $comment Cookie comment + * + * @return Cookie + */ + public function setComment($comment) + { + return $this->setData('comment', $comment); + } + + /** + * Get the comment URL of the cookie + * + * @return string|null + */ + public function getCommentUrl() + { + return $this->data['comment_url']; + } + + /** + * Set the comment URL of the cookie + * + * @param string $commentUrl Cookie comment URL for more information + * + * @return Cookie + */ + public function setCommentUrl($commentUrl) + { + return $this->setData('comment_url', $commentUrl); + } + + /** + * Get an array of acceptable ports this cookie can be used with + * + * @return array + */ + public function getPorts() + { + return $this->data['port']; + } + + /** + * Set a list of acceptable ports this cookie can be used with + * + * @param array $ports Array of acceptable ports + * + * @return Cookie + */ + public function setPorts(array $ports) + { + return $this->setData('port', $ports); + } + + /** + * Get whether or not this is an HTTP only cookie + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['http_only']; + } + + /** + * Set whether or not this is an HTTP only cookie + * + * @param bool $httpOnly Set to true or false if this is HTTP only + * + * @return Cookie + */ + public function setHttpOnly($httpOnly) + { + return $this->setData('http_only', $httpOnly); + } + + /** + * Get an array of extra cookie data + * + * @return array + */ + public function getAttributes() + { + return $this->data['data']; + } + + /** + * Get a specific data point from the extra cookie data + * + * @param string $name Name of the data point to retrieve + * + * @return null|string + */ + public function getAttribute($name) + { + return array_key_exists($name, $this->data['data']) ? $this->data['data'][$name] : null; + } + + /** + * Set a cookie data attribute + * + * @param string $name Name of the attribute to set + * @param string $value Value to set + * + * @return Cookie + */ + public function setAttribute($name, $value) + { + $this->data['data'][$name] = $value; + + return $this; + } + + /** + * Check if the cookie matches a path value + * + * @param string $path Path to check against + * + * @return bool + */ + public function matchesPath($path) + { + // RFC6265 http://tools.ietf.org/search/rfc6265#section-5.1.4 + // A request-path path-matches a given cookie-path if at least one of + // the following conditions holds: + + // o The cookie-path and the request-path are identical. + if ($path == $this->getPath()) { + return true; + } + + $pos = stripos($path, $this->getPath()); + if ($pos === 0) { + // o The cookie-path is a prefix of the request-path, and the last + // character of the cookie-path is %x2F ("/"). + if (substr($this->getPath(), -1, 1) === "/") { + return true; + } + + // o 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. + if (substr($path, strlen($this->getPath()), 1) === "/") { + return true; + } + } + + return false; + } + + /** + * Check if the cookie matches a domain value + * + * @param string $domain Domain to check against + * + * @return bool + */ + public function matchesDomain($domain) + { + // Remove the leading '.' as per spec in RFC 6265: http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265: http://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, '/') . '$/i', $domain); + } + + /** + * Check if the cookie is compatible with a specific port + * + * @param int $port Port to check + * + * @return bool + */ + public function matchesPort($port) + { + return count($this->getPorts()) == 0 || in_array($port, $this->getPorts()); + } + + /** + * Check if the cookie is expired + * + * @return bool + */ + public function isExpired() + { + return $this->getExpires() && 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() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($name)) { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (strpbrk($name, self::getInvalidCharacters()) !== false) { + return 'The cookie name must not contain invalid characters: ' . $name; + } + + // Value must not be empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name in a private network + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } + + /** + * Set a value and return the cookie object + * + * @param string $key Key to set + * @param string $value Value to set + * + * @return Cookie + */ + private function setData($key, $value) + { + $this->data[$key] = $value; + + return $this; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php new file mode 100644 index 0000000..6b67503 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/ArrayCookieJar.php @@ -0,0 +1,237 @@ +strictMode = $strictMode; + } + + /** + * Enable or disable strict mode on the cookie jar + * + * @param bool $strictMode Set to true to throw exceptions when invalid cookies are added. False to ignore them. + * + * @return self + */ + public function setStrictMode($strictMode) + { + $this->strictMode = $strictMode; + } + + public function remove($domain = null, $path = null, $name = null) + { + $cookies = $this->all($domain, $path, $name, false, false); + $this->cookies = array_filter($this->cookies, function (Cookie $cookie) use ($cookies) { + return !in_array($cookie, $cookies, true); + }); + + return $this; + } + + public function removeTemporary() + { + $this->cookies = array_filter($this->cookies, function (Cookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + }); + + return $this; + } + + public function removeExpired() + { + $currentTime = time(); + $this->cookies = array_filter($this->cookies, function (Cookie $cookie) use ($currentTime) { + return !$cookie->getExpires() || $currentTime < $cookie->getExpires(); + }); + + return $this; + } + + public function all($domain = null, $path = null, $name = null, $skipDiscardable = false, $skipExpired = true) + { + return array_values(array_filter($this->cookies, function (Cookie $cookie) use ( + $domain, + $path, + $name, + $skipDiscardable, + $skipExpired + ) { + return false === (($name && $cookie->getName() != $name) || + ($skipExpired && $cookie->isExpired()) || + ($skipDiscardable && ($cookie->getDiscard() || !$cookie->getExpires())) || + ($path && !$cookie->matchesPath($path)) || + ($domain && !$cookie->matchesDomain($domain))); + })); + } + + public function add(Cookie $cookie) + { + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new InvalidCookieException($result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, domain, port and name are identical + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getPorts() != $cookie->getPorts() || + $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; + } + + /** + * Serializes the cookie cookieJar + * + * @return string + */ + public function serialize() + { + // Only serialize long term cookies and unexpired cookies + return json_encode(array_map(function (Cookie $cookie) { + return $cookie->toArray(); + }, $this->all(null, null, null, true, true))); + } + + /** + * Unserializes the cookie cookieJar + */ + public function unserialize($data) + { + $data = json_decode($data, true); + if (empty($data)) { + $this->cookies = array(); + } else { + $this->cookies = array_map(function (array $cookie) { + return new Cookie($cookie); + }, $data); + } + } + + /** + * Returns the total number of stored cookies + * + * @return int + */ + public function count() + { + return count($this->cookies); + } + + /** + * Returns an iterator + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->cookies); + } + + public function addCookiesFromResponse(Response $response, RequestInterface $request = null) + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + $parser = ParserRegistry::getInstance()->getParser('cookie'); + foreach ($cookieHeader as $cookie) { + if ($parsed = $request + ? $parser->parseCookie($cookie, $request->getHost(), $request->getPath()) + : $parser->parseCookie($cookie) + ) { + // Break up cookie v2 into multiple cookies + foreach ($parsed['cookies'] as $key => $value) { + $row = $parsed; + $row['name'] = $key; + $row['value'] = $value; + unset($row['cookies']); + $this->add(new Cookie($row)); + } + } + } + } + } + + public function getMatchingCookies(RequestInterface $request) + { + // Find cookies that match this request + $cookies = $this->all($request->getHost(), $request->getPath()); + // Remove ineligible cookies + foreach ($cookies as $index => $cookie) { + if (!$cookie->matchesPort($request->getPort()) || ($cookie->getSecure() && $request->getScheme() != 'https')) { + unset($cookies[$index]); + } + }; + + return $cookies; + } + + /** + * If a cookie already exists and the server asks to set it again with a null value, the + * cookie must be deleted. + * + * @param \Guzzle\Plugin\Cookie\Cookie $cookie + */ + private function removeCookieIfEmpty(Cookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->remove($cookie->getDomain(), $cookie->getPath(), $cookie->getName()); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php new file mode 100644 index 0000000..7faa7d2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookieJar/CookieJarInterface.php @@ -0,0 +1,85 @@ +filename = $cookieFile; + $this->load(); + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->persist(); + } + + /** + * Save the contents of the data array to the file + * + * @throws RuntimeException if the file cannot be found or created + */ + protected function persist() + { + if (false === file_put_contents($this->filename, $this->serialize())) { + // @codeCoverageIgnoreStart + throw new RuntimeException('Unable to open file ' . $this->filename); + // @codeCoverageIgnoreEnd + } + } + + /** + * Load the contents of the json formatted file into the data array and discard any unsaved state + */ + protected function load() + { + $json = file_get_contents($this->filename); + if (false === $json) { + // @codeCoverageIgnoreStart + throw new RuntimeException('Unable to open file ' . $this->filename); + // @codeCoverageIgnoreEnd + } + + $this->unserialize($json); + $this->cookies = $this->cookies ?: array(); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php new file mode 100644 index 0000000..df3210e --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/CookiePlugin.php @@ -0,0 +1,70 @@ +cookieJar = $cookieJar ?: new ArrayCookieJar(); + } + + public static function getSubscribedEvents() + { + return array( + 'request.before_send' => array('onRequestBeforeSend', 125), + 'request.sent' => array('onRequestSent', 125) + ); + } + + /** + * Get the cookie cookieJar + * + * @return CookieJarInterface + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + /** + * Add cookies before a request is sent + * + * @param Event $event + */ + public function onRequestBeforeSend(Event $event) + { + $request = $event['request']; + if (!$request->getParams()->get('cookies.disable')) { + $request->removeHeader('Cookie'); + // Find cookies that match this request + foreach ($this->cookieJar->getMatchingCookies($request) as $cookie) { + $request->addCookie($cookie->getName(), $cookie->getValue()); + } + } + } + + /** + * Extract cookies from a sent request + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + $this->cookieJar->addCookiesFromResponse($event['response'], $event['request']); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php new file mode 100644 index 0000000..b1fa6fd --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Cookie/Exception/InvalidCookieException.php @@ -0,0 +1,7 @@ +=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Cookie": "" } + }, + "target-dir": "Guzzle/Plugin/Cookie", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php new file mode 100644 index 0000000..610e60c --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/CurlAuthPlugin.php @@ -0,0 +1,46 @@ +getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest'); + */ +class CurlAuthPlugin implements EventSubscriberInterface +{ + private $username; + private $password; + private $scheme; + + /** + * @param string $username HTTP basic auth username + * @param string $password Password + * @param int $scheme Curl auth scheme + */ + public function __construct($username, $password, $scheme=CURLAUTH_BASIC) + { + Version::warn(__CLASS__ . " is deprecated. Use \$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');"); + $this->username = $username; + $this->password = $password; + $this->scheme = $scheme; + } + + public static function getSubscribedEvents() + { + return array('client.create_request' => array('onRequestCreate', 255)); + } + + /** + * Add basic auth + * + * @param Event $event + */ + public function onRequestCreate(Event $event) + { + $event['request']->setAuth($this->username, $this->password, $this->scheme); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json new file mode 100644 index 0000000..edc8b24 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/CurlAuth/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-curlauth", + "description": "Guzzle cURL authorization plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "curl", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\CurlAuth": "" } + }, + "target-dir": "Guzzle/Plugin/CurlAuth", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php new file mode 100644 index 0000000..5dce8bd --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/ErrorResponseExceptionInterface.php @@ -0,0 +1,22 @@ + array('onCommandBeforeSend', -1)); + } + + /** + * Adds a listener to requests before they sent from a command + * + * @param Event $event Event emitted + */ + public function onCommandBeforeSend(Event $event) + { + $command = $event['command']; + if ($operation = $command->getOperation()) { + if ($operation->getErrorResponses()) { + $request = $command->getRequest(); + $request->getEventDispatcher() + ->addListener('request.complete', $this->getErrorClosure($request, $command, $operation)); + } + } + } + + /** + * @param RequestInterface $request Request that received an error + * @param CommandInterface $command Command that created the request + * @param Operation $operation Operation that defines the request and errors + * + * @return \Closure Returns a closure + * @throws ErrorResponseException + */ + protected function getErrorClosure(RequestInterface $request, CommandInterface $command, Operation $operation) + { + return function (Event $event) use ($request, $command, $operation) { + $response = $event['response']; + foreach ($operation->getErrorResponses() as $error) { + if (!isset($error['class'])) { + continue; + } + if (isset($error['code']) && $response->getStatusCode() != $error['code']) { + continue; + } + if (isset($error['reason']) && $response->getReasonPhrase() != $error['reason']) { + continue; + } + $className = $error['class']; + $errorClassInterface = __NAMESPACE__ . '\\ErrorResponseExceptionInterface'; + if (!class_exists($className)) { + throw new ErrorResponseException("{$className} does not exist"); + } elseif (!(in_array($errorClassInterface, class_implements($className)))) { + throw new ErrorResponseException("{$className} must implement {$errorClassInterface}"); + } + throw $className::fromCommand($command, $response); + } + }; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php new file mode 100644 index 0000000..1d89e40 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/ErrorResponse/Exception/ErrorResponseException.php @@ -0,0 +1,7 @@ +=5.3.2", + "guzzle/service": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\ErrorResponse": "" } + }, + "target-dir": "Guzzle/Plugin/ErrorResponse", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php new file mode 100644 index 0000000..7375e89 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/HistoryPlugin.php @@ -0,0 +1,163 @@ + array('onRequestSent', 9999)); + } + + /** + * Convert to a string that contains all request and response headers + * + * @return string + */ + public function __toString() + { + $lines = array(); + foreach ($this->transactions as $entry) { + $response = isset($entry['response']) ? $entry['response'] : ''; + $lines[] = '> ' . trim($entry['request']) . "\n\n< " . trim($response) . "\n"; + } + + return implode("\n", $lines); + } + + /** + * Add a request to the history + * + * @param RequestInterface $request Request to add + * @param Response $response Response of the request + * + * @return HistoryPlugin + */ + public function add(RequestInterface $request, Response $response = null) + { + if (!$response && $request->getResponse()) { + $response = $request->getResponse(); + } + + $this->transactions[] = array('request' => $request, 'response' => $response); + if (count($this->transactions) > $this->getlimit()) { + array_shift($this->transactions); + } + + return $this; + } + + /** + * Set the max number of requests to store + * + * @param int $limit Limit + * + * @return HistoryPlugin + */ + public function setLimit($limit) + { + $this->limit = (int) $limit; + + return $this; + } + + /** + * Get the request limit + * + * @return int + */ + public function getLimit() + { + return $this->limit; + } + + /** + * Get all of the raw transactions in the form of an array of associative arrays containing + * 'request' and 'response' keys. + * + * @return array + */ + public function getAll() + { + return $this->transactions; + } + + /** + * Get the requests in the history + * + * @return \ArrayIterator + */ + public function getIterator() + { + // Return an iterator just like the old iteration of the HistoryPlugin for BC compatibility (use getAll()) + return new \ArrayIterator(array_map(function ($entry) { + $entry['request']->getParams()->set('actual_response', $entry['response']); + return $entry['request']; + }, $this->transactions)); + } + + /** + * Get the number of requests in the history + * + * @return int + */ + public function count() + { + return count($this->transactions); + } + + /** + * Get the last request sent + * + * @return RequestInterface + */ + public function getLastRequest() + { + $last = end($this->transactions); + + return $last['request']; + } + + /** + * Get the last response in the history + * + * @return Response|null + */ + public function getLastResponse() + { + $last = end($this->transactions); + + return isset($last['response']) ? $last['response'] : null; + } + + /** + * Clears the history + * + * @return HistoryPlugin + */ + public function clear() + { + $this->transactions = array(); + + return $this; + } + + public function onRequestSent(Event $event) + { + $this->add($event['request'], $event['response']); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json new file mode 100644 index 0000000..ba0bf2c --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/History/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-history", + "description": "Guzzle history plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\History": "" } + }, + "target-dir": "Guzzle/Plugin/History", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php new file mode 100644 index 0000000..cabdea8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/LogPlugin.php @@ -0,0 +1,161 @@ +logAdapter = $logAdapter; + $this->formatter = $formatter instanceof MessageFormatter ? $formatter : new MessageFormatter($formatter); + $this->wireBodies = $wireBodies; + } + + /** + * Get a log plugin that outputs full request, response, and curl error information to stderr + * + * @param bool $wireBodies Set to false to disable request/response body output when they use are not repeatable + * @param resource $stream Stream to write to when logging. Defaults to STDERR when it is available + * + * @return self + */ + public static function getDebugPlugin($wireBodies = true, $stream = null) + { + if ($stream === null) { + if (defined('STDERR')) { + $stream = STDERR; + } else { + $stream = fopen('php://output', 'w'); + } + } + + return new self(new ClosureLogAdapter(function ($m) use ($stream) { + fwrite($stream, $m . PHP_EOL); + }), "# Request:\n{request}\n\n# Response:\n{response}\n\n# Errors: {curl_code} {curl_error}", $wireBodies); + } + + public static function getSubscribedEvents() + { + return array( + 'curl.callback.write' => array('onCurlWrite', 255), + 'curl.callback.read' => array('onCurlRead', 255), + 'request.before_send' => array('onRequestBeforeSend', 255), + 'request.sent' => array('onRequestSent', 255) + ); + } + + /** + * Event triggered when curl data is read from a request + * + * @param Event $event + */ + public function onCurlRead(Event $event) + { + // Stream the request body to the log if the body is not repeatable + if ($wire = $event['request']->getParams()->get('request_wire')) { + $wire->write($event['read']); + } + } + + /** + * Event triggered when curl data is written to a response + * + * @param Event $event + */ + public function onCurlWrite(Event $event) + { + // Stream the response body to the log if the body is not repeatable + if ($wire = $event['request']->getParams()->get('response_wire')) { + $wire->write($event['write']); + } + } + + /** + * Called before a request is sent + * + * @param Event $event + */ + public function onRequestBeforeSend(Event $event) + { + if ($this->wireBodies) { + $request = $event['request']; + // Ensure that curl IO events are emitted + $request->getCurlOptions()->set('emit_io', true); + // We need to make special handling for content wiring and non-repeatable streams. + if ($request instanceof EntityEnclosingRequestInterface && $request->getBody() + && (!$request->getBody()->isSeekable() || !$request->getBody()->isReadable()) + ) { + // The body of the request cannot be recalled so logging the body will require us to buffer it + $request->getParams()->set('request_wire', EntityBody::factory()); + } + if (!$request->getResponseBody()->isRepeatable()) { + // The body of the response cannot be recalled so logging the body will require us to buffer it + $request->getParams()->set('response_wire', EntityBody::factory()); + } + } + } + + /** + * Triggers the actual log write when a request completes + * + * @param Event $event + */ + public function onRequestSent(Event $event) + { + $request = $event['request']; + $response = $event['response']; + $handle = $event['handle']; + + if ($wire = $request->getParams()->get('request_wire')) { + $request = clone $request; + $request->setBody($wire); + } + + if ($wire = $request->getParams()->get('response_wire')) { + $response = clone $response; + $response->setBody($wire); + } + + // Send the log message to the adapter, adding a category and host + $priority = $response && $response->isError() ? LOG_ERR : LOG_DEBUG; + $message = $this->formatter->format($request, $response, $handle); + $this->logAdapter->log($message, $priority, array( + 'request' => $request, + 'response' => $response, + 'handle' => $handle + )); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json new file mode 100644 index 0000000..130e6da --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Log/composer.json @@ -0,0 +1,28 @@ +{ + "name": "guzzle/plugin-log", + "description": "Guzzle log plugin for over the wire logging", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "log", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version", + "guzzle/log": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Log": "" } + }, + "target-dir": "Guzzle/Plugin/Log", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php new file mode 100644 index 0000000..8512424 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/CommandContentMd5Plugin.php @@ -0,0 +1,57 @@ +contentMd5Param = $contentMd5Param; + $this->validateMd5Param = $validateMd5Param; + } + + public static function getSubscribedEvents() + { + return array('command.before_send' => array('onCommandBeforeSend', -255)); + } + + public function onCommandBeforeSend(Event $event) + { + $command = $event['command']; + $request = $command->getRequest(); + + // Only add an MD5 is there is a MD5 option on the operation and it has a payload + if ($request instanceof EntityEnclosingRequestInterface && $request->getBody() + && $command->getOperation()->hasParam($this->contentMd5Param)) { + // Check if an MD5 checksum value should be passed along to the request + if ($command[$this->contentMd5Param] === true) { + if (false !== ($md5 = $request->getBody()->getContentMd5(true, true))) { + $request->setHeader('Content-MD5', $md5); + } + } + } + + // Check if MD5 validation should be used with the response + if ($command[$this->validateMd5Param] === true) { + $request->addSubscriber(new Md5ValidatorPlugin(true, false)); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php new file mode 100644 index 0000000..5d7a378 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/Md5ValidatorPlugin.php @@ -0,0 +1,88 @@ +contentLengthCutoff = $contentLengthCutoff; + $this->contentEncoded = $contentEncoded; + } + + public static function getSubscribedEvents() + { + return array('request.complete' => array('onRequestComplete', 255)); + } + + /** + * {@inheritdoc} + * @throws UnexpectedValueException + */ + public function onRequestComplete(Event $event) + { + $response = $event['response']; + + if (!$contentMd5 = $response->getContentMd5()) { + return; + } + + $contentEncoding = $response->getContentEncoding(); + if ($contentEncoding && !$this->contentEncoded) { + return false; + } + + // Make sure that the size of the request is under the cutoff size + if ($this->contentLengthCutoff) { + $size = $response->getContentLength() ?: $response->getBody()->getSize(); + if (!$size || $size > $this->contentLengthCutoff) { + return; + } + } + + if (!$contentEncoding) { + $hash = $response->getBody()->getContentMd5(); + } elseif ($contentEncoding == 'gzip') { + $response->getBody()->compress('zlib.deflate'); + $hash = $response->getBody()->getContentMd5(); + $response->getBody()->uncompress(); + } elseif ($contentEncoding == 'compress') { + $response->getBody()->compress('bzip2.compress'); + $hash = $response->getBody()->getContentMd5(); + $response->getBody()->uncompress(); + } else { + return; + } + + if ($contentMd5 !== $hash) { + throw new UnexpectedValueException( + "The response entity body may have been modified over the wire. The Content-MD5 " + . "received ({$contentMd5}) did not match the calculated MD5 hash ({$hash})." + ); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json new file mode 100644 index 0000000..0602d06 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Md5/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-md5", + "description": "Guzzle MD5 plugins", + "homepage": "http://guzzlephp.org/", + "keywords": ["plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Md5": "" } + }, + "target-dir": "Guzzle/Plugin/Md5", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php new file mode 100644 index 0000000..2440578 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/MockPlugin.php @@ -0,0 +1,245 @@ +readBodies = $readBodies; + $this->temporary = $temporary; + if ($items) { + foreach ($items as $item) { + if ($item instanceof \Exception) { + $this->addException($item); + } else { + $this->addResponse($item); + } + } + } + } + + public static function getSubscribedEvents() + { + // Use a number lower than the CachePlugin + return array('request.before_send' => array('onRequestBeforeSend', -999)); + } + + public static function getAllEvents() + { + return array('mock.request'); + } + + /** + * Get a mock response from a file + * + * @param string $path File to retrieve a mock response from + * + * @return Response + * @throws InvalidArgumentException if the file is not found + */ + public static function getMockFile($path) + { + if (!file_exists($path)) { + throw new InvalidArgumentException('Unable to open mock file: ' . $path); + } + + return Response::fromMessage(file_get_contents($path)); + } + + /** + * Set whether or not to consume the entity body of a request when a mock + * response is used + * + * @param bool $readBodies Set to true to read and consume entity bodies + * + * @return self + */ + public function readBodies($readBodies) + { + $this->readBodies = $readBodies; + + return $this; + } + + /** + * Returns the number of remaining mock responses + * + * @return int + */ + public function count() + { + return count($this->queue); + } + + /** + * Add a response to the end of the queue + * + * @param string|Response $response Response object or path to response file + * + * @return MockPlugin + * @throws InvalidArgumentException if a string or Response is not passed + */ + public function addResponse($response) + { + if (!($response instanceof Response)) { + if (!is_string($response)) { + throw new InvalidArgumentException('Invalid response'); + } + $response = self::getMockFile($response); + } + + $this->queue[] = $response; + + return $this; + } + + /** + * Add an exception to the end of the queue + * + * @param CurlException $e Exception to throw when the request is executed + * + * @return MockPlugin + */ + public function addException(CurlException $e) + { + $this->queue[] = $e; + + return $this; + } + + /** + * Clear the queue + * + * @return MockPlugin + */ + public function clearQueue() + { + $this->queue = array(); + + return $this; + } + + /** + * Returns an array of mock responses remaining in the queue + * + * @return array + */ + public function getQueue() + { + return $this->queue; + } + + /** + * Check if this is a temporary plugin + * + * @return bool + */ + public function isTemporary() + { + return $this->temporary; + } + + /** + * Get a response from the front of the list and add it to a request + * + * @param RequestInterface $request Request to mock + * + * @return self + * @throws CurlException When request.send is called and an exception is queued + */ + public function dequeue(RequestInterface $request) + { + $this->dispatch('mock.request', array('plugin' => $this, 'request' => $request)); + + $item = array_shift($this->queue); + if ($item instanceof Response) { + if ($this->readBodies && $request instanceof EntityEnclosingRequestInterface) { + $request->getEventDispatcher()->addListener('request.sent', $f = function (Event $event) use (&$f) { + while ($data = $event['request']->getBody()->read(8096)); + // Remove the listener after one-time use + $event['request']->getEventDispatcher()->removeListener('request.sent', $f); + }); + } + $request->setResponse($item); + } elseif ($item instanceof CurlException) { + // Emulates exceptions encountered while transferring requests + $item->setRequest($request); + $state = $request->setState(RequestInterface::STATE_ERROR, array('exception' => $item)); + // Only throw if the exception wasn't handled + if ($state == RequestInterface::STATE_ERROR) { + throw $item; + } + } + + return $this; + } + + /** + * Clear the array of received requests + */ + public function flush() + { + $this->received = array(); + } + + /** + * Get an array of requests that were mocked by this plugin + * + * @return array + */ + public function getReceivedRequests() + { + return $this->received; + } + + /** + * Called when a request is about to be sent + * + * @param Event $event + * @throws \OutOfBoundsException When queue is empty + */ + public function onRequestBeforeSend(Event $event) + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + $request = $event['request']; + $this->received[] = $request; + // Detach the filter from the client so it's a one-time use + if ($this->temporary && count($this->queue) == 1 && $request->getClient()) { + $request->getClient()->getEventDispatcher()->removeSubscriber($this); + } + $this->dequeue($request); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json new file mode 100644 index 0000000..f8201e3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Mock/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-mock", + "description": "Guzzle Mock plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["mock", "plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Mock": "" } + }, + "target-dir": "Guzzle/Plugin/Mock", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php new file mode 100644 index 0000000..95e0c3e --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/OauthPlugin.php @@ -0,0 +1,306 @@ +config = Collection::fromConfig($config, array( + 'version' => '1.0', + 'request_method' => self::REQUEST_METHOD_HEADER, + 'consumer_key' => 'anonymous', + 'consumer_secret' => 'anonymous', + 'signature_method' => 'HMAC-SHA1', + 'signature_callback' => function($stringToSign, $key) { + return hash_hmac('sha1', $stringToSign, $key, true); + } + ), array( + 'signature_method', 'signature_callback', 'version', + 'consumer_key', 'consumer_secret' + )); + } + + public static function getSubscribedEvents() + { + return array( + 'request.before_send' => array('onRequestBeforeSend', -1000) + ); + } + + /** + * Request before-send event handler + * + * @param Event $event Event received + * @return array + * @throws \InvalidArgumentException + */ + public function onRequestBeforeSend(Event $event) + { + $timestamp = $this->getTimestamp($event); + $request = $event['request']; + $nonce = $this->generateNonce($request); + $authorizationParams = $this->getOauthParams($timestamp, $nonce); + $authorizationParams['oauth_signature'] = $this->getSignature($request, $timestamp, $nonce); + + switch ($this->config['request_method']) { + case self::REQUEST_METHOD_HEADER: + $request->setHeader( + 'Authorization', + $this->buildAuthorizationHeader($authorizationParams) + ); + break; + case self::REQUEST_METHOD_QUERY: + foreach ($authorizationParams as $key => $value) { + $request->getQuery()->set($key, $value); + } + break; + default: + throw new \InvalidArgumentException(sprintf( + 'Invalid consumer method "%s"', + $this->config['request_method'] + )); + } + + return $authorizationParams; + } + + /** + * Builds the Authorization header for a request + * + * @param array $authorizationParams Associative array of authorization parameters + * + * @return string + */ + private function buildAuthorizationHeader($authorizationParams) + { + $authorizationString = 'OAuth '; + foreach ($authorizationParams as $key => $val) { + if ($val) { + $authorizationString .= $key . '="' . urlencode($val) . '", '; + } + } + + return substr($authorizationString, 0, -2); + } + + /** + * Calculate signature for request + * + * @param RequestInterface $request Request to generate a signature for + * @param integer $timestamp Timestamp to use for nonce + * @param string $nonce + * + * @return string + */ + public function getSignature(RequestInterface $request, $timestamp, $nonce) + { + $string = $this->getStringToSign($request, $timestamp, $nonce); + $key = urlencode($this->config['consumer_secret']) . '&' . urlencode($this->config['token_secret']); + + return base64_encode(call_user_func($this->config['signature_callback'], $string, $key)); + } + + /** + * Calculate string to sign + * + * @param RequestInterface $request Request to generate a signature for + * @param int $timestamp Timestamp to use for nonce + * @param string $nonce + * + * @return string + */ + public function getStringToSign(RequestInterface $request, $timestamp, $nonce) + { + $params = $this->getParamsToSign($request, $timestamp, $nonce); + + // Convert booleans to strings. + $params = $this->prepareParameters($params); + + // Build signing string from combined params + $parameterString = clone $request->getQuery(); + $parameterString->replace($params); + + $url = Url::factory($request->getUrl())->setQuery('')->setFragment(null); + + return strtoupper($request->getMethod()) . '&' + . rawurlencode($url) . '&' + . rawurlencode((string) $parameterString); + } + + /** + * Get the oauth parameters as named by the oauth spec + * + * @param $timestamp + * @param $nonce + * @return Collection + */ + protected function getOauthParams($timestamp, $nonce) + { + $params = new Collection(array( + 'oauth_consumer_key' => $this->config['consumer_key'], + 'oauth_nonce' => $nonce, + 'oauth_signature_method' => $this->config['signature_method'], + 'oauth_timestamp' => $timestamp, + )); + + // Optional parameters should not be set if they have not been set in the config as + // the parameter may be considered invalid by the Oauth service. + $optionalParams = array( + 'callback' => 'oauth_callback', + 'token' => 'oauth_token', + 'verifier' => 'oauth_verifier', + 'version' => 'oauth_version' + ); + + foreach ($optionalParams as $optionName => $oauthName) { + if (isset($this->config[$optionName]) == true) { + $params[$oauthName] = $this->config[$optionName]; + } + } + + return $params; + } + + /** + * Get all of the parameters required to sign a request including: + * * The oauth params + * * The request GET params + * * The params passed in the POST body (with a content-type of application/x-www-form-urlencoded) + * + * @param RequestInterface $request Request to generate a signature for + * @param integer $timestamp Timestamp to use for nonce + * @param string $nonce + * + * @return array + */ + public function getParamsToSign(RequestInterface $request, $timestamp, $nonce) + { + $params = $this->getOauthParams($timestamp, $nonce); + + // Add query string parameters + $params->merge($request->getQuery()); + + // Add POST fields to signing string if required + if ($this->shouldPostFieldsBeSigned($request)) + { + $params->merge($request->getPostFields()); + } + + // Sort params + $params = $params->toArray(); + uksort($params, 'strcmp'); + + return $params; + } + + /** + * Decide whether the post fields should be added to the base string that Oauth signs. + * This implementation is correct. Non-conformant APIs may require that this method be + * overwritten e.g. the Flickr API incorrectly adds the post fields when the Content-Type + * is 'application/x-www-form-urlencoded' + * + * @param $request + * @return bool Whether the post fields should be signed or not + */ + public function shouldPostFieldsBeSigned($request) + { + if (!$this->config->get('disable_post_params') && + $request instanceof EntityEnclosingRequestInterface && + false !== strpos($request->getHeader('Content-Type'), 'application/x-www-form-urlencoded')) + { + return true; + } + + return false; + } + + /** + * Returns a Nonce Based on the unique id and URL. This will allow for multiple requests in parallel with the same + * exact timestamp to use separate nonce's. + * + * @param RequestInterface $request Request to generate a nonce for + * + * @return string + */ + public function generateNonce(RequestInterface $request) + { + return sha1(uniqid('', true) . $request->getUrl()); + } + + /** + * Gets timestamp from event or create new timestamp + * + * @param Event $event Event containing contextual information + * + * @return int + */ + public function getTimestamp(Event $event) + { + return $event['timestamp'] ?: time(); + } + + /** + * Convert booleans to strings, removed unset parameters, and sorts the array + * + * @param array $data Data array + * + * @return array + */ + protected function prepareParameters($data) + { + ksort($data); + foreach ($data as $key => &$value) { + switch (gettype($value)) { + case 'NULL': + unset($data[$key]); + break; + case 'array': + $data[$key] = self::prepareParameters($value); + break; + case 'boolean': + $data[$key] = $value ? 'true' : 'false'; + break; + } + } + + return $data; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json new file mode 100644 index 0000000..c9766ba --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/Oauth/composer.json @@ -0,0 +1,27 @@ +{ + "name": "guzzle/plugin-oauth", + "description": "Guzzle OAuth plugin", + "homepage": "http://guzzlephp.org/", + "keywords": ["oauth", "plugin", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin\\Oauth": "" } + }, + "target-dir": "Guzzle/Plugin/Oauth", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json new file mode 100644 index 0000000..2bbe64c --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Plugin/composer.json @@ -0,0 +1,44 @@ +{ + "name": "guzzle/plugin", + "description": "Guzzle plugin component containing all Guzzle HTTP plugins", + "homepage": "http://guzzlephp.org/", + "keywords": ["http", "client", "plugin", "extension", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.3.2", + "guzzle/http": "self.version" + }, + "suggest": { + "guzzle/cache": "self.version", + "guzzle/log": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Plugin": "" } + }, + "target-dir": "Guzzle/Plugin", + "replace": { + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version" + }, + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php new file mode 100644 index 0000000..cd06f57 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/AbstractConfigLoader.php @@ -0,0 +1,177 @@ + 'JSON_ERROR_NONE - No errors', + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ); + + public function load($config, array $options = array()) + { + // Reset the array of loaded files because this is a new config + $this->loadedFiles = array(); + + if (is_string($config)) { + $config = $this->loadFile($config); + } elseif (!is_array($config)) { + throw new InvalidArgumentException('Unknown type passed to configuration loader: ' . gettype($config)); + } else { + $this->mergeIncludes($config); + } + + return $this->build($config, $options); + } + + /** + * Add an include alias to the loader + * + * @param string $filename Filename to alias (e.g. _foo) + * @param string $alias Actual file to use (e.g. /path/to/foo.json) + * + * @return self + */ + public function addAlias($filename, $alias) + { + $this->aliases[$filename] = $alias; + + return $this; + } + + /** + * Remove an alias from the loader + * + * @param string $alias Alias to remove + * + * @return self + */ + public function removeAlias($alias) + { + unset($this->aliases[$alias]); + + return $this; + } + + /** + * Perform the parsing of a config file and create the end result + * + * @param array $config Configuration data + * @param array $options Options to use when building + * + * @return mixed + */ + protected abstract function build($config, array $options); + + /** + * Load a configuration file (can load JSON or PHP files that return an array when included) + * + * @param string $filename File to load + * + * @return array + * @throws InvalidArgumentException + * @throws RuntimeException when the JSON cannot be parsed + */ + protected function loadFile($filename) + { + if (isset($this->aliases[$filename])) { + $filename = $this->aliases[$filename]; + } + + switch (pathinfo($filename, PATHINFO_EXTENSION)) { + case 'js': + case 'json': + $level = error_reporting(0); + $json = file_get_contents($filename); + error_reporting($level); + + if ($json === false) { + $err = error_get_last(); + throw new InvalidArgumentException("Unable to open {$filename}: " . $err['message']); + } + + $config = json_decode($json, true); + // Throw an exception if there was an error loading the file + if ($error = json_last_error()) { + $message = isset(self::$jsonErrors[$error]) ? self::$jsonErrors[$error] : 'Unknown error'; + throw new RuntimeException("Error loading JSON data from {$filename}: ({$error}) - {$message}"); + } + break; + case 'php': + if (!is_readable($filename)) { + throw new InvalidArgumentException("Unable to open {$filename} for reading"); + } + $config = require $filename; + if (!is_array($config)) { + throw new InvalidArgumentException('PHP files must return an array of configuration data'); + } + break; + default: + throw new InvalidArgumentException('Unknown file extension: ' . $filename); + } + + // Keep track of this file being loaded to prevent infinite recursion + $this->loadedFiles[$filename] = true; + + // Merge include files into the configuration array + $this->mergeIncludes($config, dirname($filename)); + + return $config; + } + + /** + * Merges in all include files + * + * @param array $config Config data that contains includes + * @param string $basePath Base path to use when a relative path is encountered + * + * @return array Returns the merged and included data + */ + protected function mergeIncludes(&$config, $basePath = null) + { + if (!empty($config['includes'])) { + foreach ($config['includes'] as &$path) { + // Account for relative paths + if ($path[0] != DIRECTORY_SEPARATOR && !isset($this->aliases[$path]) && $basePath) { + $path = "{$basePath}/{$path}"; + } + // Don't load the same files more than once + if (!isset($this->loadedFiles[$path])) { + $this->loadedFiles[$path] = true; + $config = $this->mergeData($this->loadFile($path), $config); + } + } + } + } + + /** + * Default implementation for merging two arrays of data (uses array_merge_recursive) + * + * @param array $a Original data + * @param array $b Data to merge into the original and overwrite existing values + * + * @return array + */ + protected function mergeData(array $a, array $b) + { + return array_merge_recursive($a, $b); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php new file mode 100644 index 0000000..38150db --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilder.php @@ -0,0 +1,189 @@ +load($config, $globalParameters); + } + + /** + * @param array $serviceBuilderConfig Service configuration settings: + * - name: Name of the service + * - class: Client class to instantiate using a factory method + * - params: array of key value pair configuration settings for the builder + */ + public function __construct(array $serviceBuilderConfig = array()) + { + $this->builderConfig = $serviceBuilderConfig; + } + + public static function getAllEvents() + { + return array('service_builder.create_client'); + } + + public function unserialize($serialized) + { + $this->builderConfig = json_decode($serialized, true); + } + + public function serialize() + { + return json_encode($this->builderConfig); + } + + /** + * Attach a plugin to every client created by the builder + * + * @param EventSubscriberInterface $plugin Plugin to attach to each client + * + * @return self + */ + public function addGlobalPlugin(EventSubscriberInterface $plugin) + { + $this->plugins[] = $plugin; + + return $this; + } + + /** + * Get data from the service builder without triggering the building of a service + * + * @param string $name Name of the service to retrieve + * + * @return array|null + */ + public function getData($name) + { + return isset($this->builderConfig[$name]) ? $this->builderConfig[$name] : null; + } + + public function get($name, $throwAway = false) + { + if (!isset($this->builderConfig[$name])) { + + // Check to see if arbitrary data is being referenced + if (isset($this->clients[$name])) { + return $this->clients[$name]; + } + + // Check aliases and return a match if found + foreach ($this->builderConfig as $actualName => $config) { + if (isset($config['alias']) && $config['alias'] == $name) { + return $this->get($actualName, $throwAway); + } + } + throw new ServiceNotFoundException('No service is registered as ' . $name); + } + + if (!$throwAway && isset($this->clients[$name])) { + return $this->clients[$name]; + } + + $builder =& $this->builderConfig[$name]; + + // Convert references to the actual client + foreach ($builder['params'] as &$v) { + if (is_string($v) && substr($v, 0, 1) == '{' && substr($v, -1) == '}') { + $v = $this->get(trim($v, '{} ')); + } + } + + // Get the configured parameters and merge in any parameters provided for throw-away clients + $config = $builder['params']; + if (is_array($throwAway)) { + $config = $throwAway + $config; + } + + $client = $builder['class']::factory($config); + + if (!$throwAway) { + $this->clients[$name] = $client; + } + + if ($client instanceof ClientInterface) { + foreach ($this->plugins as $plugin) { + $client->addSubscriber($plugin); + } + // Dispatch an event letting listeners know a client was created + $this->dispatch('service_builder.create_client', array('client' => $client)); + } + + return $client; + } + + public function set($key, $service) + { + if (is_array($service) && isset($service['class']) && isset($service['params'])) { + $this->builderConfig[$key] = $service; + } else { + $this->clients[$key] = $service; + } + + return $this; + } + + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + public function offsetUnset($offset) + { + unset($this->builderConfig[$offset]); + unset($this->clients[$offset]); + } + + public function offsetExists($offset) + { + return isset($this->builderConfig[$offset]) || isset($this->clients[$offset]); + } + + public function offsetGet($offset) + { + return $this->get($offset); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php new file mode 100644 index 0000000..4fc310a --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Builder/ServiceBuilderInterface.php @@ -0,0 +1,40 @@ + &$service) { + + $service['params'] = isset($service['params']) ? $service['params'] : array(); + + // Check if this client builder extends another client + if (!empty($service['extends'])) { + + // Make sure that the service it's extending has been defined + if (!isset($services[$service['extends']])) { + throw new ServiceNotFoundException( + "{$name} is trying to extend a non-existent service: {$service['extends']}" + ); + } + + $extended = &$services[$service['extends']]; + + // Use the correct class attribute + if (empty($service['class'])) { + $service['class'] = isset($extended['class']) ? $extended['class'] : ''; + } + if ($extendsParams = isset($extended['params']) ? $extended['params'] : false) { + $service['params'] = $service['params'] + $extendsParams; + } + } + + // Overwrite default values with global parameter values + if (!empty($options)) { + $service['params'] = $options + $service['params']; + } + + $service['class'] = isset($service['class']) ? $service['class'] : ''; + } + + return new $class($services); + } + + protected function mergeData(array $a, array $b) + { + $result = $b + $a; + + // Merge services using a recursive union of arrays + if (isset($a['services']) && $b['services']) { + + // Get a union of the services of the two arrays + $result['services'] = $b['services'] + $a['services']; + + // Merge each service in using a union of the two arrays + foreach ($result['services'] as $name => &$service) { + + // By default, services completely override a previously defined service unless it extends itself + if (isset($a['services'][$name]['extends']) + && isset($b['services'][$name]['extends']) + && $b['services'][$name]['extends'] == $name + ) { + $service += $a['services'][$name]; + // Use the `extends` attribute of the parent + $service['extends'] = $a['services'][$name]['extends']; + // Merge parameters using a union if both have parameters + if (isset($a['services'][$name]['params'])) { + $service['params'] += $a['services'][$name]['params']; + } + } + } + } + + return $result; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php new file mode 100644 index 0000000..26f8360 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/CachingConfigLoader.php @@ -0,0 +1,46 @@ +loader = $loader; + $this->cache = $cache; + } + + public function load($config, array $options = array()) + { + if (!is_string($config)) { + $key = false; + } else { + $key = 'loader_' . crc32($config); + if ($result = $this->cache->fetch($key)) { + return $result; + } + } + + $result = $this->loader->load($config, $options); + if ($key) { + $this->cache->save($key, $result); + } + + return $result; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php new file mode 100644 index 0000000..3e5f8e5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Client.php @@ -0,0 +1,297 @@ +getCommand($method, isset($args[0]) ? $args[0] : array())->getResult(); + } + + public function getCommand($name, array $args = array()) + { + // Add global client options to the command + if ($options = $this->getConfig(self::COMMAND_PARAMS)) { + $args += $options; + } + + if (!($command = $this->getCommandFactory()->factory($name, $args))) { + throw new InvalidArgumentException("Command was not found matching {$name}"); + } + + $command->setClient($this); + $this->dispatch('client.command.create', array('client' => $this, 'command' => $command)); + + return $command; + } + + /** + * Set the command factory used to create commands by name + * + * @param CommandFactoryInterface $factory Command factory + * + * @return self + */ + public function setCommandFactory(CommandFactoryInterface $factory) + { + $this->commandFactory = $factory; + + return $this; + } + + /** + * Set the resource iterator factory associated with the client + * + * @param ResourceIteratorFactoryInterface $factory Resource iterator factory + * + * @return self + */ + public function setResourceIteratorFactory(ResourceIteratorFactoryInterface $factory) + { + $this->resourceIteratorFactory = $factory; + + return $this; + } + + public function getIterator($command, array $commandOptions = null, array $iteratorOptions = array()) + { + if (!($command instanceof CommandInterface)) { + $command = $this->getCommand($command, $commandOptions ?: array()); + } + + return $this->getResourceIteratorFactory()->build($command, $iteratorOptions); + } + + public function execute($command) + { + if ($command instanceof CommandInterface) { + $this->send($this->prepareCommand($command)); + $this->dispatch('command.after_send', array('command' => $command)); + return $command->getResult(); + } elseif (is_array($command) || $command instanceof \Traversable) { + return $this->executeMultiple($command); + } else { + throw new InvalidArgumentException('Command must be a command or array of commands'); + } + } + + public function setDescription(ServiceDescriptionInterface $service) + { + $this->serviceDescription = $service; + + if ($this->getCommandFactory() && $this->getCommandFactory() instanceof CompositeFactory) { + $this->commandFactory->add(new Command\Factory\ServiceDescriptionFactory($service)); + } + + // If a baseUrl was set on the description, then update the client + if ($baseUrl = $service->getBaseUrl()) { + $this->setBaseUrl($baseUrl); + } + + return $this; + } + + public function getDescription() + { + return $this->serviceDescription; + } + + /** + * Set the inflector used with the client + * + * @param InflectorInterface $inflector Inflection object + * + * @return self + */ + public function setInflector(InflectorInterface $inflector) + { + $this->inflector = $inflector; + + return $this; + } + + /** + * Get the inflector used with the client + * + * @return self + */ + public function getInflector() + { + if (!$this->inflector) { + $this->inflector = Inflector::getDefault(); + } + + return $this->inflector; + } + + /** + * Prepare a command for sending and get the RequestInterface object created by the command + * + * @param CommandInterface $command Command to prepare + * + * @return RequestInterface + */ + protected function prepareCommand(CommandInterface $command) + { + // Set the client and prepare the command + $request = $command->setClient($this)->prepare(); + // Set the state to new if the command was previously executed + $request->setState(RequestInterface::STATE_NEW); + $this->dispatch('command.before_send', array('command' => $command)); + + return $request; + } + + /** + * Execute multiple commands in parallel + * + * @param array|Traversable $commands Array of CommandInterface objects to execute + * + * @return array Returns an array of the executed commands + * @throws Exception\CommandTransferException + */ + protected function executeMultiple($commands) + { + $requests = array(); + $commandRequests = new \SplObjectStorage(); + + foreach ($commands as $command) { + $request = $this->prepareCommand($command); + $commandRequests[$request] = $command; + $requests[] = $request; + } + + try { + $this->send($requests); + foreach ($commands as $command) { + $this->dispatch('command.after_send', array('command' => $command)); + } + return $commands; + } catch (MultiTransferException $failureException) { + // Throw a CommandTransferException using the successful and failed commands + $e = CommandTransferException::fromMultiTransferException($failureException); + + // Remove failed requests from the successful requests array and add to the failures array + foreach ($failureException->getFailedRequests() as $request) { + if (isset($commandRequests[$request])) { + $e->addFailedCommand($commandRequests[$request]); + unset($commandRequests[$request]); + } + } + + // Always emit the command after_send events for successful commands + foreach ($commandRequests as $success) { + $e->addSuccessfulCommand($commandRequests[$success]); + $this->dispatch('command.after_send', array('command' => $commandRequests[$success])); + } + + throw $e; + } + } + + protected function getResourceIteratorFactory() + { + if (!$this->resourceIteratorFactory) { + // Build the default resource iterator factory if one is not set + $clientClass = get_class($this); + $prefix = substr($clientClass, 0, strrpos($clientClass, '\\')); + $this->resourceIteratorFactory = new ResourceIteratorClassFactory(array( + "{$prefix}\\Iterator", + "{$prefix}\\Model" + )); + } + + return $this->resourceIteratorFactory; + } + + /** + * Get the command factory associated with the client + * + * @return CommandFactoryInterface + */ + protected function getCommandFactory() + { + if (!$this->commandFactory) { + $this->commandFactory = CompositeFactory::getDefaultChain($this); + } + + return $this->commandFactory; + } + + /** + * @deprecated + * @codeCoverageIgnore + */ + public function enableMagicMethods($isEnabled) + { + Version::warn(__METHOD__ . ' is deprecated'); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php new file mode 100644 index 0000000..814154f --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/ClientInterface.php @@ -0,0 +1,68 @@ +operation = $operation ?: $this->createOperation(); + foreach ($this->operation->getParams() as $name => $arg) { + $currentValue = $this[$name]; + $configValue = $arg->getValue($currentValue); + // If default or static values are set, then this should always be updated on the config object + if ($currentValue !== $configValue) { + $this[$name] = $configValue; + } + } + + $headers = $this[self::HEADERS_OPTION]; + if (!$headers instanceof Collection) { + $this[self::HEADERS_OPTION] = new Collection((array) $headers); + } + + // You can set a command.on_complete option in your parameters to set an onComplete callback + if ($onComplete = $this['command.on_complete']) { + unset($this['command.on_complete']); + $this->setOnComplete($onComplete); + } + + // Set the hidden additional parameters + if (!$this[self::HIDDEN_PARAMS]) { + $this[self::HIDDEN_PARAMS] = array( + self::HEADERS_OPTION, + self::RESPONSE_PROCESSING, + self::HIDDEN_PARAMS, + self::REQUEST_OPTIONS + ); + } + + $this->init(); + } + + /** + * Custom clone behavior + */ + public function __clone() + { + $this->request = null; + $this->result = null; + } + + /** + * Execute the command in the same manner as calling a function + * + * @return mixed Returns the result of {@see AbstractCommand::execute} + */ + public function __invoke() + { + return $this->execute(); + } + + public function getName() + { + return $this->operation->getName(); + } + + /** + * Get the API command information about the command + * + * @return OperationInterface + */ + public function getOperation() + { + return $this->operation; + } + + public function setOnComplete($callable) + { + if (!is_callable($callable)) { + throw new InvalidArgumentException('The onComplete function must be callable'); + } + + $this->onComplete = $callable; + + return $this; + } + + public function execute() + { + if (!$this->client) { + throw new CommandException('A client must be associated with the command before it can be executed.'); + } + + return $this->client->execute($this); + } + + public function getClient() + { + return $this->client; + } + + public function setClient(ClientInterface $client) + { + $this->client = $client; + + return $this; + } + + public function getRequest() + { + if (!$this->request) { + throw new CommandException('The command must be prepared before retrieving the request'); + } + + return $this->request; + } + + public function getResponse() + { + if (!$this->isExecuted()) { + $this->execute(); + } + + return $this->request->getResponse(); + } + + public function getResult() + { + if (!$this->isExecuted()) { + $this->execute(); + } + + if (null === $this->result) { + $this->process(); + // Call the onComplete method if one is set + if ($this->onComplete) { + call_user_func($this->onComplete, $this); + } + } + + return $this->result; + } + + public function setResult($result) + { + $this->result = $result; + + return $this; + } + + public function isPrepared() + { + return $this->request !== null; + } + + public function isExecuted() + { + return $this->request !== null && $this->request->getState() == 'complete'; + } + + public function prepare() + { + if (!$this->isPrepared()) { + if (!$this->client) { + throw new CommandException('A client must be associated with the command before it can be prepared.'); + } + + // If no response processing value was specified, then attempt to use the highest level of processing + if (!isset($this[self::RESPONSE_PROCESSING])) { + $this[self::RESPONSE_PROCESSING] = self::TYPE_MODEL; + } + + // Notify subscribers of the client that the command is being prepared + $this->client->dispatch('command.before_prepare', array('command' => $this)); + + // Fail on missing required arguments, and change parameters via filters + $this->validate(); + // Delegate to the subclass that implements the build method + $this->build(); + + // Add custom request headers set on the command + if ($headers = $this[self::HEADERS_OPTION]) { + foreach ($headers as $key => $value) { + $this->request->setHeader($key, $value); + } + } + + // Add any curl options to the request + if ($options = $this[Client::CURL_OPTIONS]) { + $this->request->getCurlOptions()->overwriteWith(CurlHandle::parseCurlConfig($options)); + } + + // Set a custom response body + if ($responseBody = $this[self::RESPONSE_BODY]) { + $this->request->setResponseBody($responseBody); + } + + $this->client->dispatch('command.after_prepare', array('command' => $this)); + } + + return $this->request; + } + + /** + * Set the validator used to validate and prepare command parameters and nested JSON schemas. If no validator is + * set, then the command will validate using the default {@see SchemaValidator}. + * + * @param ValidatorInterface $validator Validator used to prepare and validate properties against a JSON schema + * + * @return self + */ + public function setValidator(ValidatorInterface $validator) + { + $this->validator = $validator; + + return $this; + } + + public function getRequestHeaders() + { + return $this[self::HEADERS_OPTION]; + } + + /** + * Initialize the command (hook that can be implemented in subclasses) + */ + protected function init() {} + + /** + * Create the request object that will carry out the command + */ + abstract protected function build(); + + /** + * Hook used to create an operation for concrete commands that are not associated with a service description + * + * @return OperationInterface + */ + protected function createOperation() + { + return new Operation(array('name' => get_class($this))); + } + + /** + * Create the result of the command after the request has been completed. + * Override this method in subclasses to customize this behavior + */ + protected function process() + { + $this->result = $this[self::RESPONSE_PROCESSING] != self::TYPE_RAW + ? DefaultResponseParser::getInstance()->parse($this) + : $this->request->getResponse(); + } + + /** + * Validate and prepare the command based on the schema and rules defined by the command's Operation object + * + * @throws ValidationException when validation errors occur + */ + protected function validate() + { + // Do not perform request validation/transformation if it is disable + if ($this[self::DISABLE_VALIDATION]) { + return; + } + + $errors = array(); + $validator = $this->getValidator(); + foreach ($this->operation->getParams() as $name => $schema) { + $value = $this[$name]; + if (!$validator->validate($schema, $value)) { + $errors = array_merge($errors, $validator->getErrors()); + } elseif ($value !== $this[$name]) { + // Update the config value if it changed and no validation errors were encountered + $this->data[$name] = $value; + } + } + + // Validate additional parameters + $hidden = $this[self::HIDDEN_PARAMS]; + + if ($properties = $this->operation->getAdditionalParameters()) { + foreach ($this->toArray() as $name => $value) { + // It's only additional if it isn't defined in the schema + if (!$this->operation->hasParam($name) && !in_array($name, $hidden)) { + // Always set the name so that error messages are useful + $properties->setName($name); + if (!$validator->validate($properties, $value)) { + $errors = array_merge($errors, $validator->getErrors()); + } elseif ($value !== $this[$name]) { + $this->data[$name] = $value; + } + } + } + } + + if (!empty($errors)) { + $e = new ValidationException('Validation errors: ' . implode("\n", $errors)); + $e->setErrors($errors); + throw $e; + } + } + + /** + * Get the validator used to prepare and validate properties. If no validator has been set on the command, then + * the default {@see SchemaValidator} will be used. + * + * @return ValidatorInterface + */ + protected function getValidator() + { + if (!$this->validator) { + $this->validator = SchemaValidator::getInstance(); + } + + return $this->validator; + } + + /** + * Get array of any validation errors + * If no validator has been set then return false + */ + public function getValidationErrors() + { + if (!$this->validator) { + return false; + } + + return $this->validator->getErrors(); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php new file mode 100644 index 0000000..cb6ac40 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/ClosureCommand.php @@ -0,0 +1,41 @@ +request = $closure($this, $this->operation); + + if (!$this->request || !$this->request instanceof RequestInterface) { + throw new UnexpectedValueException('Closure command did not return a RequestInterface object'); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php new file mode 100644 index 0000000..fbb61d2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/CommandInterface.php @@ -0,0 +1,128 @@ +stopPropagation(); + } + + /** + * Get the created object + * + * @return mixed + */ + public function getResult() + { + return $this['result']; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php new file mode 100644 index 0000000..2dc4acd --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultRequestSerializer.php @@ -0,0 +1,169 @@ +factory = $factory; + } + + /** + * Add a location visitor to the serializer + * + * @param string $location Location to associate with the visitor + * @param RequestVisitorInterface $visitor Visitor to attach + * + * @return self + */ + public function addVisitor($location, RequestVisitorInterface $visitor) + { + $this->factory->addRequestVisitor($location, $visitor); + + return $this; + } + + public function prepare(CommandInterface $command) + { + $request = $this->createRequest($command); + // Keep an array of visitors found in the operation + $foundVisitors = array(); + $operation = $command->getOperation(); + + // Add arguments to the request using the location attribute + foreach ($operation->getParams() as $name => $arg) { + /** @var $arg \Guzzle\Service\Description\Parameter */ + $location = $arg->getLocation(); + // Skip 'uri' locations because they've already been processed + if ($location && $location != 'uri') { + // Instantiate visitors as they are detected in the properties + if (!isset($foundVisitors[$location])) { + $foundVisitors[$location] = $this->factory->getRequestVisitor($location); + } + // Ensure that a value has been set for this parameter + $value = $command[$name]; + if ($value !== null) { + // Apply the parameter value with the location visitor + $foundVisitors[$location]->visit($command, $request, $arg, $value); + } + } + } + + // Serialize additional parameters + if ($additional = $operation->getAdditionalParameters()) { + if ($visitor = $this->prepareAdditionalParameters($operation, $command, $request, $additional)) { + $foundVisitors[$additional->getLocation()] = $visitor; + } + } + + // Call the after method on each visitor found in the operation + foreach ($foundVisitors as $visitor) { + $visitor->after($command, $request); + } + + return $request; + } + + /** + * Serialize additional parameters + * + * @param OperationInterface $operation Operation that owns the command + * @param CommandInterface $command Command to prepare + * @param RequestInterface $request Request to serialize + * @param Parameter $additional Additional parameters + * + * @return null|RequestVisitorInterface + */ + protected function prepareAdditionalParameters( + OperationInterface $operation, + CommandInterface $command, + RequestInterface $request, + Parameter $additional + ) { + if (!($location = $additional->getLocation())) { + return; + } + + $visitor = $this->factory->getRequestVisitor($location); + $hidden = $command[$command::HIDDEN_PARAMS]; + + foreach ($command->toArray() as $key => $value) { + // Ignore values that are null or built-in command options + if ($value !== null + && !in_array($key, $hidden) + && !$operation->hasParam($key) + ) { + $additional->setName($key); + $visitor->visit($command, $request, $additional, $value); + } + } + + return $visitor; + } + + /** + * Create a request for the command and operation + * + * @param CommandInterface $command Command to create a request for + * + * @return RequestInterface + */ + protected function createRequest(CommandInterface $command) + { + $operation = $command->getOperation(); + $client = $command->getClient(); + $options = $command[AbstractCommand::REQUEST_OPTIONS] ?: array(); + + // If the command does not specify a template, then assume the base URL of the client + if (!($uri = $operation->getUri())) { + return $client->createRequest($operation->getHttpMethod(), $client->getBaseUrl(), null, null, $options); + } + + // Get the path values and use the client config settings + $variables = array(); + foreach ($operation->getParams() as $name => $arg) { + if ($arg->getLocation() == 'uri') { + if (isset($command[$name])) { + $variables[$name] = $arg->filter($command[$name]); + if (!is_array($variables[$name])) { + $variables[$name] = (string) $variables[$name]; + } + } + } + } + + return $client->createRequest($operation->getHttpMethod(), array($uri, $variables), null, null, $options); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php new file mode 100644 index 0000000..4fe3803 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/DefaultResponseParser.php @@ -0,0 +1,55 @@ +getRequest()->getResponse(); + + // Account for hard coded content-type values specified in service descriptions + if ($contentType = $command['command.expects']) { + $response->setHeader('Content-Type', $contentType); + } else { + $contentType = (string) $response->getHeader('Content-Type'); + } + + return $this->handleParsing($command, $response, $contentType); + } + + protected function handleParsing(CommandInterface $command, Response $response, $contentType) + { + $result = $response; + if ($result->getBody()) { + if (stripos($contentType, 'json') !== false) { + $result = $result->json(); + } elseif (stripos($contentType, 'xml') !== false) { + $result = $result->xml(); + } + } + + return $result; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php new file mode 100644 index 0000000..1c5ce07 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/AliasFactory.php @@ -0,0 +1,39 @@ +client = $client; + $this->aliases = $aliases; + } + + public function factory($name, array $args = array()) + { + if (isset($this->aliases[$name])) { + try { + return $this->client->getCommand($this->aliases[$name], $args); + } catch (InvalidArgumentException $e) { + return null; + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php new file mode 100644 index 0000000..8c46983 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/CompositeFactory.php @@ -0,0 +1,154 @@ +getDescription()) { + $factories[] = new ServiceDescriptionFactory($description); + } + $factories[] = new ConcreteClassFactory($client); + + return new self($factories); + } + + /** + * @param array $factories Array of command factories + */ + public function __construct(array $factories = array()) + { + $this->factories = $factories; + } + + /** + * Add a command factory to the chain + * + * @param FactoryInterface $factory Factory to add + * @param string|FactoryInterface $before Insert the new command factory before a command factory class or object + * matching a class name. + * @return CompositeFactory + */ + public function add(FactoryInterface $factory, $before = null) + { + $pos = null; + + if ($before) { + foreach ($this->factories as $i => $f) { + if ($before instanceof FactoryInterface) { + if ($f === $before) { + $pos = $i; + break; + } + } elseif (is_string($before)) { + if ($f instanceof $before) { + $pos = $i; + break; + } + } + } + } + + if ($pos === null) { + $this->factories[] = $factory; + } else { + array_splice($this->factories, $i, 0, array($factory)); + } + + return $this; + } + + /** + * Check if the chain contains a specific command factory + * + * @param FactoryInterface|string $factory Factory to check + * + * @return bool + */ + public function has($factory) + { + return (bool) $this->find($factory); + } + + /** + * Remove a specific command factory from the chain + * + * @param string|FactoryInterface $factory Factory to remove by name or instance + * + * @return CompositeFactory + */ + public function remove($factory = null) + { + if (!($factory instanceof FactoryInterface)) { + $factory = $this->find($factory); + } + + $this->factories = array_values(array_filter($this->factories, function($f) use ($factory) { + return $f !== $factory; + })); + + return $this; + } + + /** + * Get a command factory by class name + * + * @param string|FactoryInterface $factory Command factory class or instance + * + * @return null|FactoryInterface + */ + public function find($factory) + { + foreach ($this->factories as $f) { + if ($factory === $f || (is_string($factory) && $f instanceof $factory)) { + return $f; + } + } + } + + /** + * Create a command using the associated command factories + * + * @param string $name Name of the command + * @param array $args Command arguments + * + * @return CommandInterface + */ + public function factory($name, array $args = array()) + { + foreach ($this->factories as $factory) { + $command = $factory->factory($name, $args); + if ($command) { + return $command; + } + } + } + + public function count() + { + return count($this->factories); + } + + public function getIterator() + { + return new \ArrayIterator($this->factories); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php new file mode 100644 index 0000000..0e93dea --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ConcreteClassFactory.php @@ -0,0 +1,47 @@ +client = $client; + $this->inflector = $inflector ?: Inflector::getDefault(); + } + + public function factory($name, array $args = array()) + { + // Determine the class to instantiate based on the namespace of the current client and the default directory + $prefix = $this->client->getConfig('command.prefix'); + if (!$prefix) { + // The prefix can be specified in a factory method and is cached + $prefix = implode('\\', array_slice(explode('\\', get_class($this->client)), 0, -1)) . '\\Command\\'; + $this->client->getConfig()->set('command.prefix', $prefix); + } + + $class = $prefix . str_replace(' ', '\\', ucwords(str_replace('.', ' ', $this->inflector->camel($name)))); + + // Create the concrete command if it exists + if (class_exists($class)) { + return new $class($args); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php new file mode 100644 index 0000000..35c299d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/FactoryInterface.php @@ -0,0 +1,21 @@ +map = $map; + } + + public function factory($name, array $args = array()) + { + if (isset($this->map[$name])) { + $class = $this->map[$name]; + + return new $class($args); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php new file mode 100644 index 0000000..b943a5b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/Factory/ServiceDescriptionFactory.php @@ -0,0 +1,71 @@ +setServiceDescription($description); + $this->inflector = $inflector; + } + + /** + * Change the service description used with the factory + * + * @param ServiceDescriptionInterface $description Service description to use + * + * @return FactoryInterface + */ + public function setServiceDescription(ServiceDescriptionInterface $description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the service description + * + * @return ServiceDescriptionInterface + */ + public function getServiceDescription() + { + return $this->description; + } + + public function factory($name, array $args = array()) + { + $command = $this->description->getOperation($name); + + // If a command wasn't found, then try to uppercase the first letter and try again + if (!$command) { + $command = $this->description->getOperation(ucfirst($name)); + // If an inflector was passed, then attempt to get the command using snake_case inflection + if (!$command && $this->inflector) { + $command = $this->description->getOperation($this->inflector->snake($name)); + } + } + + if ($command) { + $class = $command->getClass(); + return new $class($args, $command, $this->description); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php new file mode 100644 index 0000000..adcfca1 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/AbstractRequestVisitor.php @@ -0,0 +1,69 @@ +resolveRecursively($value, $param) + : $param->filter($value); + } + + /** + * Map nested parameters into the location_key based parameters + * + * @param array $value Value to map + * @param Parameter $param Parameter that holds information about the current key + * + * @return array Returns the mapped array + */ + protected function resolveRecursively(array $value, Parameter $param) + { + foreach ($value as $name => &$v) { + switch ($param->getType()) { + case 'object': + if ($subParam = $param->getProperty($name)) { + $key = $subParam->getWireName(); + $value[$key] = $this->prepareValue($v, $subParam); + if ($name != $key) { + unset($value[$name]); + } + } elseif ($param->getAdditionalProperties() instanceof Parameter) { + $v = $this->prepareValue($v, $param->getAdditionalProperties()); + } + break; + case 'array': + if ($items = $param->getItems()) { + $v = $this->prepareValue($v, $items); + } + break; + } + } + + return $param->filter($value); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php new file mode 100644 index 0000000..168d780 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/BodyVisitor.php @@ -0,0 +1,58 @@ +filter($value); + $entityBody = EntityBody::factory($value); + $request->setBody($entityBody); + $this->addExpectHeader($request, $entityBody, $param->getData('expect_header')); + // Add the Content-Encoding header if one is set on the EntityBody + if ($encoding = $entityBody->getContentEncoding()) { + $request->setHeader('Content-Encoding', $encoding); + } + } + + /** + * Add the appropriate expect header to a request + * + * @param EntityEnclosingRequestInterface $request Request to update + * @param EntityBodyInterface $body Entity body of the request + * @param string|int $expect Expect header setting + */ + protected function addExpectHeader(EntityEnclosingRequestInterface $request, EntityBodyInterface $body, $expect) + { + // Allow the `expect` data parameter to be set to remove the Expect header from the request + if ($expect === false) { + $request->removeHeader('Expect'); + } elseif ($expect !== true) { + // Default to using a MB as the point in which to start using the expect header + $expect = $expect ?: 1048576; + // If the expect_header value is numeric then only add if the size is greater than the cutoff + if (is_numeric($expect) && $body->getSize()) { + if ($body->getSize() < $expect) { + $request->removeHeader('Expect'); + } else { + $request->setHeader('Expect', '100-Continue'); + } + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php new file mode 100644 index 0000000..2a53754 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/HeaderVisitor.php @@ -0,0 +1,44 @@ +filter($value); + if ($param->getType() == 'object' && $param->getAdditionalProperties() instanceof Parameter) { + $this->addPrefixedHeaders($request, $param, $value); + } else { + $request->setHeader($param->getWireName(), $value); + } + } + + /** + * Add a prefixed array of headers to the request + * + * @param RequestInterface $request Request to update + * @param Parameter $param Parameter object + * @param array $value Header array to add + * + * @throws InvalidArgumentException + */ + protected function addPrefixedHeaders(RequestInterface $request, Parameter $param, $value) + { + if (!is_array($value)) { + throw new InvalidArgumentException('An array of mapped headers expected, but received a single value'); + } + $prefix = $param->getSentAs(); + foreach ($value as $headerName => $headerValue) { + $request->setHeader($prefix . $headerName, $headerValue); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php new file mode 100644 index 0000000..757e1c5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/JsonVisitor.php @@ -0,0 +1,63 @@ +data = new \SplObjectStorage(); + } + + /** + * Set the Content-Type header to add to the request if JSON is added to the body. This visitor does not add a + * Content-Type header unless you specify one here. + * + * @param string $header Header to set when JSON is added (e.g. application/json) + * + * @return self + */ + public function setContentTypeHeader($header = 'application/json') + { + $this->jsonContentType = $header; + + return $this; + } + + public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value) + { + if (isset($this->data[$command])) { + $json = $this->data[$command]; + } else { + $json = array(); + } + $json[$param->getWireName()] = $this->prepareValue($value, $param); + $this->data[$command] = $json; + } + + public function after(CommandInterface $command, RequestInterface $request) + { + if (isset($this->data[$command])) { + // Don't overwrite the Content-Type if one is set + if ($this->jsonContentType && !$request->hasHeader('Content-Type')) { + $request->setHeader('Content-Type', $this->jsonContentType); + } + + $request->setBody(json_encode($this->data[$command])); + unset($this->data[$command]); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php new file mode 100644 index 0000000..975850b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFieldVisitor.php @@ -0,0 +1,18 @@ +setPostField($param->getWireName(), $this->prepareValue($value, $param)); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php new file mode 100644 index 0000000..0853ebe --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/PostFileVisitor.php @@ -0,0 +1,24 @@ +filter($value); + if ($value instanceof PostFileInterface) { + $request->addPostFile($value); + } else { + $request->addPostFile($param->getWireName(), $value); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php new file mode 100644 index 0000000..315877a --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/QueryVisitor.php @@ -0,0 +1,18 @@ +getQuery()->set($param->getWireName(), $this->prepareValue($value, $param)); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php new file mode 100644 index 0000000..14e0b2d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/RequestVisitorInterface.php @@ -0,0 +1,31 @@ +setResponseBody($value); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php new file mode 100644 index 0000000..5b71487 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Request/XmlVisitor.php @@ -0,0 +1,252 @@ +data = new \SplObjectStorage(); + } + + /** + * Change the content-type header that is added when XML is found + * + * @param string $header Header to set when XML is found + * + * @return self + */ + public function setContentTypeHeader($header) + { + $this->contentType = $header; + + return $this; + } + + public function visit(CommandInterface $command, RequestInterface $request, Parameter $param, $value) + { + $xml = isset($this->data[$command]) + ? $this->data[$command] + : $this->createRootElement($param->getParent()); + $this->addXml($xml, $param, $value); + + $this->data[$command] = $xml; + } + + public function after(CommandInterface $command, RequestInterface $request) + { + $xml = null; + + // If data was found that needs to be serialized, then do so + if (isset($this->data[$command])) { + $xml = $this->finishDocument($this->data[$command]); + unset($this->data[$command]); + } else { + // Check if XML should always be sent for the command + $operation = $command->getOperation(); + if ($operation->getData('xmlAllowEmpty')) { + $xmlWriter = $this->createRootElement($operation); + $xml = $this->finishDocument($xmlWriter); + } + } + + if ($xml) { + // Don't overwrite the Content-Type if one is set + if ($this->contentType && !$request->hasHeader('Content-Type')) { + $request->setHeader('Content-Type', $this->contentType); + } + $request->setBody($xml); + } + } + + /** + * Create the root XML element to use with a request + * + * @param Operation $operation Operation object + * + * @return \XMLWriter + */ + protected function createRootElement(Operation $operation) + { + static $defaultRoot = array('name' => 'Request'); + // If no root element was specified, then just wrap the XML in 'Request' + $root = $operation->getData('xmlRoot') ?: $defaultRoot; + // Allow the XML declaration to be customized with xmlEncoding + $encoding = $operation->getData('xmlEncoding'); + + $xmlWriter = $this->startDocument($encoding); + + $xmlWriter->startElement($root['name']); + // Create the wrapping element with no namespaces if no namespaces were present + if (!empty($root['namespaces'])) { + // Create the wrapping element with an array of one or more namespaces + foreach ((array) $root['namespaces'] as $prefix => $uri) { + $nsLabel = 'xmlns'; + if (!is_numeric($prefix)) { + $nsLabel .= ':'.$prefix; + } + $xmlWriter->writeAttribute($nsLabel, $uri); + } + } + return $xmlWriter; + } + + /** + * Recursively build the XML body + * + * @param \XMLWriter $xmlWriter XML to modify + * @param Parameter $param API Parameter + * @param mixed $value Value to add + */ + protected function addXml(\XMLWriter $xmlWriter, Parameter $param, $value) + { + if ($value === null) { + return; + } + + $value = $param->filter($value); + $type = $param->getType(); + $name = $param->getWireName(); + $prefix = null; + $namespace = $param->getData('xmlNamespace'); + if (false !== strpos($name, ':')) { + list($prefix, $name) = explode(':', $name, 2); + } + + if ($type == 'object' || $type == 'array') { + if (!$param->getData('xmlFlattened')) { + $xmlWriter->startElementNS(null, $name, $namespace); + } + if ($param->getType() == 'array') { + $this->addXmlArray($xmlWriter, $param, $value); + } elseif ($param->getType() == 'object') { + $this->addXmlObject($xmlWriter, $param, $value); + } + if (!$param->getData('xmlFlattened')) { + $xmlWriter->endElement(); + } + return; + } + if ($param->getData('xmlAttribute')) { + $this->writeAttribute($xmlWriter, $prefix, $name, $namespace, $value); + } else { + $this->writeElement($xmlWriter, $prefix, $name, $namespace, $value); + } + } + + /** + * Write an attribute with namespace if used + * + * @param \XMLWriter $xmlWriter XMLWriter instance + * @param string $prefix Namespace prefix if any + * @param string $name Attribute name + * @param string $namespace The uri of the namespace + * @param string $value The attribute content + */ + protected function writeAttribute($xmlWriter, $prefix, $name, $namespace, $value) + { + if (empty($namespace)) { + $xmlWriter->writeAttribute($name, $value); + } else { + $xmlWriter->writeAttributeNS($prefix, $name, $namespace, $value); + } + } + + /** + * Write an element with namespace if used + * + * @param \XMLWriter $xmlWriter XML writer resource + * @param string $prefix Namespace prefix if any + * @param string $name Element name + * @param string $namespace The uri of the namespace + * @param string $value The element content + */ + protected function writeElement(\XMLWriter $xmlWriter, $prefix, $name, $namespace, $value) + { + $xmlWriter->startElementNS($prefix, $name, $namespace); + if (strpbrk($value, '<>&')) { + $xmlWriter->writeCData($value); + } else { + $xmlWriter->writeRaw($value); + } + $xmlWriter->endElement(); + } + + /** + * Create a new xml writer and start a document + * + * @param string $encoding document encoding + * + * @return \XMLWriter the writer resource + */ + protected function startDocument($encoding) + { + $xmlWriter = new \XMLWriter(); + $xmlWriter->openMemory(); + $xmlWriter->startDocument('1.0', $encoding); + + return $xmlWriter; + } + + /** + * End the document and return the output + * + * @param \XMLWriter $xmlWriter + * + * @return \string the writer resource + */ + protected function finishDocument($xmlWriter) + { + $xmlWriter->endDocument(); + + return $xmlWriter->outputMemory(); + } + + /** + * Add an array to the XML + */ + protected function addXmlArray(\XMLWriter $xmlWriter, Parameter $param, &$value) + { + if ($items = $param->getItems()) { + foreach ($value as $v) { + $this->addXml($xmlWriter, $items, $v); + } + } + } + + /** + * Add an object to the XML + */ + protected function addXmlObject(\XMLWriter $xmlWriter, Parameter $param, &$value) + { + $noAttributes = array(); + // add values which have attributes + foreach ($value as $name => $v) { + if ($property = $param->getProperty($name)) { + if ($property->getData('xmlAttribute')) { + $this->addXml($xmlWriter, $property, $v); + } else { + $noAttributes[] = array('value' => $v, 'property' => $property); + } + } + } + // now add values with no attributes + foreach ($noAttributes as $element) { + $this->addXml($xmlWriter, $element['property'], $element['value']); + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php new file mode 100644 index 0000000..d87eeb9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/AbstractResponseVisitor.php @@ -0,0 +1,26 @@ +getName()] = $param->filter($response->getBody()); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php new file mode 100644 index 0000000..0f8737c --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/HeaderVisitor.php @@ -0,0 +1,50 @@ +getType() == 'object' && $param->getAdditionalProperties() instanceof Parameter) { + $this->processPrefixedHeaders($response, $param, $value); + } else { + $value[$param->getName()] = $param->filter((string) $response->getHeader($param->getWireName())); + } + } + + /** + * Process a prefixed header array + * + * @param Response $response Response that contains the headers + * @param Parameter $param Parameter object + * @param array $value Value response array to modify + */ + protected function processPrefixedHeaders(Response $response, Parameter $param, &$value) + { + // Grab prefixed headers that should be placed into an array with the prefix stripped + if ($prefix = $param->getSentAs()) { + $container = $param->getName(); + $len = strlen($prefix); + // Find all matching headers and place them into the containing element + foreach ($response->getHeaders()->toArray() as $key => $header) { + if (stripos($key, $prefix) === 0) { + // Account for multi-value headers + $value[$container][substr($key, $len)] = count($header) == 1 ? end($header) : $header; + } + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php new file mode 100644 index 0000000..a609ebd --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/JsonVisitor.php @@ -0,0 +1,93 @@ +getResponse()->json(); + } + + public function visit( + CommandInterface $command, + Response $response, + Parameter $param, + &$value, + $context = null + ) { + $name = $param->getName(); + $key = $param->getWireName(); + if (isset($value[$key])) { + $this->recursiveProcess($param, $value[$key]); + if ($key != $name) { + $value[$name] = $value[$key]; + unset($value[$key]); + } + } + } + + /** + * Recursively process a parameter while applying filters + * + * @param Parameter $param API parameter being validated + * @param mixed $value Value to validate and process. The value may change during this process. + */ + protected function recursiveProcess(Parameter $param, &$value) + { + if ($value === null) { + return; + } + + if (is_array($value)) { + $type = $param->getType(); + if ($type == 'array') { + foreach ($value as &$item) { + $this->recursiveProcess($param->getItems(), $item); + } + } elseif ($type == 'object' && !isset($value[0])) { + // On the above line, we ensure that the array is associative and not numerically indexed + $knownProperties = array(); + if ($properties = $param->getProperties()) { + foreach ($properties as $property) { + $name = $property->getName(); + $key = $property->getWireName(); + $knownProperties[$name] = 1; + if (isset($value[$key])) { + $this->recursiveProcess($property, $value[$key]); + if ($key != $name) { + $value[$name] = $value[$key]; + unset($value[$key]); + } + } + } + } + + // Remove any unknown and potentially unsafe properties + if ($param->getAdditionalProperties() === false) { + $value = array_intersect_key($value, $knownProperties); + } elseif (($additional = $param->getAdditionalProperties()) !== true) { + // Validate and filter additional properties + foreach ($value as &$v) { + $this->recursiveProcess($additional, $v); + } + } + } + } + + $value = $param->filter($value); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php new file mode 100644 index 0000000..1b10ebc --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ReasonPhraseVisitor.php @@ -0,0 +1,23 @@ +getName()] = $response->getReasonPhrase(); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php new file mode 100644 index 0000000..033f40c --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/ResponseVisitorInterface.php @@ -0,0 +1,46 @@ +getName()] = $response->getStatusCode(); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php new file mode 100644 index 0000000..bb7124b --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/Response/XmlVisitor.php @@ -0,0 +1,151 @@ +getResponse()->xml()), true); + } + + public function visit( + CommandInterface $command, + Response $response, + Parameter $param, + &$value, + $context = null + ) { + $sentAs = $param->getWireName(); + $name = $param->getName(); + if (isset($value[$sentAs])) { + $this->recursiveProcess($param, $value[$sentAs]); + if ($name != $sentAs) { + $value[$name] = $value[$sentAs]; + unset($value[$sentAs]); + } + } + } + + /** + * Recursively process a parameter while applying filters + * + * @param Parameter $param API parameter being processed + * @param mixed $value Value to validate and process. The value may change during this process. + */ + protected function recursiveProcess(Parameter $param, &$value) + { + $type = $param->getType(); + + if (!is_array($value)) { + if ($type == 'array') { + // Cast to an array if the value was a string, but should be an array + $this->recursiveProcess($param->getItems(), $value); + $value = array($value); + } + } elseif ($type == 'object') { + $this->processObject($param, $value); + } elseif ($type == 'array') { + $this->processArray($param, $value); + } elseif ($type == 'string' && gettype($value) == 'array') { + $value = ''; + } + + if ($value !== null) { + $value = $param->filter($value); + } + } + + /** + * Process an array + * + * @param Parameter $param API parameter being parsed + * @param mixed $value Value to process + */ + protected function processArray(Parameter $param, &$value) + { + // Convert the node if it was meant to be an array + if (!isset($value[0])) { + // Collections fo nodes are sometimes wrapped in an additional array. For example: + // 12 should become: + // array('Items' => array(array('a' => 1), array('a' => 2)) + // Some nodes are not wrapped. For example: 12 + // should become array('Foo' => array(array('a' => 1), array('a' => 2)) + if ($param->getItems() && isset($value[$param->getItems()->getWireName()])) { + // Account for the case of a collection wrapping wrapped nodes: Items => Item[] + $value = $value[$param->getItems()->getWireName()]; + // If the wrapped node only had one value, then make it an array of nodes + if (!isset($value[0]) || !is_array($value)) { + $value = array($value); + } + } elseif (!empty($value)) { + // Account for repeated nodes that must be an array: Foo => Baz, Foo => Baz, but only if the + // value is set and not empty + $value = array($value); + } + } + + foreach ($value as &$item) { + $this->recursiveProcess($param->getItems(), $item); + } + } + + /** + * Process an object + * + * @param Parameter $param API parameter being parsed + * @param mixed $value Value to process + */ + protected function processObject(Parameter $param, &$value) + { + // Ensure that the array is associative and not numerically indexed + if (!isset($value[0]) && ($properties = $param->getProperties())) { + $knownProperties = array(); + foreach ($properties as $property) { + $name = $property->getName(); + $sentAs = $property->getWireName(); + $knownProperties[$name] = 1; + if ($property->getData('xmlAttribute')) { + $this->processXmlAttribute($property, $value); + } elseif (isset($value[$sentAs])) { + $this->recursiveProcess($property, $value[$sentAs]); + if ($name != $sentAs) { + $value[$name] = $value[$sentAs]; + unset($value[$sentAs]); + } + } + } + + // Remove any unknown and potentially unsafe properties + if ($param->getAdditionalProperties() === false) { + $value = array_intersect_key($value, $knownProperties); + } + } + } + + /** + * Process an XML attribute property + * + * @param Parameter $property Property to process + * @param array $value Value to process and update + */ + protected function processXmlAttribute(Parameter $property, array &$value) + { + $sentAs = $property->getWireName(); + if (isset($value['@attributes'][$sentAs])) { + $value[$property->getName()] = $value['@attributes'][$sentAs]; + unset($value['@attributes'][$sentAs]); + if (empty($value['@attributes'])) { + unset($value['@attributes']); + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php new file mode 100644 index 0000000..74cb628 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/LocationVisitor/VisitorFlyweight.php @@ -0,0 +1,138 @@ + 'Guzzle\Service\Command\LocationVisitor\Request\BodyVisitor', + 'request.header' => 'Guzzle\Service\Command\LocationVisitor\Request\HeaderVisitor', + 'request.json' => 'Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor', + 'request.postField' => 'Guzzle\Service\Command\LocationVisitor\Request\PostFieldVisitor', + 'request.postFile' => 'Guzzle\Service\Command\LocationVisitor\Request\PostFileVisitor', + 'request.query' => 'Guzzle\Service\Command\LocationVisitor\Request\QueryVisitor', + 'request.response_body' => 'Guzzle\Service\Command\LocationVisitor\Request\ResponseBodyVisitor', + 'request.responseBody' => 'Guzzle\Service\Command\LocationVisitor\Request\ResponseBodyVisitor', + 'request.xml' => 'Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor', + 'response.body' => 'Guzzle\Service\Command\LocationVisitor\Response\BodyVisitor', + 'response.header' => 'Guzzle\Service\Command\LocationVisitor\Response\HeaderVisitor', + 'response.json' => 'Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor', + 'response.reasonPhrase' => 'Guzzle\Service\Command\LocationVisitor\Response\ReasonPhraseVisitor', + 'response.statusCode' => 'Guzzle\Service\Command\LocationVisitor\Response\StatusCodeVisitor', + 'response.xml' => 'Guzzle\Service\Command\LocationVisitor\Response\XmlVisitor' + ); + + /** @var array Array of mappings of location names to classes */ + protected $mappings; + + /** @var array Cache of instantiated visitors */ + protected $cache = array(); + + /** + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * @param array $mappings Array mapping request.name and response.name to location visitor classes. Leave null to + * use the default values. + */ + public function __construct(array $mappings = null) + { + $this->mappings = $mappings === null ? self::$defaultMappings : $mappings; + } + + /** + * Get an instance of a request visitor by location name + * + * @param string $visitor Visitor name + * + * @return RequestVisitorInterface + */ + public function getRequestVisitor($visitor) + { + return $this->getKey('request.' . $visitor); + } + + /** + * Get an instance of a response visitor by location name + * + * @param string $visitor Visitor name + * + * @return ResponseVisitorInterface + */ + public function getResponseVisitor($visitor) + { + return $this->getKey('response.' . $visitor); + } + + /** + * Add a response visitor to the factory by name + * + * @param string $name Name of the visitor + * @param RequestVisitorInterface $visitor Visitor to add + * + * @return self + */ + public function addRequestVisitor($name, RequestVisitorInterface $visitor) + { + $this->cache['request.' . $name] = $visitor; + + return $this; + } + + /** + * Add a response visitor to the factory by name + * + * @param string $name Name of the visitor + * @param ResponseVisitorInterface $visitor Visitor to add + * + * @return self + */ + public function addResponseVisitor($name, ResponseVisitorInterface $visitor) + { + $this->cache['response.' . $name] = $visitor; + + return $this; + } + + /** + * Get a visitor by key value name + * + * @param string $key Key name to retrieve + * + * @return mixed + * @throws InvalidArgumentException + */ + private function getKey($key) + { + if (!isset($this->cache[$key])) { + if (!isset($this->mappings[$key])) { + list($type, $name) = explode('.', $key); + throw new InvalidArgumentException("No {$type} visitor has been mapped for {$name}"); + } + $this->cache[$key] = new $this->mappings[$key]; + } + + return $this->cache[$key]; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php new file mode 100644 index 0000000..0748b5a --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationCommand.php @@ -0,0 +1,89 @@ +responseParser = $parser; + + return $this; + } + + /** + * Set the request serializer used with the command + * + * @param RequestSerializerInterface $serializer Request serializer + * + * @return self + */ + public function setRequestSerializer(RequestSerializerInterface $serializer) + { + $this->requestSerializer = $serializer; + + return $this; + } + + /** + * Get the request serializer used with the command + * + * @return RequestSerializerInterface + */ + public function getRequestSerializer() + { + if (!$this->requestSerializer) { + // Use the default request serializer if none was found + $this->requestSerializer = DefaultRequestSerializer::getInstance(); + } + + return $this->requestSerializer; + } + + /** + * Get the response parser used for the operation + * + * @return ResponseParserInterface + */ + public function getResponseParser() + { + if (!$this->responseParser) { + // Use the default response parser if none was found + $this->responseParser = OperationResponseParser::getInstance(); + } + + return $this->responseParser; + } + + protected function build() + { + // Prepare and serialize the request + $this->request = $this->getRequestSerializer()->prepare($this); + } + + protected function process() + { + // Do not process the response if 'command.response_processing' is set to 'raw' + $this->result = $this[self::RESPONSE_PROCESSING] == self::TYPE_RAW + ? $this->request->getResponse() + : $this->getResponseParser()->parse($this); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php new file mode 100644 index 0000000..ca00bc0 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/OperationResponseParser.php @@ -0,0 +1,195 @@ +factory = $factory; + $this->schemaInModels = $schemaInModels; + } + + /** + * Add a location visitor to the command + * + * @param string $location Location to associate with the visitor + * @param ResponseVisitorInterface $visitor Visitor to attach + * + * @return self + */ + public function addVisitor($location, ResponseVisitorInterface $visitor) + { + $this->factory->addResponseVisitor($location, $visitor); + + return $this; + } + + protected function handleParsing(CommandInterface $command, Response $response, $contentType) + { + $operation = $command->getOperation(); + $type = $operation->getResponseType(); + $model = null; + + if ($type == OperationInterface::TYPE_MODEL) { + $model = $operation->getServiceDescription()->getModel($operation->getResponseClass()); + } elseif ($type == OperationInterface::TYPE_CLASS) { + return $this->parseClass($command); + } + + if (!$model) { + // Return basic processing if the responseType is not model or the model cannot be found + return parent::handleParsing($command, $response, $contentType); + } elseif ($command[AbstractCommand::RESPONSE_PROCESSING] != AbstractCommand::TYPE_MODEL) { + // Returns a model with no visiting if the command response processing is not model + return new Model(parent::handleParsing($command, $response, $contentType)); + } else { + // Only inject the schema into the model if "schemaInModel" is true + return new Model($this->visitResult($model, $command, $response), $this->schemaInModels ? $model : null); + } + } + + /** + * Parse a class object + * + * @param CommandInterface $command Command to parse into an object + * + * @return mixed + * @throws ResponseClassException + */ + protected function parseClass(CommandInterface $command) + { + // Emit the operation.parse_class event. If a listener injects a 'result' property, then that will be the result + $event = new CreateResponseClassEvent(array('command' => $command)); + $command->getClient()->getEventDispatcher()->dispatch('command.parse_response', $event); + if ($result = $event->getResult()) { + return $result; + } + + $className = $command->getOperation()->getResponseClass(); + if (!method_exists($className, 'fromCommand')) { + throw new ResponseClassException("{$className} must exist and implement a static fromCommand() method"); + } + + return $className::fromCommand($command); + } + + /** + * Perform transformations on the result array + * + * @param Parameter $model Model that defines the structure + * @param CommandInterface $command Command that performed the operation + * @param Response $response Response received + * + * @return array Returns the array of result data + */ + protected function visitResult(Parameter $model, CommandInterface $command, Response $response) + { + $foundVisitors = $result = $knownProps = array(); + $props = $model->getProperties(); + + foreach ($props as $schema) { + if ($location = $schema->getLocation()) { + // Trigger the before method on the first found visitor of this type + if (!isset($foundVisitors[$location])) { + $foundVisitors[$location] = $this->factory->getResponseVisitor($location); + $foundVisitors[$location]->before($command, $result); + } + } + } + + // Visit additional properties when it is an actual schema + if (($additional = $model->getAdditionalProperties()) instanceof Parameter) { + $this->visitAdditionalProperties($model, $command, $response, $additional, $result, $foundVisitors); + } + + // Apply the parameter value with the location visitor + foreach ($props as $schema) { + $knownProps[$schema->getName()] = 1; + if ($location = $schema->getLocation()) { + $foundVisitors[$location]->visit($command, $response, $schema, $result); + } + } + + // Remove any unknown and potentially unsafe top-level properties + if ($additional === false) { + $result = array_intersect_key($result, $knownProps); + } + + // Call the after() method of each found visitor + foreach ($foundVisitors as $visitor) { + $visitor->after($command); + } + + return $result; + } + + protected function visitAdditionalProperties( + Parameter $model, + CommandInterface $command, + Response $response, + Parameter $additional, + &$result, + array &$foundVisitors + ) { + // Only visit when a location is specified + if ($location = $additional->getLocation()) { + if (!isset($foundVisitors[$location])) { + $foundVisitors[$location] = $this->factory->getResponseVisitor($location); + $foundVisitors[$location]->before($command, $result); + } + // Only traverse if an array was parsed from the before() visitors + if (is_array($result)) { + // Find each additional property + foreach (array_keys($result) as $key) { + // Check if the model actually knows this property. If so, then it is not additional + if (!$model->getProperty($key)) { + // Set the name to the key so that we can parse it with each visitor + $additional->setName($key); + $foundVisitors[$location]->visit($command, $response, $additional, $result); + } + } + // Reset the additionalProperties name to null + $additional->setName(null); + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php new file mode 100644 index 0000000..60b9334 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Command/RequestSerializerInterface.php @@ -0,0 +1,21 @@ + true, 'httpMethod' => true, 'uri' => true, 'class' => true, 'responseClass' => true, + 'responseType' => true, 'responseNotes' => true, 'notes' => true, 'summary' => true, 'documentationUrl' => true, + 'deprecated' => true, 'data' => true, 'parameters' => true, 'additionalParameters' => true, + 'errorResponses' => true + ); + + /** @var array Parameters */ + protected $parameters = array(); + + /** @var Parameter Additional parameters schema */ + protected $additionalParameters; + + /** @var string Name of the command */ + protected $name; + + /** @var string HTTP method */ + protected $httpMethod; + + /** @var string This is a short summary of what the operation does */ + protected $summary; + + /** @var string A longer text field to explain the behavior of the operation. */ + protected $notes; + + /** @var string Reference URL providing more information about the operation */ + protected $documentationUrl; + + /** @var string HTTP URI of the command */ + protected $uri; + + /** @var string Class of the command object */ + protected $class; + + /** @var string This is what is returned from the method */ + protected $responseClass; + + /** @var string Type information about the response */ + protected $responseType; + + /** @var string Information about the response returned by the operation */ + protected $responseNotes; + + /** @var bool Whether or not the command is deprecated */ + protected $deprecated; + + /** @var array Array of errors that could occur when running the command */ + protected $errorResponses; + + /** @var ServiceDescriptionInterface */ + protected $description; + + /** @var array Extra operation information */ + protected $data; + + /** + * Builds an Operation object using an array of configuration data: + * - name: (string) Name of the command + * - httpMethod: (string) HTTP method of the operation + * - uri: (string) URI template that can create a relative or absolute URL + * - class: (string) Concrete class that implements this command + * - parameters: (array) Associative array of parameters for the command. {@see Parameter} for information. + * - summary: (string) This is a short summary of what the operation does + * - notes: (string) A longer text field to explain the behavior of the operation. + * - documentationUrl: (string) Reference URL providing more information about the operation + * - responseClass: (string) This is what is returned from the method. Can be a primitive, PSR-0 compliant + * class name, or model. + * - responseNotes: (string) Information about the response returned by the operation + * - responseType: (string) One of 'primitive', 'class', 'model', or 'documentation'. If not specified, this + * value will be automatically inferred based on whether or not there is a model matching the + * name, if a matching PSR-0 compliant class name is found, or set to 'primitive' by default. + * - deprecated: (bool) Set to true if this is a deprecated command + * - errorResponses: (array) Errors that could occur when executing the command. Array of hashes, each with a + * 'code' (the HTTP response code), 'reason' (response reason phrase or description of the + * error), and 'class' (a custom exception class that would be thrown if the error is + * encountered). + * - data: (array) Any extra data that might be used to help build or serialize the operation + * - additionalParameters: (null|array) Parameter schema to use when an option is passed to the operation that is + * not in the schema + * + * @param array $config Array of configuration data + * @param ServiceDescriptionInterface $description Service description used to resolve models if $ref tags are found + */ + public function __construct(array $config = array(), ServiceDescriptionInterface $description = null) + { + $this->description = $description; + + // Get the intersection of the available properties and properties set on the operation + foreach (array_intersect_key($config, self::$properties) as $key => $value) { + $this->{$key} = $value; + } + + $this->class = $this->class ?: self::DEFAULT_COMMAND_CLASS; + $this->deprecated = (bool) $this->deprecated; + $this->errorResponses = $this->errorResponses ?: array(); + $this->data = $this->data ?: array(); + + if (!$this->responseClass) { + $this->responseClass = 'array'; + $this->responseType = 'primitive'; + } elseif ($this->responseType) { + // Set the response type to perform validation + $this->setResponseType($this->responseType); + } else { + // A response class was set and no response type was set, so guess what the type is + $this->inferResponseType(); + } + + // Parameters need special handling when adding + if ($this->parameters) { + foreach ($this->parameters as $name => $param) { + if ($param instanceof Parameter) { + $param->setName($name)->setParent($this); + } elseif (is_array($param)) { + $param['name'] = $name; + $this->addParam(new Parameter($param, $this->description)); + } + } + } + + if ($this->additionalParameters) { + if ($this->additionalParameters instanceof Parameter) { + $this->additionalParameters->setParent($this); + } elseif (is_array($this->additionalParameters)) { + $this->setadditionalParameters(new Parameter($this->additionalParameters, $this->description)); + } + } + } + + public function toArray() + { + $result = array(); + // Grab valid properties and filter out values that weren't set + foreach (array_keys(self::$properties) as $check) { + if ($value = $this->{$check}) { + $result[$check] = $value; + } + } + // Remove the name property + unset($result['name']); + // Parameters need to be converted to arrays + $result['parameters'] = array(); + foreach ($this->parameters as $key => $param) { + $result['parameters'][$key] = $param->toArray(); + } + // Additional parameters need to be cast to an array + if ($this->additionalParameters instanceof Parameter) { + $result['additionalParameters'] = $this->additionalParameters->toArray(); + } + + return $result; + } + + public function getServiceDescription() + { + return $this->description; + } + + public function setServiceDescription(ServiceDescriptionInterface $description) + { + $this->description = $description; + + return $this; + } + + public function getParams() + { + return $this->parameters; + } + + public function getParamNames() + { + return array_keys($this->parameters); + } + + public function hasParam($name) + { + return isset($this->parameters[$name]); + } + + public function getParam($param) + { + return isset($this->parameters[$param]) ? $this->parameters[$param] : null; + } + + /** + * Add a parameter to the command + * + * @param Parameter $param Parameter to add + * + * @return self + */ + public function addParam(Parameter $param) + { + $this->parameters[$param->getName()] = $param; + $param->setParent($this); + + return $this; + } + + /** + * Remove a parameter from the command + * + * @param string $name Name of the parameter to remove + * + * @return self + */ + public function removeParam($name) + { + unset($this->parameters[$name]); + + return $this; + } + + public function getHttpMethod() + { + return $this->httpMethod; + } + + /** + * Set the HTTP method of the command + * + * @param string $httpMethod Method to set + * + * @return self + */ + public function setHttpMethod($httpMethod) + { + $this->httpMethod = $httpMethod; + + return $this; + } + + public function getClass() + { + return $this->class; + } + + /** + * Set the concrete class of the command + * + * @param string $className Concrete class name + * + * @return self + */ + public function setClass($className) + { + $this->class = $className; + + return $this; + } + + public function getName() + { + return $this->name; + } + + /** + * Set the name of the command + * + * @param string $name Name of the command + * + * @return self + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + public function getSummary() + { + return $this->summary; + } + + /** + * Set a short summary of what the operation does + * + * @param string $summary Short summary of the operation + * + * @return self + */ + public function setSummary($summary) + { + $this->summary = $summary; + + return $this; + } + + public function getNotes() + { + return $this->notes; + } + + /** + * Set a longer text field to explain the behavior of the operation. + * + * @param string $notes Notes on the operation + * + * @return self + */ + public function setNotes($notes) + { + $this->notes = $notes; + + return $this; + } + + public function getDocumentationUrl() + { + return $this->documentationUrl; + } + + /** + * Set the URL pointing to additional documentation on the command + * + * @param string $docUrl Documentation URL + * + * @return self + */ + public function setDocumentationUrl($docUrl) + { + $this->documentationUrl = $docUrl; + + return $this; + } + + public function getResponseClass() + { + return $this->responseClass; + } + + /** + * Set what is returned from the method. Can be a primitive, class name, or model. For example: 'array', + * 'Guzzle\\Foo\\Baz', or 'MyModelName' (to reference a model by ID). + * + * @param string $responseClass Type of response + * + * @return self + */ + public function setResponseClass($responseClass) + { + $this->responseClass = $responseClass; + $this->inferResponseType(); + + return $this; + } + + public function getResponseType() + { + return $this->responseType; + } + + /** + * Set qualifying information about the responseClass. One of 'primitive', 'class', 'model', or 'documentation' + * + * @param string $responseType Response type information + * + * @return self + * @throws InvalidArgumentException + */ + public function setResponseType($responseType) + { + static $types = array( + self::TYPE_PRIMITIVE => true, + self::TYPE_CLASS => true, + self::TYPE_MODEL => true, + self::TYPE_DOCUMENTATION => true + ); + if (!isset($types[$responseType])) { + throw new InvalidArgumentException('responseType must be one of ' . implode(', ', array_keys($types))); + } + + $this->responseType = $responseType; + + return $this; + } + + public function getResponseNotes() + { + return $this->responseNotes; + } + + /** + * Set notes about the response of the operation + * + * @param string $notes Response notes + * + * @return self + */ + public function setResponseNotes($notes) + { + $this->responseNotes = $notes; + + return $this; + } + + public function getDeprecated() + { + return $this->deprecated; + } + + /** + * Set whether or not the command is deprecated + * + * @param bool $isDeprecated Set to true to mark as deprecated + * + * @return self + */ + public function setDeprecated($isDeprecated) + { + $this->deprecated = $isDeprecated; + + return $this; + } + + public function getUri() + { + return $this->uri; + } + + /** + * Set the URI template of the command + * + * @param string $uri URI template to set + * + * @return self + */ + public function setUri($uri) + { + $this->uri = $uri; + + return $this; + } + + public function getErrorResponses() + { + return $this->errorResponses; + } + + /** + * Add an error to the command + * + * @param string $code HTTP response code + * @param string $reason HTTP response reason phrase or information about the error + * @param string $class Exception class associated with the error + * + * @return self + */ + public function addErrorResponse($code, $reason, $class) + { + $this->errorResponses[] = array('code' => $code, 'reason' => $reason, 'class' => $class); + + return $this; + } + + /** + * Set all of the error responses of the operation + * + * @param array $errorResponses Hash of error name to a hash containing a code, reason, class + * + * @return self + */ + public function setErrorResponses(array $errorResponses) + { + $this->errorResponses = $errorResponses; + + return $this; + } + + public function getData($name) + { + return isset($this->data[$name]) ? $this->data[$name] : null; + } + + /** + * Set a particular data point on the operation + * + * @param string $name Name of the data value + * @param mixed $value Value to set + * + * @return self + */ + public function setData($name, $value) + { + $this->data[$name] = $value; + + return $this; + } + + /** + * Get the additionalParameters of the operation + * + * @return Parameter|null + */ + public function getAdditionalParameters() + { + return $this->additionalParameters; + } + + /** + * Set the additionalParameters of the operation + * + * @param Parameter|null $parameter Parameter to set + * + * @return self + */ + public function setAdditionalParameters($parameter) + { + if ($this->additionalParameters = $parameter) { + $this->additionalParameters->setParent($this); + } + + return $this; + } + + /** + * Infer the response type from the responseClass value + */ + protected function inferResponseType() + { + static $primitives = array('array' => 1, 'boolean' => 1, 'string' => 1, 'integer' => 1, '' => 1); + if (isset($primitives[$this->responseClass])) { + $this->responseType = self::TYPE_PRIMITIVE; + } elseif ($this->description && $this->description->hasModel($this->responseClass)) { + $this->responseType = self::TYPE_MODEL; + } else { + $this->responseType = self::TYPE_CLASS; + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php new file mode 100644 index 0000000..4de41bd --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/OperationInterface.php @@ -0,0 +1,159 @@ +getModel($data['$ref'])) { + $data = $model->toArray() + $data; + } + } elseif (isset($data['extends'])) { + // If this parameter extends from another parameter then start with the actual data + // union in the parent's data (e.g. actual supersedes parent) + if ($extends = $description->getModel($data['extends'])) { + $data += $extends->toArray(); + } + } + } + + // Pull configuration data into the parameter + foreach ($data as $key => $value) { + $this->{$key} = $value; + } + + $this->serviceDescription = $description; + $this->required = (bool) $this->required; + $this->data = (array) $this->data; + + if ($this->filters) { + $this->setFilters((array) $this->filters); + } + + if ($this->type == 'object' && $this->additionalProperties === null) { + $this->additionalProperties = true; + } + } + + /** + * Convert the object to an array + * + * @return array + */ + public function toArray() + { + static $checks = array('required', 'description', 'static', 'type', 'format', 'instanceOf', 'location', 'sentAs', + 'pattern', 'minimum', 'maximum', 'minItems', 'maxItems', 'minLength', 'maxLength', 'data', 'enum', + 'filters'); + + $result = array(); + + // Anything that is in the `Items` attribute of an array *must* include it's name if available + if ($this->parent instanceof self && $this->parent->getType() == 'array' && isset($this->name)) { + $result['name'] = $this->name; + } + + foreach ($checks as $c) { + if ($value = $this->{$c}) { + $result[$c] = $value; + } + } + + if ($this->default !== null) { + $result['default'] = $this->default; + } + + if ($this->items !== null) { + $result['items'] = $this->getItems()->toArray(); + } + + if ($this->additionalProperties !== null) { + $result['additionalProperties'] = $this->getAdditionalProperties(); + if ($result['additionalProperties'] instanceof self) { + $result['additionalProperties'] = $result['additionalProperties']->toArray(); + } + } + + if ($this->type == 'object' && $this->properties) { + $result['properties'] = array(); + foreach ($this->getProperties() as $name => $property) { + $result['properties'][$name] = $property->toArray(); + } + } + + return $result; + } + + /** + * Get the default or static value of the command based on a value + * + * @param string $value Value that is currently set + * + * @return mixed Returns the value, a static value if one is present, or a default value + */ + public function getValue($value) + { + if ($this->static || ($this->default !== null && $value === null)) { + return $this->default; + } + + return $value; + } + + /** + * Run a value through the filters OR format attribute associated with the parameter + * + * @param mixed $value Value to filter + * + * @return mixed Returns the filtered value + */ + public function filter($value) + { + // Formats are applied exclusively and supersed filters + if ($this->format) { + return SchemaFormatter::format($this->format, $value); + } + + // Convert Boolean values + if ($this->type == 'boolean' && !is_bool($value)) { + $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + + // Apply filters to the value + if ($this->filters) { + foreach ($this->filters as $filter) { + if (is_array($filter)) { + // Convert complex filters that hold value place holders + foreach ($filter['args'] as &$data) { + if ($data == '@value') { + $data = $value; + } elseif ($data == '@api') { + $data = $this; + } + } + $value = call_user_func_array($filter['method'], $filter['args']); + } else { + $value = call_user_func($filter, $value); + } + } + } + + return $value; + } + + /** + * Get the name of the parameter + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get the key of the parameter, where sentAs will supersede name if it is set + * + * @return string + */ + public function getWireName() + { + return $this->sentAs ?: $this->name; + } + + /** + * Set the name of the parameter + * + * @param string $name Name to set + * + * @return self + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get the type(s) of the parameter + * + * @return string|array + */ + public function getType() + { + return $this->type; + } + + /** + * Set the type(s) of the parameter + * + * @param string|array $type Type of parameter or array of simple types used in a union + * + * @return self + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * Get if the parameter is required + * + * @return bool + */ + public function getRequired() + { + return $this->required; + } + + /** + * Set if the parameter is required + * + * @param bool $isRequired Whether or not the parameter is required + * + * @return self + */ + public function setRequired($isRequired) + { + $this->required = (bool) $isRequired; + + return $this; + } + + /** + * Get the default value of the parameter + * + * @return string|null + */ + public function getDefault() + { + return $this->default; + } + + /** + * Set the default value of the parameter + * + * @param string|null $default Default value to set + * + * @return self + */ + public function setDefault($default) + { + $this->default = $default; + + return $this; + } + + /** + * Get the description of the parameter + * + * @return string|null + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set the description of the parameter + * + * @param string $description Description + * + * @return self + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Get the minimum acceptable value for an integer + * + * @return int|null + */ + public function getMinimum() + { + return $this->minimum; + } + + /** + * Set the minimum acceptable value for an integer + * + * @param int|null $min Minimum + * + * @return self + */ + public function setMinimum($min) + { + $this->minimum = $min; + + return $this; + } + + /** + * Get the maximum acceptable value for an integer + * + * @return int|null + */ + public function getMaximum() + { + return $this->maximum; + } + + /** + * Set the maximum acceptable value for an integer + * + * @param int $max Maximum + * + * @return self + */ + public function setMaximum($max) + { + $this->maximum = $max; + + return $this; + } + + /** + * Get the minimum allowed length of a string value + * + * @return int + */ + public function getMinLength() + { + return $this->minLength; + } + + /** + * Set the minimum allowed length of a string value + * + * @param int|null $min Minimum + * + * @return self + */ + public function setMinLength($min) + { + $this->minLength = $min; + + return $this; + } + + /** + * Get the maximum allowed length of a string value + * + * @return int|null + */ + public function getMaxLength() + { + return $this->maxLength; + } + + /** + * Set the maximum allowed length of a string value + * + * @param int $max Maximum length + * + * @return self + */ + public function setMaxLength($max) + { + $this->maxLength = $max; + + return $this; + } + + /** + * Get the maximum allowed number of items in an array value + * + * @return int|null + */ + public function getMaxItems() + { + return $this->maxItems; + } + + /** + * Set the maximum allowed number of items in an array value + * + * @param int $max Maximum + * + * @return self + */ + public function setMaxItems($max) + { + $this->maxItems = $max; + + return $this; + } + + /** + * Get the minimum allowed number of items in an array value + * + * @return int + */ + public function getMinItems() + { + return $this->minItems; + } + + /** + * Set the minimum allowed number of items in an array value + * + * @param int|null $min Minimum + * + * @return self + */ + public function setMinItems($min) + { + $this->minItems = $min; + + return $this; + } + + /** + * Get the location of the parameter + * + * @return string|null + */ + public function getLocation() + { + return $this->location; + } + + /** + * Set the location of the parameter + * + * @param string|null $location Location of the parameter + * + * @return self + */ + public function setLocation($location) + { + $this->location = $location; + + return $this; + } + + /** + * Get the sentAs attribute of the parameter that used with locations to sentAs an attribute when it is being + * applied to a location. + * + * @return string|null + */ + public function getSentAs() + { + return $this->sentAs; + } + + /** + * Set the sentAs attribute + * + * @param string|null $name Name of the value as it is sent over the wire + * + * @return self + */ + public function setSentAs($name) + { + $this->sentAs = $name; + + return $this; + } + + /** + * Retrieve a known property from the parameter by name or a data property by name. When not specific name value + * is specified, all data properties will be returned. + * + * @param string|null $name Specify a particular property name to retrieve + * + * @return array|mixed|null + */ + public function getData($name = null) + { + if (!$name) { + return $this->data; + } + + if (isset($this->data[$name])) { + return $this->data[$name]; + } elseif (isset($this->{$name})) { + return $this->{$name}; + } + + return null; + } + + /** + * Set the extra data properties of the parameter or set a specific extra property + * + * @param string|array|null $nameOrData The name of a specific extra to set or an array of extras to set + * @param mixed|null $data When setting a specific extra property, specify the data to set for it + * + * @return self + */ + public function setData($nameOrData, $data = null) + { + if (is_array($nameOrData)) { + $this->data = $nameOrData; + } else { + $this->data[$nameOrData] = $data; + } + + return $this; + } + + /** + * Get whether or not the default value can be changed + * + * @return mixed|null + */ + public function getStatic() + { + return $this->static; + } + + /** + * Set to true if the default value cannot be changed + * + * @param bool $static True or false + * + * @return self + */ + public function setStatic($static) + { + $this->static = (bool) $static; + + return $this; + } + + /** + * Get an array of filters used by the parameter + * + * @return array + */ + public function getFilters() + { + return $this->filters ?: array(); + } + + /** + * Set the array of filters used by the parameter + * + * @param array $filters Array of functions to use as filters + * + * @return self + */ + public function setFilters(array $filters) + { + $this->filters = array(); + foreach ($filters as $filter) { + $this->addFilter($filter); + } + + return $this; + } + + /** + * Add a filter to the parameter + * + * @param string|array $filter Method to filter the value through + * + * @return self + * @throws InvalidArgumentException + */ + public function addFilter($filter) + { + if (is_array($filter)) { + if (!isset($filter['method'])) { + throw new InvalidArgumentException('A [method] value must be specified for each complex filter'); + } + } + + if (!$this->filters) { + $this->filters = array($filter); + } else { + $this->filters[] = $filter; + } + + return $this; + } + + /** + * Get the parent object (an {@see OperationInterface} or {@see Parameter} + * + * @return OperationInterface|Parameter|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * Set the parent object of the parameter + * + * @param OperationInterface|Parameter|null $parent Parent container of the parameter + * + * @return self + */ + public function setParent($parent) + { + $this->parent = $parent; + + return $this; + } + + /** + * Get the properties of the parameter + * + * @return array + */ + public function getProperties() + { + if (!$this->propertiesCache) { + $this->propertiesCache = array(); + foreach (array_keys($this->properties) as $name) { + $this->propertiesCache[$name] = $this->getProperty($name); + } + } + + return $this->propertiesCache; + } + + /** + * Get a specific property from the parameter + * + * @param string $name Name of the property to retrieve + * + * @return null|Parameter + */ + public function getProperty($name) + { + if (!isset($this->properties[$name])) { + return null; + } + + if (!($this->properties[$name] instanceof self)) { + $this->properties[$name]['name'] = $name; + $this->properties[$name] = new static($this->properties[$name], $this->serviceDescription); + $this->properties[$name]->setParent($this); + } + + return $this->properties[$name]; + } + + /** + * Remove a property from the parameter + * + * @param string $name Name of the property to remove + * + * @return self + */ + public function removeProperty($name) + { + unset($this->properties[$name]); + $this->propertiesCache = null; + + return $this; + } + + /** + * Add a property to the parameter + * + * @param Parameter $property Properties to set + * + * @return self + */ + public function addProperty(Parameter $property) + { + $this->properties[$property->getName()] = $property; + $property->setParent($this); + $this->propertiesCache = null; + + return $this; + } + + /** + * Get the additionalProperties value of the parameter + * + * @return bool|Parameter|null + */ + public function getAdditionalProperties() + { + if (is_array($this->additionalProperties)) { + $this->additionalProperties = new static($this->additionalProperties, $this->serviceDescription); + $this->additionalProperties->setParent($this); + } + + return $this->additionalProperties; + } + + /** + * Set the additionalProperties value of the parameter + * + * @param bool|Parameter|null $additional Boolean to allow any, an Parameter to specify a schema, or false to disallow + * + * @return self + */ + public function setAdditionalProperties($additional) + { + $this->additionalProperties = $additional; + + return $this; + } + + /** + * Set the items data of the parameter + * + * @param Parameter|null $items Items to set + * + * @return self + */ + public function setItems(Parameter $items = null) + { + if ($this->items = $items) { + $this->items->setParent($this); + } + + return $this; + } + + /** + * Get the item data of the parameter + * + * @return Parameter|null + */ + public function getItems() + { + if (is_array($this->items)) { + $this->items = new static($this->items, $this->serviceDescription); + $this->items->setParent($this); + } + + return $this->items; + } + + /** + * Get the class that the parameter must implement + * + * @return null|string + */ + public function getInstanceOf() + { + return $this->instanceOf; + } + + /** + * Set the class that the parameter must be an instance of + * + * @param string|null $instanceOf Class or interface name + * + * @return self + */ + public function setInstanceOf($instanceOf) + { + $this->instanceOf = $instanceOf; + + return $this; + } + + /** + * Get the enum of strings that are valid for the parameter + * + * @return array|null + */ + public function getEnum() + { + return $this->enum; + } + + /** + * Set the enum of strings that are valid for the parameter + * + * @param array|null $enum Array of strings or null + * + * @return self + */ + public function setEnum(array $enum = null) + { + $this->enum = $enum; + + return $this; + } + + /** + * Get the regex pattern that must match a value when the value is a string + * + * @return string + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Set the regex pattern that must match a value when the value is a string + * + * @param string $pattern Regex pattern + * + * @return self + */ + public function setPattern($pattern) + { + $this->pattern = $pattern; + + return $this; + } + + /** + * Get the format attribute of the schema + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set the format attribute of the schema + * + * @param string $format Format to set (e.g. date, date-time, timestamp, time, date-time-http) + * + * @return self + */ + public function setFormat($format) + { + $this->format = $format; + + return $this; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php new file mode 100644 index 0000000..7f47fc9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaFormatter.php @@ -0,0 +1,156 @@ +setTimezone(self::getUtcTimeZone())->format($format); + } + + throw new InvalidArgumentException('Date/Time values must be either a string, integer, or DateTime object'); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php new file mode 100644 index 0000000..b045422 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/SchemaValidator.php @@ -0,0 +1,291 @@ +castIntegerToStringType = $castIntegerToStringType; + } + + public function validate(Parameter $param, &$value) + { + $this->errors = array(); + $this->recursiveProcess($param, $value); + + if (empty($this->errors)) { + return true; + } else { + sort($this->errors); + return false; + } + } + + /** + * Get the errors encountered while validating + * + * @return array + */ + public function getErrors() + { + return $this->errors ?: array(); + } + + /** + * Recursively validate a parameter + * + * @param Parameter $param API parameter being validated + * @param mixed $value Value to validate and validate. The value may change during this validate. + * @param string $path Current validation path (used for error reporting) + * @param int $depth Current depth in the validation validate + * + * @return bool Returns true if valid, or false if invalid + */ + protected function recursiveProcess(Parameter $param, &$value, $path = '', $depth = 0) + { + // Update the value by adding default or static values + $value = $param->getValue($value); + + $required = $param->getRequired(); + // if the value is null and the parameter is not required or is static, then skip any further recursion + if ((null === $value && !$required) || $param->getStatic()) { + return true; + } + + $type = $param->getType(); + // Attempt to limit the number of times is_array is called by tracking if the value is an array + $valueIsArray = is_array($value); + // If a name is set then update the path so that validation messages are more helpful + if ($name = $param->getName()) { + $path .= "[{$name}]"; + } + + if ($type == 'object') { + + // Objects are either associative arrays, ToArrayInterface, or some other object + if ($param->getInstanceOf()) { + $instance = $param->getInstanceOf(); + if (!($value instanceof $instance)) { + $this->errors[] = "{$path} must be an instance of {$instance}"; + return false; + } + } + + // Determine whether or not this "value" has properties and should be traversed + $traverse = $temporaryValue = false; + + // Convert the value to an array + if (!$valueIsArray && $value instanceof ToArrayInterface) { + $value = $value->toArray(); + } + + if ($valueIsArray) { + // Ensure that the array is associative and not numerically indexed + if (isset($value[0])) { + $this->errors[] = "{$path} must be an array of properties. Got a numerically indexed array."; + return false; + } + $traverse = true; + } elseif ($value === null) { + // Attempt to let the contents be built up by default values if possible + $value = array(); + $temporaryValue = $valueIsArray = $traverse = true; + } + + if ($traverse) { + + if ($properties = $param->getProperties()) { + // if properties were found, the validate each property of the value + foreach ($properties as $property) { + $name = $property->getName(); + if (isset($value[$name])) { + $this->recursiveProcess($property, $value[$name], $path, $depth + 1); + } else { + $current = null; + $this->recursiveProcess($property, $current, $path, $depth + 1); + // Only set the value if it was populated with something + if (null !== $current) { + $value[$name] = $current; + } + } + } + } + + $additional = $param->getAdditionalProperties(); + if ($additional !== true) { + // If additional properties were found, then validate each against the additionalProperties attr. + $keys = array_keys($value); + // Determine the keys that were specified that were not listed in the properties of the schema + $diff = array_diff($keys, array_keys($properties)); + if (!empty($diff)) { + // Determine which keys are not in the properties + if ($additional instanceOf Parameter) { + foreach ($diff as $key) { + $this->recursiveProcess($additional, $value[$key], "{$path}[{$key}]", $depth); + } + } else { + // if additionalProperties is set to false and there are additionalProperties in the values, then fail + foreach ($diff as $prop) { + $this->errors[] = sprintf('%s[%s] is not an allowed property', $path, $prop); + } + } + } + } + + // A temporary value will be used to traverse elements that have no corresponding input value. + // This allows nested required parameters with default values to bubble up into the input. + // Here we check if we used a temp value and nothing bubbled up, then we need to remote the value. + if ($temporaryValue && empty($value)) { + $value = null; + $valueIsArray = false; + } + } + + } elseif ($type == 'array' && $valueIsArray && $param->getItems()) { + foreach ($value as $i => &$item) { + // Validate each item in an array against the items attribute of the schema + $this->recursiveProcess($param->getItems(), $item, $path . "[{$i}]", $depth + 1); + } + } + + // If the value is required and the type is not null, then there is an error if the value is not set + if ($required && $value === null && $type != 'null') { + $message = "{$path} is " . ($param->getType() ? ('a required ' . implode(' or ', (array) $param->getType())) : 'required'); + if ($param->getDescription()) { + $message .= ': ' . $param->getDescription(); + } + $this->errors[] = $message; + return false; + } + + // Validate that the type is correct. If the type is string but an integer was passed, the class can be + // instructed to cast the integer to a string to pass validation. This is the default behavior. + if ($type && (!$type = $this->determineType($type, $value))) { + if ($this->castIntegerToStringType && $param->getType() == 'string' && is_integer($value)) { + $value = (string) $value; + } else { + $this->errors[] = "{$path} must be of type " . implode(' or ', (array) $param->getType()); + } + } + + // Perform type specific validation for strings, arrays, and integers + if ($type == 'string') { + + // Strings can have enums which are a list of predefined values + if (($enum = $param->getEnum()) && !in_array($value, $enum)) { + $this->errors[] = "{$path} must be one of " . implode(' or ', array_map(function ($s) { + return '"' . addslashes($s) . '"'; + }, $enum)); + } + // Strings can have a regex pattern that the value must match + if (($pattern = $param->getPattern()) && !preg_match($pattern, $value)) { + $this->errors[] = "{$path} must match the following regular expression: {$pattern}"; + } + + $strLen = null; + if ($min = $param->getMinLength()) { + $strLen = strlen($value); + if ($strLen < $min) { + $this->errors[] = "{$path} length must be greater than or equal to {$min}"; + } + } + if ($max = $param->getMaxLength()) { + if (($strLen ?: strlen($value)) > $max) { + $this->errors[] = "{$path} length must be less than or equal to {$max}"; + } + } + + } elseif ($type == 'array') { + + $size = null; + if ($min = $param->getMinItems()) { + $size = count($value); + if ($size < $min) { + $this->errors[] = "{$path} must contain {$min} or more elements"; + } + } + if ($max = $param->getMaxItems()) { + if (($size ?: count($value)) > $max) { + $this->errors[] = "{$path} must contain {$max} or fewer elements"; + } + } + + } elseif ($type == 'integer' || $type == 'number' || $type == 'numeric') { + if (($min = $param->getMinimum()) && $value < $min) { + $this->errors[] = "{$path} must be greater than or equal to {$min}"; + } + if (($max = $param->getMaximum()) && $value > $max) { + $this->errors[] = "{$path} must be less than or equal to {$max}"; + } + } + + return empty($this->errors); + } + + /** + * From the allowable types, determine the type that the variable matches + * + * @param string $type Parameter type + * @param mixed $value Value to determine the type + * + * @return string|bool Returns the matching type on + */ + protected function determineType($type, $value) + { + foreach ((array) $type as $t) { + if ($t == 'string' && (is_string($value) || (is_object($value) && method_exists($value, '__toString')))) { + return 'string'; + } elseif ($t == 'object' && (is_array($value) || is_object($value))) { + return 'object'; + } elseif ($t == 'array' && is_array($value)) { + return 'array'; + } elseif ($t == 'integer' && is_integer($value)) { + return 'integer'; + } elseif ($t == 'boolean' && is_bool($value)) { + return 'boolean'; + } elseif ($t == 'number' && is_numeric($value)) { + return 'number'; + } elseif ($t == 'numeric' && is_numeric($value)) { + return 'numeric'; + } elseif ($t == 'null' && !$value) { + return 'null'; + } elseif ($t == 'any') { + return 'any'; + } + } + + return false; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php new file mode 100644 index 0000000..286e65e --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescription.php @@ -0,0 +1,271 @@ +load($config, $options); + } + + /** + * @param array $config Array of configuration data + */ + public function __construct(array $config = array()) + { + $this->fromArray($config); + } + + public function serialize() + { + return json_encode($this->toArray()); + } + + public function unserialize($json) + { + $this->operations = array(); + $this->fromArray(json_decode($json, true)); + } + + public function toArray() + { + $result = array( + 'name' => $this->name, + 'apiVersion' => $this->apiVersion, + 'baseUrl' => $this->baseUrl, + 'description' => $this->description + ) + $this->extraData; + $result['operations'] = array(); + foreach ($this->getOperations() as $name => $operation) { + $result['operations'][$operation->getName() ?: $name] = $operation->toArray(); + } + if (!empty($this->models)) { + $result['models'] = array(); + foreach ($this->models as $id => $model) { + $result['models'][$id] = $model instanceof Parameter ? $model->toArray(): $model; + } + } + + return array_filter($result); + } + + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Set the baseUrl of the description + * + * @param string $baseUrl Base URL of each operation + * + * @return self + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + + return $this; + } + + public function getOperations() + { + foreach (array_keys($this->operations) as $name) { + $this->getOperation($name); + } + + return $this->operations; + } + + public function hasOperation($name) + { + return isset($this->operations[$name]); + } + + public function getOperation($name) + { + // Lazily retrieve and build operations + if (!isset($this->operations[$name])) { + return null; + } + + if (!($this->operations[$name] instanceof Operation)) { + $this->operations[$name] = new Operation($this->operations[$name], $this); + } + + return $this->operations[$name]; + } + + /** + * Add a operation to the service description + * + * @param OperationInterface $operation Operation to add + * + * @return self + */ + public function addOperation(OperationInterface $operation) + { + $this->operations[$operation->getName()] = $operation->setServiceDescription($this); + + return $this; + } + + public function getModel($id) + { + if (!isset($this->models[$id])) { + return null; + } + + if (!($this->models[$id] instanceof Parameter)) { + $this->models[$id] = new Parameter($this->models[$id] + array('name' => $id), $this); + } + + return $this->models[$id]; + } + + public function getModels() + { + // Ensure all models are converted into parameter objects + foreach (array_keys($this->models) as $id) { + $this->getModel($id); + } + + return $this->models; + } + + public function hasModel($id) + { + return isset($this->models[$id]); + } + + /** + * Add a model to the service description + * + * @param Parameter $model Model to add + * + * @return self + */ + public function addModel(Parameter $model) + { + $this->models[$model->getName()] = $model; + + return $this; + } + + public function getApiVersion() + { + return $this->apiVersion; + } + + public function getName() + { + return $this->name; + } + + public function getDescription() + { + return $this->description; + } + + public function getData($key) + { + return isset($this->extraData[$key]) ? $this->extraData[$key] : null; + } + + public function setData($key, $value) + { + $this->extraData[$key] = $value; + + return $this; + } + + /** + * Initialize the state from an array + * + * @param array $config Configuration data + * @throws InvalidArgumentException + */ + protected function fromArray(array $config) + { + // Keep a list of default keys used in service descriptions that is later used to determine extra data keys + static $defaultKeys = array('name', 'models', 'apiVersion', 'baseUrl', 'description'); + // Pull in the default configuration values + foreach ($defaultKeys as $key) { + if (isset($config[$key])) { + $this->{$key} = $config[$key]; + } + } + + // Account for the Swagger name for Guzzle's baseUrl + if (isset($config['basePath'])) { + $this->baseUrl = $config['basePath']; + } + + // Ensure that the models and operations properties are always arrays + $this->models = (array) $this->models; + $this->operations = (array) $this->operations; + + // We want to add operations differently than adding the other properties + $defaultKeys[] = 'operations'; + + // Create operations for each operation + if (isset($config['operations'])) { + foreach ($config['operations'] as $name => $operation) { + if (!($operation instanceof Operation) && !is_array($operation)) { + throw new InvalidArgumentException('Invalid operation in service description: ' + . gettype($operation)); + } + $this->operations[$name] = $operation; + } + } + + // Get all of the additional properties of the service description and store them in a data array + foreach (array_diff(array_keys($config), $defaultKeys) as $key) { + $this->extraData[$key] = $config[$key]; + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php new file mode 100644 index 0000000..5983e58 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ServiceDescriptionInterface.php @@ -0,0 +1,106 @@ + $op) { + $name = $op['name'] = isset($op['name']) ? $op['name'] : $name; + // Extend other operations + if (!empty($op['extends'])) { + $this->resolveExtension($name, $op, $operations); + } + $op['parameters'] = isset($op['parameters']) ? $op['parameters'] : array(); + $operations[$name] = $op; + } + } + + return new ServiceDescription(array( + 'apiVersion' => isset($config['apiVersion']) ? $config['apiVersion'] : null, + 'baseUrl' => isset($config['baseUrl']) ? $config['baseUrl'] : null, + 'description' => isset($config['description']) ? $config['description'] : null, + 'operations' => $operations, + 'models' => isset($config['models']) ? $config['models'] : null + ) + $config); + } + + /** + * @param string $name Name of the operation + * @param array $op Operation value array + * @param array $operations Currently loaded operations + * @throws DescriptionBuilderException when extending a non-existent operation + */ + protected function resolveExtension($name, array &$op, array &$operations) + { + $resolved = array(); + $original = empty($op['parameters']) ? false: $op['parameters']; + $hasClass = !empty($op['class']); + foreach ((array) $op['extends'] as $extendedCommand) { + if (empty($operations[$extendedCommand])) { + throw new DescriptionBuilderException("{$name} extends missing operation {$extendedCommand}"); + } + $toArray = $operations[$extendedCommand]; + $resolved = empty($resolved) + ? $toArray['parameters'] + : array_merge($resolved, $toArray['parameters']); + + $op = $op + $toArray; + if (!$hasClass && isset($toArray['class'])) { + $op['class'] = $toArray['class']; + } + } + $op['parameters'] = $original ? array_merge($resolved, $original) : $resolved; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php new file mode 100644 index 0000000..94ca77d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Description/ValidatorInterface.php @@ -0,0 +1,28 @@ +getMessage(), $e->getCode(), $e->getPrevious()); + $ce->setSuccessfulRequests($e->getSuccessfulRequests()); + + $alreadyAddedExceptions = array(); + foreach ($e->getFailedRequests() as $request) { + if ($re = $e->getExceptionForFailedRequest($request)) { + $alreadyAddedExceptions[] = $re; + $ce->addFailedRequestWithException($request, $re); + } else { + $ce->addFailedRequest($request); + } + } + + // Add any exceptions that did not map to a request + if (count($alreadyAddedExceptions) < count($e)) { + foreach ($e as $ex) { + if (!in_array($ex, $alreadyAddedExceptions)) { + $ce->add($ex); + } + } + } + + return $ce; + } + + /** + * Get all of the commands in the transfer + * + * @return array + */ + public function getAllCommands() + { + return array_merge($this->successfulCommands, $this->failedCommands); + } + + /** + * Add to the array of successful commands + * + * @param CommandInterface $command Successful command + * + * @return self + */ + public function addSuccessfulCommand(CommandInterface $command) + { + $this->successfulCommands[] = $command; + + return $this; + } + + /** + * Add to the array of failed commands + * + * @param CommandInterface $command Failed command + * + * @return self + */ + public function addFailedCommand(CommandInterface $command) + { + $this->failedCommands[] = $command; + + return $this; + } + + /** + * Get an array of successful commands + * + * @return array + */ + public function getSuccessfulCommands() + { + return $this->successfulCommands; + } + + /** + * Get an array of failed commands + * + * @return array + */ + public function getFailedCommands() + { + return $this->failedCommands; + } + + /** + * Get the Exception that caused the given $command to fail + * + * @param CommandInterface $command Failed command + * + * @return \Exception|null + */ + public function getExceptionForFailedCommand(CommandInterface $command) + { + return $this->getExceptionForFailedRequest($command->getRequest()); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php new file mode 100644 index 0000000..1407e56 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/DescriptionBuilderException.php @@ -0,0 +1,7 @@ +invalidCommands = $commands; + parent::__construct( + 'Encountered commands in a batch transfer that use inconsistent clients. The batching ' . + 'strategy you use with a command transfer must divide command batches by client.' + ); + } + + /** + * Get the invalid commands + * + * @return array + */ + public function getCommands() + { + return $this->invalidCommands; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php new file mode 100644 index 0000000..d59ff21 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Exception/ResponseClassException.php @@ -0,0 +1,9 @@ +errors = $errors; + } + + /** + * Get any validation errors + * + * @return array + */ + public function getErrors() + { + return $this->errors; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php new file mode 100644 index 0000000..21140e7 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/AbstractResourceIteratorFactory.php @@ -0,0 +1,37 @@ +canBuild($command)) { + throw new InvalidArgumentException('Iterator was not found for ' . $command->getName()); + } + + $className = $this->getClassName($command); + + return new $className($command, $options); + } + + public function canBuild(CommandInterface $command) + { + return (bool) $this->getClassName($command); + } + + /** + * Get the name of the class to instantiate for the command + * + * @param CommandInterface $command Command that is associated with the iterator + * + * @return string + */ + abstract protected function getClassName(CommandInterface $command); +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php new file mode 100644 index 0000000..2efc133 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/CompositeResourceIteratorFactory.php @@ -0,0 +1,67 @@ +factories = $factories; + } + + public function build(CommandInterface $command, array $options = array()) + { + if (!($factory = $this->getFactory($command))) { + throw new InvalidArgumentException('Iterator was not found for ' . $command->getName()); + } + + return $factory->build($command, $options); + } + + public function canBuild(CommandInterface $command) + { + return $this->getFactory($command) !== false; + } + + /** + * Add a factory to the composite factory + * + * @param ResourceIteratorFactoryInterface $factory Factory to add + * + * @return self + */ + public function addFactory(ResourceIteratorFactoryInterface $factory) + { + $this->factories[] = $factory; + + return $this; + } + + /** + * Get the factory that matches the command object + * + * @param CommandInterface $command Command retrieving the iterator for + * + * @return ResourceIteratorFactoryInterface|bool + */ + protected function getFactory(CommandInterface $command) + { + foreach ($this->factories as $factory) { + if ($factory->canBuild($command)) { + return $factory; + } + } + + return false; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php new file mode 100644 index 0000000..c71ca9d --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/MapResourceIteratorFactory.php @@ -0,0 +1,34 @@ +map = $map; + } + + public function getClassName(CommandInterface $command) + { + $className = $command->getName(); + + if (isset($this->map[$className])) { + return $this->map[$className]; + } elseif (isset($this->map['*'])) { + // If a wildcard was added, then always use that + return $this->map['*']; + } + + return null; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php new file mode 100644 index 0000000..2322434 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/Model.php @@ -0,0 +1,64 @@ +data = $data; + $this->structure = $structure; + } + + /** + * Get the structure of the model + * + * @return Parameter + */ + public function getStructure() + { + return $this->structure ?: new Parameter(); + } + + /** + * Provides debug information about the model object + * + * @return string + */ + public function __toString() + { + $output = 'Debug output of '; + if ($this->structure) { + $output .= $this->structure->getName() . ' '; + } + $output .= 'model'; + $output = str_repeat('=', strlen($output)) . "\n" . $output . "\n" . str_repeat('=', strlen($output)) . "\n\n"; + $output .= "Model data\n-----------\n\n"; + $output .= "This data can be retrieved from the model object using the get() method of the model " + . "(e.g. \$model->get(\$key)) or accessing the model like an associative array (e.g. \$model['key']).\n\n"; + $lines = array_slice(explode("\n", trim(print_r($this->toArray(), true))), 2, -1); + $output .= implode("\n", $lines); + + if ($this->structure) { + $output .= "\n\nModel structure\n---------------\n\n"; + $output .= "The following JSON document defines how the model was parsed from an HTTP response into the " + . "associative array structure you see above.\n\n"; + $output .= ' ' . json_encode($this->structure->toArray()) . "\n\n"; + } + + return $output . "\n"; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php new file mode 100644 index 0000000..e141524 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIterator.php @@ -0,0 +1,254 @@ +originalCommand = $command; + + // Parse options from the array of options + $this->data = $data; + $this->limit = array_key_exists('limit', $data) ? $data['limit'] : 0; + $this->pageSize = array_key_exists('page_size', $data) ? $data['page_size'] : false; + } + + /** + * Get all of the resources as an array (Warning: this could issue a large number of requests) + * + * @return array + */ + public function toArray() + { + return iterator_to_array($this, false); + } + + public function setLimit($limit) + { + $this->limit = $limit; + $this->resetState(); + + return $this; + } + + public function setPageSize($pageSize) + { + $this->pageSize = $pageSize; + $this->resetState(); + + return $this; + } + + /** + * Get an option from the iterator + * + * @param string $key Key of the option to retrieve + * + * @return mixed|null Returns NULL if not set or the value if set + */ + public function get($key) + { + return array_key_exists($key, $this->data) ? $this->data[$key] : null; + } + + /** + * Set an option on the iterator + * + * @param string $key Key of the option to set + * @param mixed $value Value to set for the option + * + * @return ResourceIterator + */ + public function set($key, $value) + { + $this->data[$key] = $value; + + return $this; + } + + public function current() + { + return $this->resources ? current($this->resources) : false; + } + + public function key() + { + return max(0, $this->iteratedCount - 1); + } + + public function count() + { + return $this->retrievedCount; + } + + /** + * Get the total number of requests sent + * + * @return int + */ + public function getRequestCount() + { + return $this->requestCount; + } + + /** + * Rewind the Iterator to the first element and send the original command + */ + public function rewind() + { + // Use the original command + $this->command = clone $this->originalCommand; + $this->resetState(); + $this->next(); + } + + public function valid() + { + return !$this->invalid && (!$this->resources || $this->current() || $this->nextToken) + && (!$this->limit || $this->iteratedCount < $this->limit + 1); + } + + public function next() + { + $this->iteratedCount++; + + // Check if a new set of resources needs to be retrieved + $sendRequest = false; + if (!$this->resources) { + $sendRequest = true; + } else { + // iterate over the internal array + $current = next($this->resources); + $sendRequest = $current === false && $this->nextToken && (!$this->limit || $this->iteratedCount < $this->limit + 1); + } + + if ($sendRequest) { + + $this->dispatch('resource_iterator.before_send', array( + 'iterator' => $this, + 'resources' => $this->resources + )); + + // Get a new command object from the original command + $this->command = clone $this->originalCommand; + // Send a request and retrieve the newly loaded resources + $this->resources = $this->sendRequest(); + $this->requestCount++; + + // If no resources were found, then the last request was not needed + // and iteration must stop + if (empty($this->resources)) { + $this->invalid = true; + } else { + // Add to the number of retrieved resources + $this->retrievedCount += count($this->resources); + // Ensure that we rewind to the beginning of the array + reset($this->resources); + } + + $this->dispatch('resource_iterator.after_send', array( + 'iterator' => $this, + 'resources' => $this->resources + )); + } + } + + /** + * Retrieve the NextToken that can be used in other iterators. + * + * @return string Returns a NextToken + */ + public function getNextToken() + { + return $this->nextToken; + } + + /** + * Returns the value that should be specified for the page size for a request that will maintain any hard limits, + * but still honor the specified pageSize if the number of items retrieved + pageSize < hard limit + * + * @return int Returns the page size of the next request. + */ + protected function calculatePageSize() + { + if ($this->limit && $this->iteratedCount + $this->pageSize > $this->limit) { + return 1 + ($this->limit - $this->iteratedCount); + } + + return (int) $this->pageSize; + } + + /** + * Reset the internal state of the iterator without triggering a rewind() + */ + protected function resetState() + { + $this->iteratedCount = 0; + $this->retrievedCount = 0; + $this->nextToken = false; + $this->resources = null; + $this->invalid = false; + } + + /** + * Send a request to retrieve the next page of results. Hook for subclasses to implement. + * + * @return array Returns the newly loaded resources + */ + abstract protected function sendRequest(); +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php new file mode 100644 index 0000000..6aa3615 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorApplyBatched.php @@ -0,0 +1,111 @@ +iterator = $iterator; + $this->callback = $callback; + Version::warn(__CLASS__ . ' is deprecated'); + } + + /** + * Apply the callback to the contents of the resource iterator + * + * @param int $perBatch The number of records to group per batch transfer + * + * @return int Returns the number of iterated resources + */ + public function apply($perBatch = 50) + { + $this->iterated = $this->batches = $batches = 0; + $that = $this; + $it = $this->iterator; + $callback = $this->callback; + + $batch = BatchBuilder::factory() + ->createBatchesWith(new BatchSizeDivisor($perBatch)) + ->transferWith(new BatchClosureTransfer(function (array $batch) use ($that, $callback, &$batches, $it) { + $batches++; + $that->dispatch('iterator_batch.before_batch', array('iterator' => $it, 'batch' => $batch)); + call_user_func_array($callback, array($it, $batch)); + $that->dispatch('iterator_batch.after_batch', array('iterator' => $it, 'batch' => $batch)); + })) + ->autoFlushAt($perBatch) + ->build(); + + $this->dispatch('iterator_batch.created_batch', array('batch' => $batch)); + + foreach ($this->iterator as $resource) { + $this->iterated++; + $batch->add($resource); + } + + $batch->flush(); + $this->batches = $batches; + + return $this->iterated; + } + + /** + * Get the total number of batches sent + * + * @return int + */ + public function getBatchCount() + { + return $this->batches; + } + + /** + * Get the total number of iterated resources + * + * @return int + */ + public function getIteratedCount() + { + return $this->iterated; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php new file mode 100644 index 0000000..2fd9980 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorClassFactory.php @@ -0,0 +1,60 @@ + AbcFoo). + */ +class ResourceIteratorClassFactory extends AbstractResourceIteratorFactory +{ + /** @var array List of namespaces used to look for classes */ + protected $namespaces; + + /** @var InflectorInterface Inflector used to determine class names */ + protected $inflector; + + /** + * @param string|array $namespaces List of namespaces for iterator objects + * @param InflectorInterface $inflector Inflector used to resolve class names + */ + public function __construct($namespaces = array(), InflectorInterface $inflector = null) + { + $this->namespaces = (array) $namespaces; + $this->inflector = $inflector ?: Inflector::getDefault(); + } + + /** + * Registers a namespace to check for Iterators + * + * @param string $namespace Namespace which contains Iterator classes + * + * @return self + */ + public function registerNamespace($namespace) + { + array_unshift($this->namespaces, $namespace); + + return $this; + } + + protected function getClassName(CommandInterface $command) + { + $iteratorName = $this->inflector->camel($command->getName()) . 'Iterator'; + + // Determine the name of the class to load + foreach ($this->namespaces as $namespace) { + $potentialClassName = $namespace . '\\' . $iteratorName; + if (class_exists($potentialClassName)) { + return $potentialClassName; + } + } + + return false; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php new file mode 100644 index 0000000..8b4e8db --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Service/Resource/ResourceIteratorFactoryInterface.php @@ -0,0 +1,30 @@ +=5.3.2", + "guzzle/cache": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version" + }, + "autoload": { + "psr-0": { "Guzzle\\Service": "" } + }, + "target-dir": "Guzzle/Service", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php b/source/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php new file mode 100644 index 0000000..d115fd8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Stream/PhpStreamRequestFactory.php @@ -0,0 +1,284 @@ +contextOptions = stream_context_get_options($context); + $this->context = $context; + } elseif (is_array($context) || !$context) { + $this->contextOptions = $context; + $this->createContext($params); + } elseif ($context) { + throw new InvalidArgumentException('$context must be an array or resource'); + } + + // Dispatch the before send event + $request->dispatch('request.before_send', array( + 'request' => $request, + 'context' => $this->context, + 'context_options' => $this->contextOptions + )); + + $this->setUrl($request); + $this->addDefaultContextOptions($request); + $this->addSslOptions($request); + $this->addBodyOptions($request); + $this->addProxyOptions($request); + + // Create the file handle but silence errors + return $this->createStream($params) + ->setCustomData('request', $request) + ->setCustomData('response_headers', $this->getLastResponseHeaders()); + } + + /** + * Set an option on the context and the internal options array + * + * @param string $wrapper Stream wrapper name of http + * @param string $name Context name + * @param mixed $value Context value + * @param bool $overwrite Set to true to overwrite an existing value + */ + protected function setContextValue($wrapper, $name, $value, $overwrite = false) + { + if (!isset($this->contextOptions[$wrapper])) { + $this->contextOptions[$wrapper] = array($name => $value); + } elseif (!$overwrite && isset($this->contextOptions[$wrapper][$name])) { + return; + } + $this->contextOptions[$wrapper][$name] = $value; + stream_context_set_option($this->context, $wrapper, $name, $value); + } + + /** + * Create a stream context + * + * @param array $params Parameter array + */ + protected function createContext(array $params) + { + $options = $this->contextOptions; + $this->context = $this->createResource(function () use ($params, $options) { + return stream_context_create($options, $params); + }); + } + + /** + * Get the last response headers received by the HTTP request + * + * @return array + */ + public function getLastResponseHeaders() + { + return $this->lastResponseHeaders; + } + + /** + * Adds the default context options to the stream context options + * + * @param RequestInterface $request Request + */ + protected function addDefaultContextOptions(RequestInterface $request) + { + $this->setContextValue('http', 'method', $request->getMethod()); + $headers = $request->getHeaderLines(); + + // "Connection: close" is required to get streams to work in HTTP 1.1 + if (!$request->hasHeader('Connection')) { + $headers[] = 'Connection: close'; + } + + $this->setContextValue('http', 'header', $headers); + $this->setContextValue('http', 'protocol_version', $request->getProtocolVersion()); + $this->setContextValue('http', 'ignore_errors', true); + } + + /** + * Set the URL to use with the factory + * + * @param RequestInterface $request Request that owns the URL + */ + protected function setUrl(RequestInterface $request) + { + $this->url = $request->getUrl(true); + + // Check for basic Auth username + if ($request->getUsername()) { + $this->url->setUsername($request->getUsername()); + } + + // Check for basic Auth password + if ($request->getPassword()) { + $this->url->setPassword($request->getPassword()); + } + } + + /** + * Add SSL options to the stream context + * + * @param RequestInterface $request Request + */ + protected function addSslOptions(RequestInterface $request) + { + if ($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)) { + $this->setContextValue('ssl', 'verify_peer', true, true); + if ($cafile = $request->getCurlOptions()->get(CURLOPT_CAINFO)) { + $this->setContextValue('ssl', 'cafile', $cafile, true); + } + } else { + $this->setContextValue('ssl', 'verify_peer', false, true); + } + } + + /** + * Add body (content) specific options to the context options + * + * @param RequestInterface $request + */ + protected function addBodyOptions(RequestInterface $request) + { + // Add the content for the request if needed + if (!($request instanceof EntityEnclosingRequestInterface)) { + return; + } + + if (count($request->getPostFields())) { + $this->setContextValue('http', 'content', (string) $request->getPostFields(), true); + } elseif ($request->getBody()) { + $this->setContextValue('http', 'content', (string) $request->getBody(), true); + } + + // Always ensure a content-length header is sent + if (isset($this->contextOptions['http']['content'])) { + $headers = isset($this->contextOptions['http']['header']) ? $this->contextOptions['http']['header'] : array(); + $headers[] = 'Content-Length: ' . strlen($this->contextOptions['http']['content']); + $this->setContextValue('http', 'header', $headers, true); + } + } + + /** + * Add proxy parameters to the context if needed + * + * @param RequestInterface $request Request + */ + protected function addProxyOptions(RequestInterface $request) + { + if ($proxy = $request->getCurlOptions()->get(CURLOPT_PROXY)) { + $this->setContextValue('http', 'proxy', $proxy); + } + } + + /** + * Create the stream for the request with the context options + * + * @param array $params Parameters of the stream + * + * @return StreamInterface + */ + protected function createStream(array $params) + { + $http_response_header = null; + $url = $this->url; + $context = $this->context; + $fp = $this->createResource(function () use ($context, $url, &$http_response_header) { + return fopen((string) $url, 'r', false, $context); + }); + + // Determine the class to instantiate + $className = isset($params['stream_class']) ? $params['stream_class'] : __NAMESPACE__ . '\\Stream'; + + /** @var $stream StreamInterface */ + $stream = new $className($fp); + + // Track the response headers of the request + if (isset($http_response_header)) { + $this->lastResponseHeaders = $http_response_header; + $this->processResponseHeaders($stream); + } + + return $stream; + } + + /** + * Process response headers + * + * @param StreamInterface $stream + */ + protected function processResponseHeaders(StreamInterface $stream) + { + // Set the size on the stream if it was returned in the response + foreach ($this->lastResponseHeaders as $header) { + if ((stripos($header, 'Content-Length:')) === 0) { + $stream->setSize(trim(substr($header, 15))); + } + } + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Closure to invoke that must return a valid resource + * + * @return resource + * @throws RuntimeException on error + */ + protected function createResource($callback) + { + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = array( + 'message' => $msg, + 'file' => $file, + 'line' => $line + ); + return true; + }); + $resource = call_user_func($callback); + 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; + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php b/source/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php new file mode 100644 index 0000000..12bed26 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Stream/Stream.php @@ -0,0 +1,289 @@ + array( + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, + 'rt' => true, 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a+' => true + ), + 'write' => array( + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'wb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, + 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + ) + ); + + /** + * @param resource $stream Stream resource to wrap + * @param int $size Size of the stream in bytes. Only pass if the size cannot be obtained from the stream. + * + * @throws InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream, $size = null) + { + $this->setStream($stream, $size); + } + + /** + * Closes the stream when the helper is destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + if (!$this->isReadable() || (!$this->isSeekable() && $this->isConsumed())) { + return ''; + } + + $originalPos = $this->ftell(); + $body = stream_get_contents($this->stream, -1, 0); + $this->seek($originalPos); + + return $body; + } + + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->cache[self::IS_READABLE] = false; + $this->cache[self::IS_WRITABLE] = false; + } + + /** + * Calculate a hash of a Stream + * + * @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 + * + * @return bool|string Returns false on failure or a hash string on success + */ + public static function getHash(StreamInterface $stream, $algo, $rawOutput = false) + { + $pos = $stream->ftell(); + if (!$stream->seek(0)) { + return false; + } + + $ctx = hash_init($algo); + while (!$stream->feof()) { + hash_update($ctx, $stream->read(8192)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; + } + + public function getMetaData($key = null) + { + $meta = stream_get_meta_data($this->stream); + + return !$key ? $meta : (array_key_exists($key, $meta) ? $meta[$key] : null); + } + + public function getStream() + { + return $this->stream; + } + + public function setStream($stream, $size = null) + { + if (!is_resource($stream)) { + throw new InvalidArgumentException('Stream must be a resource'); + } + + $this->size = $size; + $this->stream = $stream; + $this->rebuildCache(); + + return $this; + } + + public function detachStream() + { + $this->stream = null; + + return $this; + } + + public function getWrapper() + { + return $this->cache[self::WRAPPER_TYPE]; + } + + public function getWrapperData() + { + return $this->getMetaData('wrapper_data') ?: array(); + } + + public function getStreamType() + { + return $this->cache[self::STREAM_TYPE]; + } + + public function getUri() + { + return $this->cache['uri']; + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + // If the stream is a file based stream and local, then use fstat + clearstatcache(true, $this->cache['uri']); + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } elseif ($this->cache[self::IS_READABLE] && $this->cache[self::SEEKABLE]) { + // Only get the size based on the content if the the stream is readable and seekable + $pos = $this->ftell(); + $this->size = strlen((string) $this); + $this->seek($pos); + return $this->size; + } + + return false; + } + + public function isReadable() + { + return $this->cache[self::IS_READABLE]; + } + + public function isRepeatable() + { + return $this->cache[self::IS_READABLE] && $this->cache[self::SEEKABLE]; + } + + public function isWritable() + { + return $this->cache[self::IS_WRITABLE]; + } + + public function isConsumed() + { + return feof($this->stream); + } + + public function feof() + { + return $this->isConsumed(); + } + + public function isLocal() + { + return $this->cache[self::IS_LOCAL]; + } + + public function isSeekable() + { + return $this->cache[self::SEEKABLE]; + } + + public function setSize($size) + { + $this->size = $size; + + return $this; + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->cache[self::SEEKABLE] ? fseek($this->stream, $offset, $whence) === 0 : false; + } + + public function read($length) + { + return fread($this->stream, $length); + } + + public function write($string) + { + // We can't know the size after writing anything + $this->size = null; + + return fwrite($this->stream, $string); + } + + public function ftell() + { + return ftell($this->stream); + } + + public function rewind() + { + return $this->seek(0); + } + + public function readLine($maxLength = null) + { + if (!$this->cache[self::IS_READABLE]) { + return false; + } else { + return $maxLength ? fgets($this->getStream(), $maxLength) : fgets($this->getStream()); + } + } + + public function setCustomData($key, $value) + { + $this->customData[$key] = $value; + + return $this; + } + + public function getCustomData($key) + { + return isset($this->customData[$key]) ? $this->customData[$key] : null; + } + + /** + * Reprocess stream metadata + */ + protected function rebuildCache() + { + $this->cache = stream_get_meta_data($this->stream); + $this->cache[self::IS_LOCAL] = stream_is_local($this->stream); + $this->cache[self::IS_READABLE] = isset(self::$readWriteHash['read'][$this->cache['mode']]); + $this->cache[self::IS_WRITABLE] = isset(self::$readWriteHash['write'][$this->cache['mode']]); + } +} diff --git a/source/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php b/source/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php new file mode 100644 index 0000000..6d7dc37 --- /dev/null +++ b/source/vendor/guzzle/guzzle/src/Guzzle/Stream/StreamInterface.php @@ -0,0 +1,218 @@ +=5.3.2", + "guzzle/common": "self.version" + }, + "suggest": { + "guzzle/http": "To convert Guzzle request objects to PHP streams" + }, + "autoload": { + "psr-0": { "Guzzle\\Stream": "" } + }, + "target-dir": "Guzzle/Stream", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php new file mode 100644 index 0000000..951738d --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/AbstractBatchDecoratorTest.php @@ -0,0 +1,33 @@ +getMock('Guzzle\Batch\BatchTransferInterface'), + $this->getMock('Guzzle\Batch\BatchDivisorInterface') + ); + + $decoratorA = $this->getMockBuilder('Guzzle\Batch\AbstractBatchDecorator') + ->setConstructorArgs(array($batch)) + ->getMockForAbstractClass(); + + $decoratorB = $this->getMockBuilder('Guzzle\Batch\AbstractBatchDecorator') + ->setConstructorArgs(array($decoratorA)) + ->getMockForAbstractClass(); + + $decoratorA->add('foo'); + $this->assertFalse($decoratorB->isEmpty()); + $this->assertFalse($batch->isEmpty()); + $this->assertEquals(array($decoratorB, $decoratorA), $decoratorB->getDecorators()); + $this->assertEquals(array(), $decoratorB->flush()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php new file mode 100644 index 0000000..4da09d3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchBuilderTest.php @@ -0,0 +1,86 @@ +getMock('Guzzle\Batch\BatchTransferInterface'); + } + + private function getMockDivisor() + { + return $this->getMock('Guzzle\Batch\BatchDivisorInterface'); + } + + private function getMockBatchBuilder() + { + return BatchBuilder::factory() + ->transferWith($this->getMockTransfer()) + ->createBatchesWith($this->getMockDivisor()); + } + + public function testFactoryCreatesInstance() + { + $builder = BatchBuilder::factory(); + $this->assertInstanceOf('Guzzle\Batch\BatchBuilder', $builder); + } + + public function testAddsAutoFlush() + { + $batch = $this->getMockBatchBuilder()->autoFlushAt(10)->build(); + $this->assertInstanceOf('Guzzle\Batch\FlushingBatch', $batch); + } + + public function testAddsExceptionBuffering() + { + $batch = $this->getMockBatchBuilder()->bufferExceptions()->build(); + $this->assertInstanceOf('Guzzle\Batch\ExceptionBufferingBatch', $batch); + } + + public function testAddHistory() + { + $batch = $this->getMockBatchBuilder()->keepHistory()->build(); + $this->assertInstanceOf('Guzzle\Batch\HistoryBatch', $batch); + } + + public function testAddsNotify() + { + $batch = $this->getMockBatchBuilder()->notify(function() {})->build(); + $this->assertInstanceOf('Guzzle\Batch\NotifyingBatch', $batch); + } + + /** + * @expectedException Guzzle\Common\Exception\RuntimeException + */ + public function testTransferStrategyMustBeSet() + { + $batch = BatchBuilder::factory()->createBatchesWith($this->getMockDivisor())->build(); + } + + /** + * @expectedException Guzzle\Common\Exception\RuntimeException + */ + public function testDivisorStrategyMustBeSet() + { + $batch = BatchBuilder::factory()->transferWith($this->getMockTransfer())->build(); + } + + public function testTransfersRequests() + { + $batch = BatchBuilder::factory()->transferRequests(10)->build(); + $this->assertInstanceOf('Guzzle\Batch\BatchRequestTransfer', $this->readAttribute($batch, 'transferStrategy')); + } + + public function testTransfersCommands() + { + $batch = BatchBuilder::factory()->transferCommands(10)->build(); + $this->assertInstanceOf('Guzzle\Batch\BatchCommandTransfer', $this->readAttribute($batch, 'transferStrategy')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php new file mode 100644 index 0000000..753db7d --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureDivisorTest.php @@ -0,0 +1,36 @@ +createBatches($queue); + $this->assertEquals(array(array('foo'), array('baz')), $batches); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php new file mode 100644 index 0000000..6ba7ae0 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchClosureTransferTest.php @@ -0,0 +1,52 @@ +itemsTransferred = null; + $itemsTransferred =& $this->itemsTransferred; + + $this->transferStrategy = new BatchClosureTransfer(function (array $batch) use (&$itemsTransferred) { + $itemsTransferred = $batch; + return; + }); + } + + public function testTransfersBatch() + { + $batchedItems = array('foo', 'bar', 'baz'); + $this->transferStrategy->transfer($batchedItems); + + $this->assertEquals($batchedItems, $this->itemsTransferred); + } + + public function testTransferBailsOnEmptyBatch() + { + $batchedItems = array(); + $this->transferStrategy->transfer($batchedItems); + + $this->assertNull($this->itemsTransferred); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresCallableIsCallable() + { + $foo = new BatchClosureTransfer('uh oh!'); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php new file mode 100644 index 0000000..a04efab --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchCommandTransferTest.php @@ -0,0 +1,83 @@ + $command) { + if ($i % 2) { + $command->setClient($client1); + } else { + $command->setClient($client2); + } + $queue[] = $command; + } + + $batch = new BatchCommandTransfer(2); + $this->assertEquals(array( + array($commands[0], $commands[2]), + array($commands[4]), + array($commands[1], $commands[3]) + ), $batch->createBatches($queue)); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresAllItemsAreCommands() + { + $queue = new \SplQueue(); + $queue[] = 'foo'; + $batch = new BatchCommandTransfer(2); + $batch->createBatches($queue); + } + + public function testTransfersBatches() + { + $client = $this->getMockBuilder('Guzzle\Service\Client') + ->setMethods(array('send')) + ->getMock(); + $client->expects($this->once()) + ->method('send'); + $command = new Mc(); + $command->setClient($client); + $batch = new BatchCommandTransfer(2); + $batch->transfer(array($command)); + } + + public function testDoesNotTransfersEmptyBatches() + { + $batch = new BatchCommandTransfer(2); + $batch->transfer(array()); + } + + /** + * @expectedException Guzzle\Service\Exception\InconsistentClientTransferException + */ + public function testEnsuresAllCommandsUseTheSameClient() + { + $batch = new BatchCommandTransfer(2); + $client1 = new Client('http://www.example.com'); + $client2 = new Client('http://www.example.com'); + $command1 = new Mc(); + $command1->setClient($client1); + $command2 = new Mc(); + $command2->setClient($client2); + $batch->transfer(array($command1, $command2)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php new file mode 100644 index 0000000..dec7bd5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchRequestTransferTest.php @@ -0,0 +1,80 @@ +setCurlMulti(new CurlMulti()); + + $client2 = new Client('http://www.example.com'); + $client2->setCurlMulti(new CurlMulti()); + + $request1 = $client1->get(); + $request2 = $client2->get(); + $request3 = $client1->get(); + $request4 = $client2->get(); + $request5 = $client1->get(); + + $queue = new \SplQueue(); + $queue[] = $request1; + $queue[] = $request2; + $queue[] = $request3; + $queue[] = $request4; + $queue[] = $request5; + + $batch = new BatchRequestTransfer(2); + $this->assertEquals(array( + array($request1, $request3), + array($request3), + array($request2, $request4) + ), $batch->createBatches($queue)); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresAllItemsAreRequests() + { + $queue = new \SplQueue(); + $queue[] = 'foo'; + $batch = new BatchRequestTransfer(2); + $batch->createBatches($queue); + } + + public function testTransfersBatches() + { + $client = new Client('http://127.0.0.1:123'); + $request = $client->get(); + // For some reason... PHP unit clones the request, which emits a request.clone event. This causes the + // 'sorted' property of the event dispatcher to contain an array in the cloned request that is not present in + // the original. + $request->dispatch('request.clone'); + + $multi = $this->getMock('Guzzle\Http\Curl\CurlMultiInterface'); + $client->setCurlMulti($multi); + $multi->expects($this->once()) + ->method('add') + ->with($request); + $multi->expects($this->once()) + ->method('send'); + + $batch = new BatchRequestTransfer(2); + $batch->transfer(array($request)); + } + + public function testDoesNotTransfersEmptyBatches() + { + $batch = new BatchRequestTransfer(2); + $batch->transfer(array()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php new file mode 100644 index 0000000..5542228 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchSizeDivisorTest.php @@ -0,0 +1,24 @@ +assertEquals(3, $d->getSize()); + $d->setSize(2); + $batches = $d->createBatches($queue); + $this->assertEquals(array(array('foo', 'baz'), array('bar')), $batches); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php new file mode 100644 index 0000000..296f57a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/BatchTest.php @@ -0,0 +1,91 @@ +getMock('Guzzle\Batch\BatchTransferInterface'); + } + + private function getMockDivisor() + { + return $this->getMock('Guzzle\Batch\BatchDivisorInterface'); + } + + public function testAddsItemsToQueue() + { + $batch = new Batch($this->getMockTransfer(), $this->getMockDivisor()); + $this->assertSame($batch, $batch->add('foo')); + $this->assertEquals(1, count($batch)); + } + + public function testFlushReturnsItems() + { + $transfer = $this->getMockTransfer(); + $transfer->expects($this->exactly(2)) + ->method('transfer'); + + $divisor = $this->getMockDivisor(); + $divisor->expects($this->once()) + ->method('createBatches') + ->will($this->returnValue(array(array('foo', 'baz'), array('bar')))); + + $batch = new Batch($transfer, $divisor); + + $batch->add('foo')->add('baz')->add('bar'); + $items = $batch->flush(); + + $this->assertEquals(array('foo', 'baz', 'bar'), $items); + } + + public function testThrowsExceptionContainingTheFailedBatch() + { + $called = 0; + $originalException = new \Exception('Foo!'); + + $transfer = $this->getMockTransfer(); + $transfer->expects($this->exactly(2)) + ->method('transfer') + ->will($this->returnCallback(function () use (&$called, $originalException) { + if (++$called == 2) { + throw $originalException; + } + })); + + $divisor = $this->getMockDivisor(); + $batch = new Batch($transfer, $divisor); + + // PHPunit clones objects before passing them to a callback. + // Horrible hack to get around this! + $queue = $this->readAttribute($batch, 'queue'); + + $divisor->expects($this->once()) + ->method('createBatches') + ->will($this->returnCallback(function ($batch) use ($queue) { + foreach ($queue as $item) { + $items[] = $item; + } + return array_chunk($items, 2); + })); + + $batch->add('foo')->add('baz')->add('bar')->add('bee')->add('boo'); + $this->assertFalse($batch->isEmpty()); + + try { + $items = $batch->flush(); + $this->fail('Expected exception'); + } catch (BatchTransferException $e) { + $this->assertEquals($originalException, $e->getPrevious()); + $this->assertEquals(array('bar', 'bee'), array_values($e->getBatch())); + $this->assertEquals(1, count($batch)); + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php new file mode 100644 index 0000000..fd810b1 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/ExceptionBufferingBatchTest.php @@ -0,0 +1,45 @@ +getMockBuilder('Guzzle\Batch\BatchTransferInterface') + ->setMethods(array('transfer')) + ->getMock(); + + $d = new BatchSizeDivisor(1); + $batch = new Batch($t, $d); + + $called = 0; + $t->expects($this->exactly(3)) + ->method('transfer') + ->will($this->returnCallback(function ($batch) use (&$called) { + if (++$called === 2) { + throw new \Exception('Foo'); + } + })); + + $decorator = new ExceptionBufferingBatch($batch); + $decorator->add('foo')->add('baz')->add('bar'); + $result = $decorator->flush(); + + $e = $decorator->getExceptions(); + $this->assertEquals(1, count($e)); + $this->assertEquals(array('baz'), $e[0]->getBatch()); + + $decorator->clearExceptions(); + $this->assertEquals(0, count($decorator->getExceptions())); + + $this->assertEquals(array('foo', 'bar'), $result); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php new file mode 100644 index 0000000..9b37a48 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/FlushingBatchTest.php @@ -0,0 +1,40 @@ +getMock('Guzzle\Batch\BatchTransferInterface', array('transfer')); + $d = $this->getMock('Guzzle\Batch\BatchDivisorInterface', array('createBatches')); + + $batch = new Batch($t, $d); + $queue = $this->readAttribute($batch, 'queue'); + + $d->expects($this->exactly(2)) + ->method('createBatches') + ->will($this->returnCallback(function () use ($queue) { + $items = array(); + foreach ($queue as $item) { + $items[] = $item; + } + return array($items); + })); + + $t->expects($this->exactly(2)) + ->method('transfer'); + + $flush = new FlushingBatch($batch, 3); + $this->assertEquals(3, $flush->getThreshold()); + $flush->setThreshold(2); + $flush->add('foo')->add('baz')->add('bar')->add('bee')->add('boo'); + $this->assertEquals(1, count($flush)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php new file mode 100644 index 0000000..60d6f95 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/HistoryBatchTest.php @@ -0,0 +1,26 @@ +getMock('Guzzle\Batch\BatchTransferInterface'), + $this->getMock('Guzzle\Batch\BatchDivisorInterface') + ); + + $history = new HistoryBatch($batch); + $history->add('foo')->add('baz'); + $this->assertEquals(array('foo', 'baz'), $history->getHistory()); + $history->clearHistory(); + $this->assertEquals(array(), $history->getHistory()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php new file mode 100644 index 0000000..69a8900 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Batch/NotifyingBatchTest.php @@ -0,0 +1,45 @@ +getMock('Guzzle\Batch\Batch', array('flush'), array( + $this->getMock('Guzzle\Batch\BatchTransferInterface'), + $this->getMock('Guzzle\Batch\BatchDivisorInterface') + )); + + $batch->expects($this->once()) + ->method('flush') + ->will($this->returnValue(array('foo', 'baz'))); + + $data = array(); + $decorator = new NotifyingBatch($batch, function ($batch) use (&$data) { + $data[] = $batch; + }); + + $decorator->add('foo')->add('baz'); + $decorator->flush(); + $this->assertEquals(array(array('foo', 'baz')), $data); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresCallableIsValid() + { + $batch = new Batch( + $this->getMock('Guzzle\Batch\BatchTransferInterface'), + $this->getMock('Guzzle\Batch\BatchDivisorInterface') + ); + $decorator = new NotifyingBatch($batch, 'foo'); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php new file mode 100644 index 0000000..c4140a9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterFactoryTest.php @@ -0,0 +1,64 @@ +cache = new ArrayCache(); + $this->adapter = new DoctrineCacheAdapter($this->cache); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEnsuresConfigIsObject() + { + CacheAdapterFactory::fromCache(array()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEnsuresKnownType() + { + CacheAdapterFactory::fromCache(new \stdClass()); + } + + public function cacheProvider() + { + return array( + array(new DoctrineCacheAdapter(new ArrayCache()), 'Guzzle\Cache\DoctrineCacheAdapter'), + array(new ArrayCache(), 'Guzzle\Cache\DoctrineCacheAdapter'), + array(StorageFactory::factory(array('adapter' => 'memory')), 'Guzzle\Cache\Zf2CacheAdapter'), + ); + } + + /** + * @dataProvider cacheProvider + */ + public function testCreatesNullCacheAdapterByDefault($cache, $type) + { + $adapter = CacheAdapterFactory::fromCache($cache); + $this->assertInstanceOf($type, $adapter); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php new file mode 100644 index 0000000..3e30ddd --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/CacheAdapterTest.php @@ -0,0 +1,68 @@ +cache = new ArrayCache(); + $this->adapter = new DoctrineCacheAdapter($this->cache); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->adapter = null; + $this->cache = null; + parent::tearDown(); + } + + public function testGetCacheObject() + { + $this->assertEquals($this->cache, $this->adapter->getCacheObject()); + } + + public function testSave() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + } + + public function testFetch() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertEquals('data', $this->adapter->fetch('test')); + } + + public function testContains() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertTrue($this->adapter->contains('test')); + } + + public function testDelete() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertTrue($this->adapter->delete('test')); + $this->assertFalse($this->adapter->contains('test')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php new file mode 100644 index 0000000..12de65b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/ClosureCacheAdapterTest.php @@ -0,0 +1,94 @@ +callables = array( + 'contains' => function($id, $options = array()) use ($that) { + return array_key_exists($id, $that->data); + }, + 'delete' => function($id, $options = array()) use ($that) { + unset($that->data[$id]); + return true; + }, + 'fetch' => function($id, $options = array()) use ($that) { + return array_key_exists($id, $that->data) ? $that->data[$id] : null; + }, + 'save' => function($id, $data, $lifeTime, $options = array()) use ($that) { + $that->data[$id] = $data; + return true; + } + ); + + $this->adapter = new ClosureCacheAdapter($this->callables); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->cache = null; + $this->callables = null; + parent::tearDown(); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testEnsuresCallablesArePresent() + { + $callables = $this->callables; + unset($callables['delete']); + $cache = new ClosureCacheAdapter($callables); + } + + public function testAllCallablesMustBePresent() + { + $cache = new ClosureCacheAdapter($this->callables); + } + + public function testCachesDataUsingCallables() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertEquals('data', $this->adapter->fetch('test')); + } + + public function testChecksIfCacheContainsKeys() + { + $this->adapter->save('test', 'data', 1000); + $this->assertTrue($this->adapter->contains('test')); + $this->assertFalse($this->adapter->contains('foo')); + } + + public function testDeletesFromCacheByKey() + { + $this->adapter->save('test', 'data', 1000); + $this->assertTrue($this->adapter->contains('test')); + $this->adapter->delete('test'); + $this->assertFalse($this->adapter->contains('test')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php new file mode 100644 index 0000000..e05df3f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/NullCacheAdapterTest.php @@ -0,0 +1,20 @@ +assertEquals(false, $c->contains('foo')); + $this->assertEquals(true, $c->delete('foo')); + $this->assertEquals(false, $c->fetch('foo')); + $this->assertEquals(true, $c->save('foo', 'bar')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php new file mode 100644 index 0000000..9077c12 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Cache/Zf2CacheAdapterTest.php @@ -0,0 +1,58 @@ +cache = StorageFactory::factory(array( + 'adapter' => 'memory' + )); + $this->adapter = new Zf2CacheAdapter($this->cache); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->adapter = null; + $this->cache = null; + parent::tearDown(); + } + + public function testCachesDataUsingCallables() + { + $this->assertTrue($this->adapter->save('test', 'data', 1000)); + $this->assertEquals('data', $this->adapter->fetch('test')); + } + + public function testChecksIfCacheContainsKeys() + { + $this->adapter->save('test', 'data', 1000); + $this->assertTrue($this->adapter->contains('test')); + $this->assertFalse($this->adapter->contains('foo')); + } + + public function testDeletesFromCacheByKey() + { + $this->adapter->save('test', 'data', 1000); + $this->assertTrue($this->adapter->contains('test')); + $this->adapter->delete('test'); + $this->assertFalse($this->adapter->contains('test')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php new file mode 100644 index 0000000..19d12e6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/AbstractHasDispatcherTest.php @@ -0,0 +1,63 @@ +assertEquals(array(), AbstractHasDispatcher::getAllEvents()); + } + + public function testAllowsDispatcherToBeInjected() + { + $d = new EventDispatcher(); + $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher'); + $this->assertSame($mock, $mock->setEventDispatcher($d)); + $this->assertSame($d, $mock->getEventDispatcher()); + } + + public function testCreatesDefaultEventDispatcherIfNeeded() + { + $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher'); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\EventDispatcher', $mock->getEventDispatcher()); + } + + public function testHelperDispatchesEvents() + { + $data = array(); + $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher'); + $mock->getEventDispatcher()->addListener('test', function(Event $e) use (&$data) { + $data = $e->getIterator()->getArrayCopy(); + }); + $mock->dispatch('test', array( + 'param' => 'abc' + )); + $this->assertEquals(array( + 'param' => 'abc', + ), $data); + } + + public function testHelperAttachesSubscribers() + { + $mock = $this->getMockForAbstractClass('Guzzle\Common\AbstractHasDispatcher'); + $subscriber = $this->getMockForAbstractClass('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcher') + ->setMethods(array('addSubscriber')) + ->getMock(); + + $dispatcher->expects($this->once()) + ->method('addSubscriber'); + + $mock->setEventDispatcher($dispatcher); + $mock->addSubscriber($subscriber); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php new file mode 100644 index 0000000..0648a02 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/CollectionTest.php @@ -0,0 +1,529 @@ +coll = new Collection(); + } + + public function testConstructorCanBeCalledWithNoParams() + { + $this->coll = new Collection(); + $p = $this->coll->getAll(); + $this->assertEmpty($p, '-> Collection must be empty when no data is passed'); + } + + public function testConstructorCanBeCalledWithParams() + { + $testData = array( + 'test' => 'value', + 'test_2' => 'value2' + ); + $this->coll = new Collection($testData); + $this->assertEquals($this->coll->getAll(), $testData, '-> getAll() must return the data passed in the constructor'); + $this->assertEquals($this->coll->getAll(), $this->coll->toArray()); + } + + public function testImplementsIteratorAggregate() + { + $this->coll->set('key', 'value'); + $this->assertInstanceOf('ArrayIterator', $this->coll->getIterator()); + $this->assertEquals(1, count($this->coll)); + $total = 0; + foreach ($this->coll as $key => $value) { + $this->assertEquals('key', $key); + $this->assertEquals('value', $value); + $total++; + } + $this->assertEquals(1, $total); + } + + public function testCanAddValuesToExistingKeysByUsingArray() + { + $this->coll->add('test', 'value1'); + $this->assertEquals($this->coll->getAll(), array('test' => 'value1')); + $this->coll->add('test', 'value2'); + $this->assertEquals($this->coll->getAll(), array('test' => array('value1', 'value2'))); + $this->coll->add('test', 'value3'); + $this->assertEquals($this->coll->getAll(), array('test' => array('value1', 'value2', 'value3'))); + } + + public function testHandlesMergingInDisparateDataSources() + { + $params = array( + 'test' => 'value1', + 'test2' => 'value2', + 'test3' => array('value3', 'value4') + ); + $this->coll->merge($params); + $this->assertEquals($this->coll->getAll(), $params); + + // Pass the same object to itself + $this->assertEquals($this->coll->merge($this->coll), $this->coll); + } + + public function testCanClearAllDataOrSpecificKeys() + { + $this->coll->merge(array( + 'test' => 'value1', + 'test2' => 'value2' + )); + + // Clear a specific parameter by name + $this->coll->remove('test'); + + $this->assertEquals($this->coll->getAll(), array( + 'test2' => 'value2' + )); + + // Clear all parameters + $this->coll->clear(); + + $this->assertEquals($this->coll->getAll(), array()); + } + + public function testGetsValuesByKey() + { + $this->assertNull($this->coll->get('test')); + $this->coll->add('test', 'value'); + $this->assertEquals('value', $this->coll->get('test')); + $this->coll->set('test2', 'v2'); + $this->coll->set('test3', 'v3'); + $this->assertEquals(array( + 'test' => 'value', + 'test2' => 'v2' + ), $this->coll->getAll(array('test', 'test2'))); + } + + public function testProvidesKeys() + { + $this->assertEquals(array(), $this->coll->getKeys()); + $this->coll->merge(array( + 'test1' => 'value1', + 'test2' => 'value2' + )); + $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys()); + // Returns the cached array previously returned + $this->assertEquals(array('test1', 'test2'), $this->coll->getKeys()); + $this->coll->remove('test1'); + $this->assertEquals(array('test2'), $this->coll->getKeys()); + $this->coll->add('test3', 'value3'); + $this->assertEquals(array('test2', 'test3'), $this->coll->getKeys()); + } + + public function testChecksIfHasKey() + { + $this->assertFalse($this->coll->hasKey('test')); + $this->coll->add('test', 'value'); + $this->assertEquals(true, $this->coll->hasKey('test')); + $this->coll->add('test2', 'value2'); + $this->assertEquals(true, $this->coll->hasKey('test')); + $this->assertEquals(true, $this->coll->hasKey('test2')); + $this->assertFalse($this->coll->hasKey('testing')); + $this->assertEquals(false, $this->coll->hasKey('AB-C', 'junk')); + } + + public function testChecksIfHasValue() + { + $this->assertFalse($this->coll->hasValue('value')); + $this->coll->add('test', 'value'); + $this->assertEquals('test', $this->coll->hasValue('value')); + $this->coll->add('test2', 'value2'); + $this->assertEquals('test', $this->coll->hasValue('value')); + $this->assertEquals('test2', $this->coll->hasValue('value2')); + $this->assertFalse($this->coll->hasValue('val')); + } + + public function testCanGetAllValuesByArray() + { + $this->coll->add('foo', 'bar'); + $this->coll->add('tEsT', 'value'); + $this->coll->add('tesTing', 'v2'); + $this->coll->add('key', 'v3'); + $this->assertNull($this->coll->get('test')); + $this->assertEquals(array( + 'foo' => 'bar', + 'tEsT' => 'value', + 'tesTing' => 'v2' + ), $this->coll->getAll(array( + 'foo', 'tesTing', 'tEsT' + ))); + } + + public function testImplementsCount() + { + $data = new Collection(); + $this->assertEquals(0, $data->count()); + $data->add('key', 'value'); + $this->assertEquals(1, count($data)); + $data->add('key', 'value2'); + $this->assertEquals(1, count($data)); + $data->add('key_2', 'value3'); + $this->assertEquals(2, count($data)); + } + + public function testAddParamsByMerging() + { + $params = array( + 'test' => 'value1', + 'test2' => 'value2', + 'test3' => array('value3', 'value4') + ); + + // Add some parameters + $this->coll->merge($params); + + // Add more parameters by merging them in + $this->coll->merge(array( + 'test' => 'another', + 'different_key' => 'new value' + )); + + $this->assertEquals(array( + 'test' => array('value1', 'another'), + 'test2' => 'value2', + 'test3' => array('value3', 'value4'), + 'different_key' => 'new value' + ), $this->coll->getAll()); + } + + public function testAllowsFunctionalFilter() + { + $this->coll->merge(array( + 'fruit' => 'apple', + 'number' => 'ten', + 'prepositions' => array('about', 'above', 'across', 'after'), + 'same_number' => 'ten' + )); + + $filtered = $this->coll->filter(function($key, $value) { + return $value == 'ten'; + }); + + $this->assertNotEquals($filtered, $this->coll); + + $this->assertEquals(array( + 'number' => 'ten', + 'same_number' => 'ten' + ), $filtered->getAll()); + } + + public function testAllowsFunctionalMapping() + { + $this->coll->merge(array( + 'number_1' => 1, + 'number_2' => 2, + 'number_3' => 3 + )); + + $mapped = $this->coll->map(function($key, $value) { + return $value * $value; + }); + + $this->assertNotEquals($mapped, $this->coll); + + $this->assertEquals(array( + 'number_1' => 1, + 'number_2' => 4, + 'number_3' => 9 + ), $mapped->getAll()); + } + + public function testImplementsArrayAccess() + { + $this->coll->merge(array( + 'k1' => 'v1', + 'k2' => 'v2' + )); + + $this->assertTrue($this->coll->offsetExists('k1')); + $this->assertFalse($this->coll->offsetExists('Krull')); + + $this->coll->offsetSet('k3', 'v3'); + $this->assertEquals('v3', $this->coll->offsetGet('k3')); + $this->assertEquals('v3', $this->coll->get('k3')); + + $this->coll->offsetUnset('k1'); + $this->assertFalse($this->coll->offsetExists('k1')); + } + + public function testUsesStaticWhenCreatingNew() + { + $qs = new QueryString(array( + 'a' => 'b', + 'c' => 'd' + )); + + $this->assertInstanceOf('Guzzle\\Http\\QueryString', $qs->map(function($a, $b) {})); + $this->assertInstanceOf('Guzzle\\Common\\Collection', $qs->map(function($a, $b) {}, array(), false)); + + $this->assertInstanceOf('Guzzle\\Http\\QueryString', $qs->filter(function($a, $b) {})); + $this->assertInstanceOf('Guzzle\\Common\\Collection', $qs->filter(function($a, $b) {}, false)); + } + + public function testCanReplaceAllData() + { + $this->assertSame($this->coll, $this->coll->replace(array( + 'a' => '123' + ))); + + $this->assertEquals(array( + 'a' => '123' + ), $this->coll->getAll()); + } + + public function dataProvider() + { + return array( + array('this_is_a_test', '{a}_is_a_{b}', array( + 'a' => 'this', + 'b' => 'test' + )), + array('this_is_a_test', '{abc}_is_a_{0}', array( + 'abc' => 'this', + 0 => 'test' + )), + array('this_is_a_test', '{abc}_is_a_{0}', array( + 'abc' => 'this', + 0 => 'test' + )), + array('this_is_a_test', 'this_is_a_test', array( + 'abc' => 'this' + )), + array('{abc}_is_{not_found}a_{0}', '{abc}_is_{not_found}a_{0}', array()) + ); + } + + /** + * @dataProvider dataProvider + */ + public function testInjectsConfigData($output, $input, $config) + { + $collection = new Collection($config); + $this->assertEquals($output, $collection->inject($input)); + } + + public function testCanSearchByKey() + { + $collection = new Collection(array( + 'foo' => 'bar', + 'BaZ' => 'pho' + )); + + $this->assertEquals('foo', $collection->keySearch('FOO')); + $this->assertEquals('BaZ', $collection->keySearch('baz')); + $this->assertEquals(false, $collection->keySearch('Bar')); + } + + public function testPreparesFromConfig() + { + $c = Collection::fromConfig(array( + 'a' => '123', + 'base_url' => 'http://www.test.com/' + ), array( + 'a' => 'xyz', + 'b' => 'lol' + ), array('a')); + + $this->assertInstanceOf('Guzzle\Common\Collection', $c); + $this->assertEquals(array( + 'a' => '123', + 'b' => 'lol', + 'base_url' => 'http://www.test.com/' + ), $c->getAll()); + + try { + $c = Collection::fromConfig(array(), array(), array('a')); + $this->fail('Exception not throw when missing config'); + } catch (InvalidArgumentException $e) { + } + } + + function falseyDataProvider() + { + return array( + array(false, false), + array(null, null), + array('', ''), + array(array(), array()), + array(0, 0), + ); + } + + /** + * @dataProvider falseyDataProvider + */ + public function testReturnsCorrectData($a, $b) + { + $c = new Collection(array('value' => $a)); + $this->assertSame($b, $c->get('value')); + } + + public function testRetrievesNestedKeysUsingPath() + { + $data = array( + 'foo' => 'bar', + 'baz' => array( + 'mesa' => array( + 'jar' => 'jar' + ) + ) + ); + $collection = new Collection($data); + $this->assertEquals('bar', $collection->getPath('foo')); + $this->assertEquals('jar', $collection->getPath('baz/mesa/jar')); + $this->assertNull($collection->getPath('wewewf')); + $this->assertNull($collection->getPath('baz/mesa/jar/jar')); + } + + public function testFalseyKeysStillDescend() + { + $collection = new Collection(array( + '0' => array( + 'a' => 'jar' + ), + 1 => 'other' + )); + $this->assertEquals('jar', $collection->getPath('0/a')); + $this->assertEquals('other', $collection->getPath('1')); + } + + public function getPathProvider() + { + $data = array( + 'foo' => 'bar', + 'baz' => array( + 'mesa' => array( + 'jar' => 'jar', + 'array' => array('a', 'b', 'c') + ), + 'bar' => array( + 'baz' => 'bam', + 'array' => array('d', 'e', 'f') + ) + ), + 'bam' => array( + array('foo' => 1), + array('foo' => 2), + array('array' => array('h', 'i')) + ) + ); + $c = new Collection($data); + + return array( + // Simple path selectors + array($c, 'foo', 'bar'), + array($c, 'baz', $data['baz']), + array($c, 'bam', $data['bam']), + array($c, 'baz/mesa', $data['baz']['mesa']), + array($c, 'baz/mesa/jar', 'jar'), + // Merge everything two levels under baz + array($c, 'baz/*', array( + 'jar' => 'jar', + 'array' => array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array']), + 'baz' => 'bam' + )), + // Does not barf on missing keys + array($c, 'fefwfw', null), + // Does not barf when a wildcard does not resolve correctly + array($c, '*/*/*/*/*/wefwfe', array()), + // Allows custom separator + array($c, '*|mesa', $data['baz']['mesa'], '|'), + // Merge all 'array' keys two levels under baz (the trailing * does not hurt the results) + array($c, 'baz/*/array/*', array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array'])), + // Merge all 'array' keys two levels under baz + array($c, 'baz/*/array', array_merge($data['baz']['mesa']['array'], $data['baz']['bar']['array'])), + array($c, 'baz/mesa/array', $data['baz']['mesa']['array']), + // Having a trailing * does not hurt the results + array($c, 'baz/mesa/array/*', $data['baz']['mesa']['array']), + // Merge of anything one level deep + array($c, '*', array_merge(array('bar'), $data['baz'], $data['bam'])), + // Funky merge of anything two levels deep + array($c, '*/*', array( + 'jar' => 'jar', + 'array' => array('a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'), + 'baz' => 'bam', + 'foo' => array(1, 2) + )), + // Funky merge of all 'array' keys that are two levels deep + array($c, '*/*/array', array('a', 'b', 'c', 'd', 'e', 'f', 'h', 'i')) + ); + } + + /** + * @dataProvider getPathProvider + */ + public function testGetPath(Collection $c, $path, $expected, $separator = '/') + { + $this->assertEquals($expected, $c->getPath($path, $separator)); + } + + public function testOverridesSettings() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $c->overwriteWith(array('foo' => 10, 'bar' => 300)); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll()); + } + + public function testOverwriteWithCollection() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $b = new Collection(array('foo' => 10, 'bar' => 300)); + $c->overwriteWith($b); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll()); + } + + public function testOverwriteWithTraversable() + { + $c = new Collection(array('foo' => 1, 'baz' => 2, 'bar' => 3)); + $b = new Collection(array('foo' => 10, 'bar' => 300)); + $c->overwriteWith($b->getIterator()); + $this->assertEquals(array('foo' => 10, 'baz' => 2, 'bar' => 300), $c->getAll()); + } + + public function testCanSetNestedPathValueThatDoesNotExist() + { + $c = new Collection(array()); + $c->setPath('foo/bar/baz/123', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']['baz']['123']); + } + + public function testCanSetNestedPathValueThatExists() + { + $c = new Collection(array('foo' => array('bar' => 'test'))); + $c->setPath('foo/bar', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testVerifiesNestedPathIsValidAtExactLevel() + { + $c = new Collection(array('foo' => 'bar')); + $c->setPath('foo/bar', 'hi'); + $this->assertEquals('hi', $c['foo']['bar']); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testVerifiesThatNestedPathIsValidAtAnyLevel() + { + $c = new Collection(array('foo' => 'bar')); + $c->setPath('foo/bar/baz', 'test'); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php new file mode 100644 index 0000000..5484e14 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/EventTest.php @@ -0,0 +1,62 @@ + '123', + 'other' => '456', + 'event' => 'test.notify' + )); + } + + public function testAllowsParameterInjection() + { + $event = new Event(array( + 'test' => '123' + )); + $this->assertEquals('123', $event['test']); + } + + public function testImplementsArrayAccess() + { + $event = $this->getEvent(); + $this->assertEquals('123', $event['test']); + $this->assertNull($event['foobar']); + + $this->assertTrue($event->offsetExists('test')); + $this->assertFalse($event->offsetExists('foobar')); + + unset($event['test']); + $this->assertFalse($event->offsetExists('test')); + + $event['test'] = 'new'; + $this->assertEquals('new', $event['test']); + } + + public function testImplementsIteratorAggregate() + { + $event = $this->getEvent(); + $this->assertInstanceOf('ArrayIterator', $event->getIterator()); + } + + public function testConvertsToArray() + { + $this->assertEquals(array( + 'test' => '123', + 'other' => '456', + 'event' => 'test.notify' + ), $this->getEvent()->toArray()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php new file mode 100644 index 0000000..c72a2a6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/BatchTransferExceptionTest.php @@ -0,0 +1,21 @@ +getMock('Guzzle\Batch\BatchTransferInterface'); + $d = $this->getMock('Guzzle\Batch\BatchDivisorInterface'); + $transferException = new BatchTransferException(array('foo'), array(1, 2), $e, $t, $d); + $this->assertEquals(array('foo'), $transferException->getBatch()); + $this->assertSame($t, $transferException->getTransferStrategy()); + $this->assertSame($d, $transferException->getDivisorStrategy()); + $this->assertSame($e, $transferException->getPrevious()); + $this->assertEquals(array(1, 2), $transferException->getTransferredItems()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php new file mode 100644 index 0000000..2aecf2a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php @@ -0,0 +1,66 @@ +getExceptions(); + $e->add($exceptions[0]); + $e->add($exceptions[1]); + $this->assertContains("(Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $e->getMessage()); + $this->assertContains(" Test\n\n #0 ./", $e->getMessage()); + $this->assertSame($exceptions[0], $e->getFirst()); + } + + public function testCanSetExceptions() + { + $ex = new \Exception('foo'); + $e = new ExceptionCollection(); + $e->setExceptions(array($ex)); + $this->assertSame($ex, $e->getFirst()); + } + + public function testActsAsArray() + { + $e = new ExceptionCollection(); + $exceptions = $this->getExceptions(); + $e->add($exceptions[0]); + $e->add($exceptions[1]); + $this->assertEquals(2, count($e)); + $this->assertEquals($exceptions, $e->getIterator()->getArrayCopy()); + } + + public function testCanAddSelf() + { + $e1 = new ExceptionCollection(); + $e1->add(new \Exception("Test")); + $e2 = new ExceptionCollection('Meta description!'); + $e2->add(new \Exception("Test 2")); + $e3 = new ExceptionCollection(); + $e3->add(new \Exception('Baz')); + $e2->add($e3); + $e1->add($e2); + $message = $e1->getMessage(); + $this->assertContains("(Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message); + $this->assertContains("\n Test\n\n #0 ", $message); + $this->assertContains("\n\n(Guzzle\\Common\\Exception\\ExceptionCollection) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message); + $this->assertContains("\n\n Meta description!\n\n", $message); + $this->assertContains(" (Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message); + $this->assertContains("\n Test 2\n\n #0 ", $message); + $this->assertContains(" (Exception) ./tests/Guzzle/Tests/Common/Exception/ExceptionCollectionTest.php line ", $message); + $this->assertContains(" Baz\n\n #0", $message); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php new file mode 100644 index 0000000..c3a81d1 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Common/VersionTest.php @@ -0,0 +1,27 @@ +isRunning()) { + self::$server->flush(); + } else { + self::$server->start(); + } + } + + return self::$server; + } + + /** + * Set the service builder to use for tests + * + * @param ServiceBuilderInterface $builder Service builder + */ + public static function setServiceBuilder(ServiceBuilderInterface $builder) + { + self::$serviceBuilder = $builder; + } + + /** + * Get a service builder object that can be used throughout the service tests + * + * @return ServiceBuilder + */ + public static function getServiceBuilder() + { + if (!self::$serviceBuilder) { + throw new RuntimeException('No service builder has been set via setServiceBuilder()'); + } + + return self::$serviceBuilder; + } + + /** + * Check if an event dispatcher has a subscriber + * + * @param HasDispatcherInterface $dispatcher + * @param EventSubscriberInterface $subscriber + * + * @return bool + */ + protected function hasSubscriber(HasDispatcherInterface $dispatcher, EventSubscriberInterface $subscriber) + { + $class = get_class($subscriber); + $all = array_keys(call_user_func(array($class, 'getSubscribedEvents'))); + + foreach ($all as $i => $event) { + foreach ($dispatcher->getEventDispatcher()->getListeners($event) as $e) { + if ($e[0] === $subscriber) { + unset($all[$i]); + break; + } + } + } + + return count($all) == 0; + } + + /** + * Get a wildcard observer for an event dispatcher + * + * @param HasDispatcherInterface $hasDispatcher + * + * @return MockObserver + */ + public function getWildcardObserver(HasDispatcherInterface $hasDispatcher) + { + $class = get_class($hasDispatcher); + $o = new MockObserver(); + $events = call_user_func(array($class, 'getAllEvents')); + foreach ($events as $event) { + $hasDispatcher->getEventDispatcher()->addListener($event, array($o, 'update')); + } + + return $o; + } + + /** + * Set the mock response base path + * + * @param string $path Path to mock response folder + * + * @return GuzzleTestCase + */ + public static function setMockBasePath($path) + { + self::$mockBasePath = $path; + } + + /** + * Mark a request as being mocked + * + * @param RequestInterface $request + * + * @return self + */ + public function addMockedRequest(RequestInterface $request) + { + $this->requests[] = $request; + + return $this; + } + + /** + * Get all of the mocked requests + * + * @return array + */ + public function getMockedRequests() + { + return $this->requests; + } + + /** + * Get a mock response for a client by mock file name + * + * @param string $path Relative path to the mock response file + * + * @return Response + */ + public function getMockResponse($path) + { + return $path instanceof Response + ? $path + : MockPlugin::getMockFile(self::$mockBasePath . DIRECTORY_SEPARATOR . $path); + } + + /** + * Set a mock response from a mock file on the next client request. + * + * This method assumes that mock response files are located under the + * Command/Mock/ directory of the Service being tested + * (e.g. Unfuddle/Command/Mock/). A mock response is added to the next + * request sent by the client. + * + * @param Client $client Client object to modify + * @param string $paths Path to files within the Mock folder of the service + * + * @return MockPlugin returns the created mock plugin + */ + public function setMockResponse(Client $client, $paths) + { + $this->requests = array(); + $that = $this; + $mock = new MockPlugin(null, true); + $client->getEventDispatcher()->removeSubscriber($mock); + $mock->getEventDispatcher()->addListener('mock.request', function(Event $event) use ($that) { + $that->addMockedRequest($event['request']); + }); + + if ($paths instanceof Response) { + // A single response instance has been specified, create an array with that instance + // as the only element for the following loop to work as expected + $paths = array($paths); + } + + foreach ((array) $paths as $path) { + $mock->addResponse($this->getMockResponse($path)); + } + + $client->getEventDispatcher()->addSubscriber($mock); + + return $mock; + } + + /** + * Compare HTTP headers and use special markup to filter values + * A header prefixed with '!' means it must not exist + * A header prefixed with '_' means it must be ignored + * A header value of '*' means anything after the * will be ignored + * + * @param array $filteredHeaders Array of special headers + * @param array $actualHeaders Array of headers to check against + * + * @return array|bool Returns an array of the differences or FALSE if none + */ + public function compareHeaders($filteredHeaders, $actualHeaders) + { + $comparison = new HeaderComparison(); + + return $comparison->compare($filteredHeaders, $actualHeaders); + } + + /** + * Case insensitive assertContains + * + * @param string $needle Search string + * @param string $haystack Search this + * @param string $message Optional failure message + */ + public function assertContainsIns($needle, $haystack, $message = null) + { + $this->assertContains(strtolower($needle), strtolower($haystack), $message); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php new file mode 100644 index 0000000..20feaa8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/AbstractEntityBodyDecoratorTest.php @@ -0,0 +1,34 @@ +getMockForAbstractClass('Guzzle\Http\AbstractEntityBodyDecorator', array($e)); + + $this->assertSame($e->getStream(), $mock->getStream()); + $this->assertSame($e->getContentLength(), $mock->getContentLength()); + $this->assertSame($e->getSize(), $mock->getSize()); + $this->assertSame($e->getContentMd5(), $mock->getContentMd5()); + $this->assertSame($e->getContentType(), $mock->getContentType()); + $this->assertSame($e->__toString(), $mock->__toString()); + $this->assertSame($e->getUri(), $mock->getUri()); + $this->assertSame($e->getStreamType(), $mock->getStreamType()); + $this->assertSame($e->getWrapper(), $mock->getWrapper()); + $this->assertSame($e->getWrapperData(), $mock->getWrapperData()); + $this->assertSame($e->isReadable(), $mock->isReadable()); + $this->assertSame($e->isWritable(), $mock->isWritable()); + $this->assertSame($e->isConsumed(), $mock->isConsumed()); + $this->assertSame($e->isLocal(), $mock->isLocal()); + $this->assertSame($e->isSeekable(), $mock->isSeekable()); + $this->assertSame($e->getContentEncoding(), $mock->getContentEncoding()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php new file mode 100644 index 0000000..e6e6cdb --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/CachingEntityBodyTest.php @@ -0,0 +1,249 @@ +decorated = EntityBody::factory('testing'); + $this->body = new CachingEntityBody($this->decorated); + } + + public function testUsesRemoteSizeIfPossible() + { + $body = EntityBody::factory('test'); + $caching = new CachingEntityBody($body); + $this->assertEquals(4, $caching->getSize()); + $this->assertEquals(4, $caching->getContentLength()); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage does not support custom stream rewind + */ + public function testDoesNotAllowRewindFunction() + { + $this->body->setRewindFunction(true); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage Cannot seek to byte 10 + */ + public function testCannotSeekPastWhatHasBeenRead() + { + $this->body->seek(10); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage supports only SEEK_SET and SEEK_CUR + */ + public function testCannotUseSeekEnd() + { + $this->body->seek(2, SEEK_END); + } + + public function testChangingUnderlyingStreamUpdatesSizeAndStream() + { + $size = filesize(__FILE__); + $s = fopen(__FILE__, 'r'); + $this->body->setStream($s, $size); + $this->assertEquals($size, $this->body->getSize()); + $this->assertEquals($size, $this->decorated->getSize()); + $this->assertSame($s, $this->body->getStream()); + $this->assertSame($s, $this->decorated->getStream()); + } + + public function testRewindUsesSeek() + { + $a = EntityBody::factory('foo'); + $d = $this->getMockBuilder('Guzzle\Http\CachingEntityBody') + ->setMethods(array('seek')) + ->setConstructorArgs(array($a)) + ->getMock(); + $d->expects($this->once()) + ->method('seek') + ->with(0) + ->will($this->returnValue(true)); + $d->rewind(); + } + + public function testCanSeekToReadBytes() + { + $this->assertEquals('te', $this->body->read(2)); + $this->body->seek(0); + $this->assertEquals('test', $this->body->read(4)); + $this->assertEquals(4, $this->body->ftell()); + $this->body->seek(2); + $this->assertEquals(2, $this->body->ftell()); + $this->body->seek(2, SEEK_CUR); + $this->assertEquals(4, $this->body->ftell()); + $this->assertEquals('ing', $this->body->read(3)); + } + + public function testWritesToBufferStream() + { + $this->body->read(2); + $this->body->write('hi'); + $this->body->rewind(); + $this->assertEquals('tehiing', (string) $this->body); + } + + public function testReadLinesFromBothStreams() + { + $this->body->seek($this->body->ftell()); + $this->body->write("test\n123\nhello\n1234567890\n"); + $this->body->rewind(); + $this->assertEquals("test\n", $this->body->readLine(7)); + $this->assertEquals("123\n", $this->body->readLine(7)); + $this->assertEquals("hello\n", $this->body->readLine(7)); + $this->assertEquals("123456", $this->body->readLine(7)); + $this->assertEquals("7890\n", $this->body->readLine(7)); + // We overwrote the decorated stream, so no more data + $this->assertEquals('', $this->body->readLine(7)); + } + + public function testSkipsOverwrittenBytes() + { + $decorated = EntityBody::factory( + implode("\n", array_map(function ($n) { + return str_pad($n, 4, '0', STR_PAD_LEFT); + }, range(0, 25))) + ); + + $body = new CachingEntityBody($decorated); + + $this->assertEquals("0000\n", $body->readLine()); + $this->assertEquals("0001\n", $body->readLine()); + // Write over part of the body yet to be read, so skip some bytes + $this->assertEquals(5, $body->write("TEST\n")); + $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes')); + // Read, which skips bytes, then reads + $this->assertEquals("0003\n", $body->readLine()); + $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); + $this->assertEquals("0004\n", $body->readLine()); + $this->assertEquals("0005\n", $body->readLine()); + + // Overwrite part of the cached body (so don't skip any bytes) + $body->seek(5); + $this->assertEquals(5, $body->write("ABCD\n")); + $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes')); + $this->assertEquals("TEST\n", $body->readLine()); + $this->assertEquals("0003\n", $body->readLine()); + $this->assertEquals("0004\n", $body->readLine()); + $this->assertEquals("0005\n", $body->readLine()); + $this->assertEquals("0006\n", $body->readLine()); + $this->assertEquals(5, $body->write("1234\n")); + $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes')); + + // Seek to 0 and ensure the overwritten bit is replaced + $body->rewind(); + $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50)); + + // Ensure that casting it to a string does not include the bit that was overwritten + $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body); + } + + public function testWrapsContentType() + { + $a = $this->getMockBuilder('Guzzle\Http\EntityBody') + ->setMethods(array('getContentType')) + ->setConstructorArgs(array(fopen(__FILE__, 'r'))) + ->getMock(); + $a->expects($this->once()) + ->method('getContentType') + ->will($this->returnValue('foo')); + $d = new CachingEntityBody($a); + $this->assertEquals('foo', $d->getContentType()); + } + + public function testWrapsContentEncoding() + { + $a = $this->getMockBuilder('Guzzle\Http\EntityBody') + ->setMethods(array('getContentEncoding')) + ->setConstructorArgs(array(fopen(__FILE__, 'r'))) + ->getMock(); + $a->expects($this->once()) + ->method('getContentEncoding') + ->will($this->returnValue('foo')); + $d = new CachingEntityBody($a); + $this->assertEquals('foo', $d->getContentEncoding()); + } + + public function testWrapsMetadata() + { + $a = $this->getMockBuilder('Guzzle\Http\EntityBody') + ->setMethods(array('getMetadata', 'getWrapper', 'getWrapperData', 'getStreamType', 'getUri')) + ->setConstructorArgs(array(fopen(__FILE__, 'r'))) + ->getMock(); + + $a->expects($this->once()) + ->method('getMetadata') + ->will($this->returnValue(array())); + // Called twice for getWrapper and getWrapperData + $a->expects($this->exactly(1)) + ->method('getWrapper') + ->will($this->returnValue('wrapper')); + $a->expects($this->once()) + ->method('getWrapperData') + ->will($this->returnValue(array())); + $a->expects($this->once()) + ->method('getStreamType') + ->will($this->returnValue('baz')); + $a->expects($this->once()) + ->method('getUri') + ->will($this->returnValue('path/to/foo')); + + $d = new CachingEntityBody($a); + $this->assertEquals(array(), $d->getMetaData()); + $this->assertEquals('wrapper', $d->getWrapper()); + $this->assertEquals(array(), $d->getWrapperData()); + $this->assertEquals('baz', $d->getStreamType()); + $this->assertEquals('path/to/foo', $d->getUri()); + } + + public function testWrapsCustomData() + { + $a = $this->getMockBuilder('Guzzle\Http\EntityBody') + ->setMethods(array('getCustomData', 'setCustomData')) + ->setConstructorArgs(array(fopen(__FILE__, 'r'))) + ->getMock(); + + $a->expects($this->exactly(1)) + ->method('getCustomData') + ->with('foo') + ->will($this->returnValue('bar')); + + $a->expects($this->exactly(1)) + ->method('setCustomData') + ->with('foo', 'bar') + ->will($this->returnSelf()); + + $d = new CachingEntityBody($a); + $this->assertSame($d, $d->setCustomData('foo', 'bar')); + $this->assertEquals('bar', $d->getCustomData('foo')); + } + + public function testClosesBothStreams() + { + $s = fopen('php://temp', 'r'); + $a = EntityBody::factory($s); + $d = new CachingEntityBody($a); + $d->close(); + $this->assertFalse(is_resource($s)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php new file mode 100644 index 0000000..4a91a18 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ClientTest.php @@ -0,0 +1,601 @@ +assertEquals('http://www.google.com/', $client->getBaseUrl()); + $this->assertSame($client, $client->setConfig(array( + 'test' => '123' + ))); + $this->assertEquals(array('test' => '123'), $client->getConfig()->getAll()); + $this->assertEquals('123', $client->getConfig('test')); + $this->assertSame($client, $client->setBaseUrl('http://www.test.com/{test}')); + $this->assertEquals('http://www.test.com/123', $client->getBaseUrl()); + $this->assertEquals('http://www.test.com/{test}', $client->getBaseUrl(false)); + + try { + $client->setConfig(false); + } catch (\InvalidArgumentException $e) { + } + } + + public function testDescribesEvents() + { + $this->assertEquals(array('client.create_request'), Client::getAllEvents()); + } + + public function testConstructorCanAcceptConfig() + { + $client = new Client('http://www.test.com/', array( + 'data' => '123' + )); + $this->assertEquals('123', $client->getConfig('data')); + } + + public function testCanUseCollectionAsConfig() + { + $client = new Client('http://www.google.com/'); + $client->setConfig(new Collection(array( + 'api' => 'v1', + 'key' => 'value', + 'base_url' => 'http://www.google.com/' + ))); + $this->assertEquals('v1', $client->getConfig('api')); + } + + public function testExpandsUriTemplatesUsingConfig() + { + $client = new Client('http://www.google.com/'); + $client->setConfig(array('api' => 'v1', 'key' => 'value', 'foo' => 'bar')); + $ref = new \ReflectionMethod($client, 'expandTemplate'); + $ref->setAccessible(true); + $this->assertEquals('Testing...api/v1/key/value', $ref->invoke($client, 'Testing...api/{api}/key/{key}')); + } + + public function testClientAttachersObserversToRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client($this->getServer()->getUrl()); + $logPlugin = $this->getLogPlugin(); + $client->getEventDispatcher()->addSubscriber($logPlugin); + + // Get a request from the client and ensure the the observer was + // attached to the new request + $request = $client->createRequest(); + $this->assertTrue($this->hasSubscriber($request, $logPlugin)); + } + + public function testClientReturnsValidBaseUrls() + { + $client = new Client('http://www.{foo}.{data}/', array( + 'data' => '123', + 'foo' => 'bar' + )); + $this->assertEquals('http://www.bar.123/', $client->getBaseUrl()); + $client->setBaseUrl('http://www.google.com/'); + $this->assertEquals('http://www.google.com/', $client->getBaseUrl()); + } + + public function testClientAddsCurlOptionsToRequests() + { + $client = new Client('http://www.test.com/', array( + 'api' => 'v1', + // Adds the option using the curl values + 'curl.options' => array( + 'CURLOPT_HTTPAUTH' => 'CURLAUTH_DIGEST', + 'abc' => 'foo', + 'blacklist' => 'abc', + 'debug' => true + ) + )); + + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertEquals(CURLAUTH_DIGEST, $options->get(CURLOPT_HTTPAUTH)); + $this->assertEquals('foo', $options->get('abc')); + $this->assertEquals('abc', $options->get('blacklist')); + } + + public function testClientAllowsFineGrainedSslControlButIsSecureByDefault() + { + $client = new Client('https://www.secure.com/'); + + // secure by default + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertTrue($options->get(CURLOPT_SSL_VERIFYPEER)); + + // set a capath if you prefer + $client = new Client('https://www.secure.com/'); + $client->setSslVerification(__DIR__); + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertSame(__DIR__, $options->get(CURLOPT_CAPATH)); + } + + public function testConfigSettingsControlSslConfiguration() + { + // Use the default ca certs on the system + $client = new Client('https://www.secure.com/', array('ssl.certificate_authority' => 'system')); + $this->assertNull($client->getConfig('curl.options')); + // Can set the cacert value as well + $client = new Client('https://www.secure.com/', array('ssl.certificate_authority' => false)); + $options = $client->getConfig('curl.options'); + $this->assertArrayNotHasKey(CURLOPT_CAINFO, $options); + $this->assertSame(false, $options[CURLOPT_SSL_VERIFYPEER]); + $this->assertSame(0, $options[CURLOPT_SSL_VERIFYHOST]); + } + + public function testClientAllowsUnsafeOperationIfRequested() + { + // be really unsafe if you insist + $client = new Client('https://www.secure.com/', array( + 'api' => 'v1' + )); + + $client->setSslVerification(false); + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertFalse($options->get(CURLOPT_SSL_VERIFYPEER)); + $this->assertNull($options->get(CURLOPT_CAINFO)); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testThrowsExceptionForInvalidCertificate() + { + $client = new Client('https://www.secure.com/'); + $client->setSslVerification('/path/to/missing/file'); + } + + public function testClientAllowsSettingSpecificSslCaInfo() + { + // set a file other than the provided cacert.pem + $client = new Client('https://www.secure.com/', array( + 'api' => 'v1' + )); + + $client->setSslVerification(__FILE__); + $request = $client->createRequest(); + $options = $request->getCurlOptions(); + $this->assertSame(__FILE__, $options->get(CURLOPT_CAINFO)); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testClientPreventsInadvertentInsecureVerifyHostSetting() + { + // set a file other than the provided cacert.pem + $client = new Client('https://www.secure.com/', array( + 'api' => 'v1' + )); + $client->setSslVerification(__FILE__, true, true); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testClientPreventsInvalidVerifyPeerSetting() + { + // set a file other than the provided cacert.pem + $client = new Client('https://www.secure.com/', array( + 'api' => 'v1' + )); + $client->setSslVerification(__FILE__, 'yes'); + } + + public function testClientAddsParamsToRequests() + { + Version::$emitWarnings = false; + $client = new Client('http://www.example.com', array( + 'api' => 'v1', + 'request.params' => array( + 'foo' => 'bar', + 'baz' => 'jar' + ) + )); + $request = $client->createRequest(); + $this->assertEquals('bar', $request->getParams()->get('foo')); + $this->assertEquals('jar', $request->getParams()->get('baz')); + Version::$emitWarnings = true; + } + + public function urlProvider() + { + $u = $this->getServer()->getUrl() . 'base/'; + $u2 = $this->getServer()->getUrl() . 'base?z=1'; + return array( + array($u, '', $u), + array($u, 'relative/path/to/resource', $u . 'relative/path/to/resource'), + array($u, 'relative/path/to/resource?a=b&c=d', $u . 'relative/path/to/resource?a=b&c=d'), + array($u, '/absolute/path/to/resource', $this->getServer()->getUrl() . 'absolute/path/to/resource'), + array($u, '/absolute/path/to/resource?a=b&c=d', $this->getServer()->getUrl() . 'absolute/path/to/resource?a=b&c=d'), + array($u2, '/absolute/path/to/resource?a=b&c=d', $this->getServer()->getUrl() . 'absolute/path/to/resource?a=b&c=d&z=1'), + array($u2, 'relative/path/to/resource', $this->getServer()->getUrl() . 'base/relative/path/to/resource?z=1'), + array($u2, 'relative/path/to/resource?another=query', $this->getServer()->getUrl() . 'base/relative/path/to/resource?another=query&z=1') + ); + } + + /** + * @dataProvider urlProvider + */ + public function testBuildsRelativeUrls($baseUrl, $url, $result) + { + $client = new Client($baseUrl); + $this->assertEquals($result, $client->get($url)->getUrl()); + } + + public function testAllowsConfigsToBeChangedAndInjectedInBaseUrl() + { + $client = new Client('http://{a}/{b}'); + $this->assertEquals('http:///', $client->getBaseUrl()); + $this->assertEquals('http://{a}/{b}', $client->getBaseUrl(false)); + $client->setConfig(array( + 'a' => 'test.com', + 'b' => 'index.html' + )); + $this->assertEquals('http://test.com/index.html', $client->getBaseUrl()); + } + + public function testCreatesRequestsWithDefaultValues() + { + $client = new Client($this->getServer()->getUrl() . 'base'); + + // Create a GET request + $request = $client->createRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals($client->getBaseUrl(), $request->getUrl()); + + // Create a DELETE request + $request = $client->createRequest('DELETE'); + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals($client->getBaseUrl(), $request->getUrl()); + + // Create a HEAD request with custom headers + $request = $client->createRequest('HEAD', 'http://www.test.com/'); + $this->assertEquals('HEAD', $request->getMethod()); + $this->assertEquals('http://www.test.com/', $request->getUrl()); + + // Create a PUT request + $request = $client->createRequest('PUT'); + $this->assertEquals('PUT', $request->getMethod()); + + // Create a PUT request with injected config + $client->getConfig()->set('a', 1)->set('b', 2); + $request = $client->createRequest('PUT', '/path/{a}?q={b}'); + $this->assertEquals($request->getUrl(), $this->getServer()->getUrl() . 'path/1?q=2'); + } + + public function testClientHasHelperMethodsForCreatingRequests() + { + $url = $this->getServer()->getUrl(); + $client = new Client($url . 'base'); + $this->assertEquals('GET', $client->get()->getMethod()); + $this->assertEquals('PUT', $client->put()->getMethod()); + $this->assertEquals('POST', $client->post()->getMethod()); + $this->assertEquals('HEAD', $client->head()->getMethod()); + $this->assertEquals('DELETE', $client->delete()->getMethod()); + $this->assertEquals('OPTIONS', $client->options()->getMethod()); + $this->assertEquals('PATCH', $client->patch()->getMethod()); + $this->assertEquals($url . 'base/abc', $client->get('abc')->getUrl()); + $this->assertEquals($url . 'zxy', $client->put('/zxy')->getUrl()); + $this->assertEquals($url . 'zxy?a=b', $client->post('/zxy?a=b')->getUrl()); + $this->assertEquals($url . 'base?a=b', $client->head('?a=b')->getUrl()); + $this->assertEquals($url . 'base?a=b', $client->delete('/base?a=b')->getUrl()); + } + + public function testClientInjectsConfigsIntoUrls() + { + $client = new Client('http://www.test.com/api/v1', array( + 'test' => '123' + )); + $request = $client->get('relative/{test}'); + $this->assertEquals('http://www.test.com/api/v1/relative/123', $request->getUrl()); + } + + public function testAllowsEmptyBaseUrl() + { + $client = new Client(); + $request = $client->get('http://www.google.com/'); + $this->assertEquals('http://www.google.com/', $request->getUrl()); + $request->setResponse(new Response(200), true); + $request->send(); + } + + public function testAllowsCustomCurlMultiObjects() + { + $mock = $this->getMock('Guzzle\\Http\\Curl\\CurlMulti', array('add', 'send')); + $mock->expects($this->once()) + ->method('add') + ->will($this->returnSelf()); + $mock->expects($this->once()) + ->method('send') + ->will($this->returnSelf()); + + $client = new Client(); + $client->setCurlMulti($mock); + + $request = $client->get(); + $request->setResponse(new Response(200), true); + $client->send($request); + } + + public function testClientSendsMultipleRequests() + { + $client = new Client($this->getServer()->getUrl()); + $mock = new MockPlugin(); + + $responses = array( + new Response(200), + new Response(201), + new Response(202) + ); + + $mock->addResponse($responses[0]); + $mock->addResponse($responses[1]); + $mock->addResponse($responses[2]); + + $client->getEventDispatcher()->addSubscriber($mock); + + $requests = array( + $client->get(), + $client->head(), + $client->put('/', null, 'test') + ); + + $this->assertEquals(array( + $responses[0], + $responses[1], + $responses[2] + ), $client->send($requests)); + } + + public function testClientSendsSingleRequest() + { + $client = new Client($this->getServer()->getUrl()); + $mock = new MockPlugin(); + $response = new Response(200); + $mock->addResponse($response); + $client->getEventDispatcher()->addSubscriber($mock); + $this->assertEquals($response, $client->send($client->get())); + } + + /** + * @expectedException \Guzzle\Http\Exception\BadResponseException + */ + public function testClientThrowsExceptionForSingleRequest() + { + $client = new Client($this->getServer()->getUrl()); + $mock = new MockPlugin(); + $response = new Response(404); + $mock->addResponse($response); + $client->getEventDispatcher()->addSubscriber($mock); + $client->send($client->get()); + } + + /** + * @expectedException \Guzzle\Common\Exception\ExceptionCollection + */ + public function testClientThrowsExceptionForMultipleRequests() + { + $client = new Client($this->getServer()->getUrl()); + $mock = new MockPlugin(); + $mock->addResponse(new Response(200)); + $mock->addResponse(new Response(404)); + $client->getEventDispatcher()->addSubscriber($mock); + $client->send(array($client->get(), $client->head())); + } + + public function testQueryStringsAreNotDoubleEncoded() + { + $client = new Client('http://test.com', array( + 'path' => array('foo', 'bar'), + 'query' => 'hi there', + 'data' => array( + 'test' => 'a&b' + ) + )); + + $request = $client->get('{/path*}{?query,data*}'); + $this->assertEquals('http://test.com/foo/bar?query=hi%20there&test=a%26b', $request->getUrl()); + $this->assertEquals('hi there', $request->getQuery()->get('query')); + $this->assertEquals('a&b', $request->getQuery()->get('test')); + } + + public function testQueryStringsAreNotDoubleEncodedUsingAbsolutePaths() + { + $client = new Client('http://test.com', array( + 'path' => array('foo', 'bar'), + 'query' => 'hi there', + )); + $request = $client->get('http://test.com{?query}'); + $this->assertEquals('http://test.com?query=hi%20there', $request->getUrl()); + $this->assertEquals('hi there', $request->getQuery()->get('query')); + } + + public function testAllowsUriTemplateInjection() + { + $client = new Client('http://test.com'); + $ref = new \ReflectionMethod($client, 'getUriTemplate'); + $ref->setAccessible(true); + $a = $ref->invoke($client); + $this->assertSame($a, $ref->invoke($client)); + $client->setUriTemplate(new UriTemplate()); + $this->assertNotSame($a, $ref->invoke($client)); + } + + public function testAllowsCustomVariablesWhenExpandingTemplates() + { + $client = new Client('http://test.com', array('test' => 'hi')); + $ref = new \ReflectionMethod($client, 'expandTemplate'); + $ref->setAccessible(true); + $uri = $ref->invoke($client, 'http://{test}{?query*}', array('query' => array('han' => 'solo'))); + $this->assertEquals('http://hi?han=solo', $uri); + } + + public function testUriArrayAllowsCustomTemplateVariables() + { + $client = new Client(); + $vars = array( + 'var' => 'hi' + ); + $this->assertEquals('/hi', (string) $client->createRequest('GET', array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->get(array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->put(array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->post(array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->head(array('/{var}', $vars))->getUrl()); + $this->assertEquals('/hi', (string) $client->options(array('/{var}', $vars))->getUrl()); + } + + public function testAllowsDefaultHeaders() + { + Version::$emitWarnings = false; + $default = array('X-Test' => 'Hi!'); + $other = array('X-Other' => 'Foo'); + + $client = new Client(); + $client->setDefaultHeaders($default); + $this->assertEquals($default, $client->getDefaultHeaders()->getAll()); + $client->setDefaultHeaders(new Collection($default)); + $this->assertEquals($default, $client->getDefaultHeaders()->getAll()); + + $request = $client->createRequest('GET', null, $other); + $this->assertEquals('Hi!', $request->getHeader('X-Test')); + $this->assertEquals('Foo', $request->getHeader('X-Other')); + + $request = $client->createRequest('GET', null, new Collection($other)); + $this->assertEquals('Hi!', $request->getHeader('X-Test')); + $this->assertEquals('Foo', $request->getHeader('X-Other')); + + $request = $client->createRequest('GET'); + $this->assertEquals('Hi!', $request->getHeader('X-Test')); + Version::$emitWarnings = true; + } + + public function testDontReuseCurlMulti() + { + $client1 = new Client(); + $client2 = new Client(); + $this->assertNotSame($client1->getCurlMulti(), $client2->getCurlMulti()); + } + + public function testGetDefaultUserAgent() + { + $client = new Client(); + $agent = $this->readAttribute($client, 'userAgent'); + $version = curl_version(); + $testAgent = sprintf('Guzzle/%s curl/%s PHP/%s', Version::VERSION, $version['version'], PHP_VERSION); + $this->assertEquals($agent, $testAgent); + + $client->setUserAgent('foo'); + $this->assertEquals('foo', $this->readAttribute($client, 'userAgent')); + } + + public function testOverwritesUserAgent() + { + $client = new Client(); + $request = $client->createRequest('GET', 'http://www.foo.com', array('User-agent' => 'foo')); + $this->assertEquals('foo', (string) $request->getHeader('User-Agent')); + } + + public function testUsesDefaultUserAgent() + { + $client = new Client(); + $request = $client->createRequest('GET', 'http://www.foo.com'); + $this->assertContains('Guzzle/', (string) $request->getHeader('User-Agent')); + } + + public function testCanSetDefaultRequestOptions() + { + $client = new Client(); + $client->getConfig()->set('request.options', array( + 'query' => array('test' => '123', 'other' => 'abc'), + 'headers' => array('Foo' => 'Bar', 'Baz' => 'Bam') + )); + $request = $client->createRequest('GET', 'http://www.foo.com?test=hello', array('Foo' => 'Test')); + // Explicit options on a request should overrule default options + $this->assertEquals('Test', (string) $request->getHeader('Foo')); + $this->assertEquals('hello', $request->getQuery()->get('test')); + // Default options should still be set + $this->assertEquals('abc', $request->getQuery()->get('other')); + $this->assertEquals('Bam', (string) $request->getHeader('Baz')); + } + + public function testCanSetSetOptionsOnRequests() + { + $client = new Client(); + $request = $client->createRequest('GET', 'http://www.foo.com?test=hello', array('Foo' => 'Test'), null, array( + 'cookies' => array('michael' => 'test') + )); + $this->assertEquals('test', $request->getCookie('michael')); + } + + public function testHasDefaultOptionsHelperMethods() + { + $client = new Client(); + // With path + $client->setDefaultOption('headers/foo', 'bar'); + $this->assertEquals('bar', $client->getDefaultOption('headers/foo')); + // With simple key + $client->setDefaultOption('allow_redirects', false); + $this->assertFalse($client->getDefaultOption('allow_redirects')); + + $this->assertEquals(array( + 'headers' => array('foo' => 'bar'), + 'allow_redirects' => false + ), $client->getConfig('request.options')); + + $request = $client->get('/'); + $this->assertEquals('bar', $request->getHeader('foo')); + } + + public function testHeadCanUseOptions() + { + $client = new Client(); + $head = $client->head('http://www.foo.com', array(), array('query' => array('foo' => 'bar'))); + $this->assertEquals('bar', $head->getQuery()->get('foo')); + } + + public function testCanSetRelativeUrlStartingWithHttp() + { + $client = new Client('http://www.foo.com'); + $this->assertEquals( + 'http://www.foo.com/httpfoo', + $client->createRequest('GET', 'httpfoo')->getUrl() + ); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php new file mode 100644 index 0000000..5bf28de --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlHandleTest.php @@ -0,0 +1,947 @@ +getEventDispatcher()->addListener('request.sent', function (Event $e) use ($that) { + $that->requestHandle = $e['handle']; + }); + + return $request; + } + + public function setUp() + { + $this->requestHandle = null; + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructorExpectsCurlResource() + { + $h = new CurlHandle(false, array()); + } + + public function testConstructorExpectsProperOptions() + { + $h = curl_init($this->getServer()->getUrl()); + try { + $ha = new CurlHandle($h, false); + $this->fail('Expected InvalidArgumentException'); + } catch (\InvalidArgumentException $e) { + } + + $ha = new CurlHandle($h, array( + CURLOPT_URL => $this->getServer()->getUrl() + )); + $this->assertEquals($this->getServer()->getUrl(), $ha->getOptions()->get(CURLOPT_URL)); + + $ha = new CurlHandle($h, new Collection(array( + CURLOPT_URL => $this->getServer()->getUrl() + ))); + $this->assertEquals($this->getServer()->getUrl(), $ha->getOptions()->get(CURLOPT_URL)); + } + + public function testConstructorInitializesObject() + { + $handle = curl_init($this->getServer()->getUrl()); + $h = new CurlHandle($handle, array( + CURLOPT_URL => $this->getServer()->getUrl() + )); + $this->assertSame($handle, $h->getHandle()); + $this->assertInstanceOf('Guzzle\\Http\\Url', $h->getUrl()); + $this->assertEquals($this->getServer()->getUrl(), (string) $h->getUrl()); + $this->assertEquals($this->getServer()->getUrl(), $h->getOptions()->get(CURLOPT_URL)); + } + + public function testStoresStdErr() + { + $request = RequestFactory::getInstance()->create('GET', 'http://test.com'); + $request->getCurlOptions()->set('debug', true); + $h = CurlHandle::factory($request); + $this->assertEquals($h->getStderr(true), $h->getOptions()->get(CURLOPT_STDERR)); + $this->assertInternalType('resource', $h->getStderr(true)); + $this->assertInternalType('string', $h->getStderr(false)); + $r = $h->getStderr(true); + fwrite($r, 'test'); + $this->assertEquals('test', $h->getStderr(false)); + } + + public function testStoresCurlErrorNumber() + { + $h = new CurlHandle(curl_init('http://test.com'), array(CURLOPT_URL => 'http://test.com')); + $this->assertEquals(CURLE_OK, $h->getErrorNo()); + $h->setErrorNo(CURLE_OPERATION_TIMEOUTED); + $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $h->getErrorNo()); + } + + public function testAccountsForMissingStdErr() + { + $handle = curl_init('http://www.test.com/'); + $h = new CurlHandle($handle, array( + CURLOPT_URL => 'http://www.test.com/' + )); + $this->assertNull($h->getStderr(false)); + } + + public function testDeterminesIfResourceIsAvailable() + { + $handle = curl_init($this->getServer()->getUrl()); + $h = new CurlHandle($handle, array()); + $this->assertTrue($h->isAvailable()); + + // Mess it up by closing the handle + curl_close($handle); + $this->assertFalse($h->isAvailable()); + + // Mess it up by unsetting the handle + $handle = null; + $this->assertFalse($h->isAvailable()); + } + + public function testWrapsErrorsAndInfo() + { + if (!defined('CURLOPT_TIMEOUT_MS')) { + $this->markTestSkipped('Update curl'); + } + + $settings = array( + CURLOPT_PORT => 123, + CURLOPT_CONNECTTIMEOUT_MS => 1, + CURLOPT_TIMEOUT_MS => 1 + ); + + $handle = curl_init($this->getServer()->getUrl()); + curl_setopt_array($handle, $settings); + $h = new CurlHandle($handle, $settings); + @curl_exec($handle); + + $errors = array( + "couldn't connect to host", + 'timeout was reached', + 'connection time-out', + 'connect() timed out!', + 'failed connect to 127.0.0.1:123; connection refused', + 'failed to connect to 127.0.0.1 port 123: connection refused' + ); + $this->assertTrue(in_array(strtolower($h->getError()), $errors), $h->getError() . ' was not the error'); + + $this->assertTrue($h->getErrorNo() > 0); + + $this->assertEquals($this->getServer()->getUrl(), $h->getInfo(CURLINFO_EFFECTIVE_URL)); + $this->assertInternalType('array', $h->getInfo()); + + curl_close($handle); + $this->assertEquals(null, $h->getInfo('url')); + } + + public function testGetInfoWithoutDebugMode() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get($this->getServer()->getUrl()); + $response = $request->send(); + + $info = $response->getInfo(); + $this->assertFalse(empty($info)); + $this->assertEquals($this->getServer()->getUrl(), $info['url']); + } + + public function testWrapsCurlOptions() + { + $handle = curl_init($this->getServer()->getUrl()); + $h = new CurlHandle($handle, array( + CURLOPT_AUTOREFERER => true, + CURLOPT_BUFFERSIZE => 1024 + )); + + $this->assertEquals(true, $h->getOptions()->get(CURLOPT_AUTOREFERER)); + $this->assertEquals(1024, $h->getOptions()->get(CURLOPT_BUFFERSIZE)); + } + + /** + * Data provider for factory tests + * + * @return array + */ + public function dataProvider() + { + $testFile = __DIR__ . '/../../../../../phpunit.xml.dist'; + + $postBody = new QueryString(array('file' => '@' . $testFile)); + $qs = new QueryString(array( + 'x' => 'y', + 'z' => 'a' + )); + + $client = new Client(); + $userAgent = $client->getDefaultUserAgent(); + $auth = base64_encode('michael:123'); + $testFileSize = filesize($testFile); + + $tests = array( + // Send a regular GET + array('GET', 'http://www.google.com/', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent), + )), + // Test that custom request methods can be used + array('TRACE', 'http://www.google.com/', null, null, array( + CURLOPT_CUSTOMREQUEST => 'TRACE' + )), + // Send a GET using a port + array('GET', 'http://127.0.0.1:8080', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_PORT => 8080, + CURLOPT_HTTPHEADER => array('Accept:', 'Host: 127.0.0.1:8080', 'User-Agent: ' . $userAgent), + )), + // Send a HEAD request + array('HEAD', 'http://www.google.com/', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent), + CURLOPT_NOBODY => 1 + )), + // Send a GET using basic auth + array('GET', 'https://michael:123@127.0.0.1/index.html?q=2', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_HTTPHEADER => array( + 'Accept:', + 'Host: 127.0.0.1', + 'Authorization: Basic ' . $auth, + 'User-Agent: ' . $userAgent + ), + CURLOPT_PORT => 443 + )), + // Send a GET request with custom headers + array('GET', 'http://127.0.0.1:8124/', array( + 'x-test-data' => 'Guzzle' + ), null, array( + CURLOPT_PORT => 8124, + CURLOPT_HTTPHEADER => array( + 'Accept:', + 'Host: 127.0.0.1:8124', + 'x-test-data: Guzzle', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'x-test-data' => 'Guzzle' + )), + // Send a POST using a query string + array('POST', 'http://127.0.0.1:8124/post.php', null, $qs, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_POSTFIELDS => 'x=y&z=a', + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Content-Type: application/x-www-form-urlencoded; charset=utf-8', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '7', + '!Expect' => null, + 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8', + '!Transfer-Encoding' => null + )), + // Send a PUT using raw data + array('PUT', 'http://127.0.0.1:8124/put.php', null, EntityBody::factory(fopen($testFile, 'r+')), array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_READFUNCTION => 'callback', + CURLOPT_INFILESIZE => filesize($testFile), + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + '!Expect' => null, + 'Content-Length' => $testFileSize, + '!Transfer-Encoding' => null + )), + // Send a POST request using an array of fields + array('POST', 'http://127.0.0.1:8124/post.php', null, array( + 'x' => 'y', + 'a' => 'b' + ), array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => 'x=y&a=b', + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Content-Type: application/x-www-form-urlencoded; charset=utf-8', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '7', + '!Expect' => null, + 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8', + '!Transfer-Encoding' => null + )), + // Send a POST request with raw POST data and a custom content-type + array('POST', 'http://127.0.0.1:8124/post.php', array( + 'Content-Type' => 'application/json' + ), '{"hi":"there"}', array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_UPLOAD => true, + CURLOPT_INFILESIZE => 14, + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Content-Type: application/json', + 'User-Agent: ' . $userAgent + ), + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Type' => 'application/json', + '!Expect' => null, + 'Content-Length' => '14', + '!Transfer-Encoding' => null + )), + // Send a POST request with raw POST data, a custom content-type, and use chunked encoding + array('POST', 'http://127.0.0.1:8124/post.php', array( + 'Content-Type' => 'application/json', + 'Transfer-Encoding' => 'chunked' + ), '{"hi":"there"}', array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_UPLOAD => true, + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Transfer-Encoding: chunked', + 'Content-Type: application/json', + 'User-Agent: ' . $userAgent + ), + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Type' => 'application/json', + '!Expect' => null, + 'Transfer-Encoding' => 'chunked', + '!Content-Length' => '' + )), + // Send a POST request with no body + array('POST', 'http://127.0.0.1:8124/post.php', null, '', array( + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '0', + '!Transfer-Encoding' => null + )), + // Send a POST request with empty post fields + array('POST', 'http://127.0.0.1:8124/post.php', null, array(), array( + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '0', + '!Transfer-Encoding' => null + )), + // Send a PATCH request + array('PATCH', 'http://127.0.0.1:8124/patch.php', null, 'body', array( + CURLOPT_INFILESIZE => 4, + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + )), + // Send a DELETE request with a body + array('DELETE', 'http://127.0.0.1:8124/delete.php', null, 'body', array( + CURLOPT_CUSTOMREQUEST => 'DELETE', + CURLOPT_INFILESIZE => 4, + CURLOPT_HTTPHEADER => array ( + 'Expect:', + 'Accept:', + 'Host: 127.0.0.1:8124', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '4', + '!Expect' => null, + '!Transfer-Encoding' => null + )), + + /** + * Send a request with empty path and a fragment - the fragment must be + * stripped out before sending it to curl + * + * @issue 453 + * @link https://github.com/guzzle/guzzle/issues/453 + */ + array('GET', 'http://www.google.com#head', null, null, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_HTTPHEADER => array('Accept:', 'Host: www.google.com', 'User-Agent: ' . $userAgent), + )), + ); + + $postTest = array('POST', 'http://127.0.0.1:8124/post.php', null, $postBody, array( + CURLOPT_RETURNTRANSFER => 0, + CURLOPT_HEADER => 0, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_WRITEFUNCTION => 'callback', + CURLOPT_HEADERFUNCTION => 'callback', + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => array( + 'file' => '@' . $testFile . ';filename=phpunit.xml.dist;type=application/octet-stream' + ), + CURLOPT_HTTPHEADER => array ( + 'Accept:', + 'Host: 127.0.0.1:8124', + 'Content-Type: multipart/form-data', + 'Expect: 100-Continue', + 'User-Agent: ' . $userAgent + ) + ), array( + 'Host' => '*', + 'User-Agent' => '*', + 'Content-Length' => '*', + 'Expect' => '100-Continue', + 'Content-Type' => 'multipart/form-data; boundary=*', + '!Transfer-Encoding' => null + )); + + if (version_compare(phpversion(), '5.5.0', '>=')) { + $postTest[4][CURLOPT_POSTFIELDS] = array( + 'file' => new \CurlFile($testFile, 'application/octet-stream', 'phpunit.xml.dist') + ); + } + + $tests[] = $postTest; + + return $tests; + } + + /** + * @dataProvider dataProvider + */ + public function testFactoryCreatesCurlBasedOnRequest($method, $url, $headers, $body, $options, $expectedHeaders = null) + { + $client = new Client(); + $request = $client->createRequest($method, $url, $headers, $body); + $request->getCurlOptions()->set('debug', true); + + $originalRequest = clone $request; + $curlTest = clone $request; + $handle = CurlHandle::factory($curlTest); + + $this->assertInstanceOf('Guzzle\\Http\\Curl\\CurlHandle', $handle); + $o = $handle->getOptions()->getAll(); + + // Headers are case-insensitive + if (isset($o[CURLOPT_HTTPHEADER])) { + $o[CURLOPT_HTTPHEADER] = array_map('strtolower', $o[CURLOPT_HTTPHEADER]); + } + if (isset($options[CURLOPT_HTTPHEADER])) { + $options[CURLOPT_HTTPHEADER] = array_map('strtolower', $options[CURLOPT_HTTPHEADER]); + } + + $check = 0; + foreach ($options as $key => $value) { + $check++; + $this->assertArrayHasKey($key, $o, '-> Check number ' . $check); + if ($key != CURLOPT_HTTPHEADER && $key != CURLOPT_POSTFIELDS && (is_array($o[$key])) || $o[$key] instanceof \Closure) { + $this->assertEquals('callback', $value, '-> Check number ' . $check); + } else { + $this->assertTrue($value == $o[$key], '-> Check number ' . $check . ' - ' . var_export($value, true) . ' != ' . var_export($o[$key], true)); + } + } + + // If we are testing the actual sent headers + if ($expectedHeaders) { + + // Send the request to the test server + $client = new Client($this->getServer()->getUrl()); + $request->setClient($client); + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request->send(); + + // Get the request that was sent and create a request that we expected + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals($method, $requests[0]->getMethod()); + + $test = $this->compareHeaders($expectedHeaders, $requests[0]->getHeaders()); + $this->assertFalse($test, $test . "\nSent: \n" . $request . "\n\n" . $requests[0]); + + // Ensure only one Content-Length header is sent + if ($request->getHeader('Content-Length')) { + $this->assertEquals((string) $request->getHeader('Content-Length'), (string) $requests[0]->getHeader('Content-Length')); + } + } + } + + public function testFactoryUsesSpecifiedProtocol() + { + $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:8124/'); + $request->setProtocolVersion('1.1'); + $handle = CurlHandle::factory($request); + $options = $handle->getOptions(); + $this->assertEquals(CURL_HTTP_VERSION_1_1, $options[CURLOPT_HTTP_VERSION]); + } + + public function testUploadsPutData() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/'); + $request->getCurlOptions()->set('debug', true); + $request->setBody(EntityBody::factory('test'), 'text/plain', false); + $request->getCurlOptions()->set('progress', true); + + $o = $this->getWildcardObserver($request); + $request->send(); + + // Make sure that the events were dispatched + $this->assertTrue($o->has('curl.callback.progress')); + + // Ensure that the request was received exactly as intended + $r = $this->getServer()->getReceivedRequests(true); + $this->assertFalse($r[0]->hasHeader('Transfer-Encoding')); + $this->assertEquals(4, (string) $r[0]->getHeader('Content-Length')); + $sent = strtolower($r[0]); + $this->assertContains('put / http/1.1', $sent); + $this->assertContains('host: 127.0.0.1', $sent); + $this->assertContains('user-agent:', $sent); + $this->assertContains('content-type: text/plain', $sent); + } + + public function testUploadsPutDataUsingChunkedEncodingWhenLengthCannotBeDetermined() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi" + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/'); + $request->setBody(EntityBody::factory(fopen($this->getServer()->getUrl(), 'r')), 'text/plain'); + $request->send(); + + $r = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('chunked', $r[1]->getHeader('Transfer-Encoding')); + $this->assertFalse($r[1]->hasHeader('Content-Length')); + } + + public function testUploadsPutDataUsingChunkedEncodingWhenForced() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', array('Transfer-Encoding' => 'chunked'), 'hi!'); + $request->send(); + + $r = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('chunked', $r[0]->getHeader('Transfer-Encoding')); + $this->assertFalse($r[0]->hasHeader('Content-Length')); + $this->assertEquals('hi!', $r[0]->getBody(true)); + } + + public function testSendsPostRequestsWithFields() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + + $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl()); + $request->getCurlOptions()->set('debug', true); + $request->setClient(new Client()); + $request->addPostFields(array( + 'a' => 'b', + 'c' => 'ay! ~This is a test, isn\'t it?' + )); + $request->send(); + + // Make sure that the request was sent correctly + $r = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('a=b&c=ay%21%20~This%20is%20a%20test%2C%20isn%27t%20it%3F', (string) $r[0]->getBody()); + $this->assertFalse($r[0]->hasHeader('Transfer-Encoding')); + $this->assertEquals(56, (string) $r[0]->getHeader('Content-Length')); + $sent = strtolower($r[0]); + $this->assertContains('post / http/1.1', $sent); + $this->assertContains('content-type: application/x-www-form-urlencoded; charset=utf-8', $sent); + } + + public function testSendsPostRequestsWithFiles() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + + $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl()); + $request->getCurlOptions()->set('debug', true); + $request->setClient(new Client()); + $request->addPostFiles(array( + 'foo' => __FILE__, + )); + $request->addPostFields(array( + 'bar' => 'baz', + 'arr' => array('a' => 1, 'b' => 2), + )); + $this->updateForHandle($request); + $request->send(); + + // Ensure the CURLOPT_POSTFIELDS option was set properly + $options = $this->requestHandle->getOptions()->getAll(); + if (version_compare(phpversion(), '5.5.0', '<')) { + $this->assertContains('@' . __FILE__ . ';filename=CurlHandleTest.php;type=text/x-', $options[CURLOPT_POSTFIELDS]['foo']); + } else{ + $this->assertInstanceOf('CURLFile', $options[CURLOPT_POSTFIELDS]['foo']); + } + $this->assertEquals('baz', $options[CURLOPT_POSTFIELDS]['bar']); + $this->assertEquals('1', $options[CURLOPT_POSTFIELDS]['arr[a]']); + $this->assertEquals('2', $options[CURLOPT_POSTFIELDS]['arr[b]']); + // Ensure that a Content-Length header was sent by cURL + $this->assertTrue($request->hasHeader('Content-Length')); + } + + public function testCurlConfigurationOptionsAreSet() + { + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()); + $request->setClient(new Client('http://www.example.com')); + $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, 99); + $request->getCurlOptions()->set('curl.fake_opt', 99); + $request->getCurlOptions()->set(CURLOPT_PORT, 8181); + $handle = CurlHandle::factory($request); + $this->assertEquals(99, $handle->getOptions()->get(CURLOPT_CONNECTTIMEOUT)); + $this->assertEquals(8181, $handle->getOptions()->get(CURLOPT_PORT)); + $this->assertNull($handle->getOptions()->get('curl.fake_opt')); + $this->assertNull($handle->getOptions()->get('fake_opt')); + } + + public function testEnsuresRequestsHaveResponsesWhenUpdatingFromTransfer() + { + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()); + $handle = CurlHandle::factory($request); + $handle->updateRequestFromTransfer($request); + } + + public function testCanSendBodyAsString() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', null, 'foo'); + $request->getCurlOptions()->set('body_as_string', true); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(false); + $this->assertContains('PUT /', $requests[0]); + $this->assertContains("\nfoo", $requests[0]); + $this->assertContains('content-length: 3', $requests[0]); + $this->assertNotContains('content-type', $requests[0]); + } + + public function testCanSendPostBodyAsString() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/', null, 'foo'); + $request->getCurlOptions()->set('body_as_string', true); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(false); + $this->assertContains('POST /', $requests[0]); + $this->assertContains("\nfoo", $requests[0]); + $this->assertContains('content-length: 3', $requests[0]); + $this->assertNotContains('content-type', $requests[0]); + } + + public function testAllowsWireTransferInfoToBeEnabled() + { + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()); + $request->getCurlOptions()->set('debug', true); + $handle = CurlHandle::factory($request); + $this->assertNotNull($handle->getOptions()->get(CURLOPT_STDERR)); + $this->assertNotNull($handle->getOptions()->get(CURLOPT_VERBOSE)); + } + + public function testAddsCustomCurlOptions() + { + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()); + $request->getCurlOptions()->set(CURLOPT_TIMEOUT, 200); + $handle = CurlHandle::factory($request); + $this->assertEquals(200, $handle->getOptions()->get(CURLOPT_TIMEOUT)); + } + + public function testSendsPostUploadsWithContentDispositionHeaders() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n"); + + $fileToUpload = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'TestData' . DIRECTORY_SEPARATOR . 'test_service.json'; + + $client = new Client($this->getServer()->getUrl()); + $request = $client->post(); + $request->addPostFile('foo', $fileToUpload, 'application/json'); + $request->addPostFile('foo', __FILE__); + + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $body = (string) $requests[0]->getBody(); + + $this->assertContains('Content-Disposition: form-data; name="foo[0]"; filename="', $body); + $this->assertContains('Content-Type: application/json', $body); + $this->assertContains('Content-Type: text/x-', $body); + $this->assertContains('Content-Disposition: form-data; name="foo[1]"; filename="', $body); + } + + public function requestMethodProvider() + { + return array(array('POST'), array('PUT'), array('PATCH')); + } + + /** + * @dataProvider requestMethodProvider + */ + public function testSendsRequestsWithNoBodyUsingContentLengthZero($method) + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $client->createRequest($method)->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertFalse($requests[0]->hasHeader('Transfer-Encoding')); + $this->assertTrue($requests[0]->hasHeader('Content-Length')); + $this->assertEquals('0', (string) $requests[0]->getHeader('Content-Length')); + } + + /** + * @dataProvider provideCurlConfig + */ + public function testParseCurlConfigConvertsStringKeysToConstantKeys($options, $expected) + { + $actual = CurlHandle::parseCurlConfig($options); + $this->assertEquals($expected, $actual); + } + + /** + * Data provider for curl configurations + * + * @return array + */ + public function provideCurlConfig() + { + return array( + // Conversion of option name to constant value + array( + array( + 'CURLOPT_PORT' => 10, + 'CURLOPT_TIMEOUT' => 99 + ), + array( + CURLOPT_PORT => 10, + CURLOPT_TIMEOUT => 99 + ) + ), + // Keeps non constant options + array( + array('debug' => true), + array('debug' => true) + ), + // Conversion of constant names to constant values + array( + array('debug' => 'CURLPROXY_HTTP'), + array('debug' => CURLPROXY_HTTP) + ) + ); + } + + public function testSeeksToBeginningOfStreamWhenSending() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', null, 'test'); + $request->send(); + $request->send(); + + $received = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(2, count($received)); + $this->assertEquals('test', (string) $received[0]->getBody()); + $this->assertEquals('test', (string) $received[1]->getBody()); + } + + public function testAllowsCurloptEncodingToBeSet() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/', null); + $request->getCurlOptions()->set(CURLOPT_ENCODING, ''); + $this->updateForHandle($request); + $request->send(); + $options = $this->requestHandle->getOptions()->getAll(); + $this->assertSame('', $options[CURLOPT_ENCODING]); + $received = $this->getServer()->getReceivedRequests(false); + $this->assertContainsIns('accept: */*', $received[0]); + $this->assertContainsIns('accept-encoding: ', $received[0]); + } + + public function testSendsExpectHeaderWhenSizeIsGreaterThanCutoff() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', null, 'test'); + // Start sending the expect header to 2 bytes + $this->updateForHandle($request); + $request->setExpectHeaderCutoff(2)->send(); + $options = $this->requestHandle->getOptions()->getAll(); + $this->assertContains('Expect: 100-Continue', $options[CURLOPT_HTTPHEADER]); + $received = $this->getServer()->getReceivedRequests(false); + $this->assertContainsIns('expect: 100-continue', $received[0]); + } + + public function testSetsCurloptEncodingWhenAcceptEncodingHeaderIsSet() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/', array( + 'Accept' => 'application/json', + 'Accept-Encoding' => 'gzip, deflate', + )); + $this->updateForHandle($request); + $request->send(); + $options = $this->requestHandle->getOptions()->getAll(); + $this->assertSame('gzip, deflate', $options[CURLOPT_ENCODING]); + $received = $this->getServer()->getReceivedRequests(false); + $this->assertContainsIns('accept: application/json', $received[0]); + $this->assertContainsIns('accept-encoding: gzip, deflate', $received[0]); + } + + public function testSendsPostFieldsForNonPostRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client(); + $request = $client->put($this->getServer()->getUrl(), null, array( + 'foo' => 'baz', + 'baz' => 'bar' + )); + + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('PUT', $requests[0]->getMethod()); + $this->assertEquals( + 'application/x-www-form-urlencoded; charset=utf-8', + (string) $requests[0]->getHeader('Content-Type') + ); + $this->assertEquals(15, (string) $requests[0]->getHeader('Content-Length')); + $this->assertEquals('foo=baz&baz=bar', (string) $requests[0]->getBody()); + } + + public function testSendsPostFilesForNonPostRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\n\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client(); + $request = $client->put($this->getServer()->getUrl(), null, array( + 'foo' => '@' . __FILE__ + )); + + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('PUT', $requests[0]->getMethod()); + $this->assertContains('multipart/form-data', (string) $requests[0]->getHeader('Content-Type')); + $this->assertContains('testSendsPostFilesForNonPostRequests', (string) $requests[0]->getBody()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php new file mode 100644 index 0000000..e04141c --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiProxyTest.php @@ -0,0 +1,110 @@ +multi = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT); + } + + public function tearDown() + { + unset($this->multi); + } + + public function testConstructorSetsMaxHandles() + { + $m = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT); + $this->assertEquals(self::MAX_HANDLES, $this->readAttribute($m, 'maxHandles')); + } + + public function testConstructorSetsSelectTimeout() + { + $m = new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT); + $this->assertEquals(self::SELECT_TIMEOUT, $this->readAttribute($m, 'selectTimeout')); + } + + public function testAddingRequestsAddsToQueue() + { + $r = new Request('GET', 'http://www.foo.com'); + $this->assertSame($this->multi, $this->multi->add($r)); + $this->assertEquals(1, count($this->multi)); + $this->assertEquals(array($r), $this->multi->all()); + + $this->assertTrue($this->multi->remove($r)); + $this->assertFalse($this->multi->remove($r)); + $this->assertEquals(0, count($this->multi)); + } + + public function testResetClearsState() + { + $r = new Request('GET', 'http://www.foo.com'); + $this->multi->add($r); + $this->multi->reset(); + $this->assertEquals(0, count($this->multi)); + } + + public function testSendWillSendQueuedRequestsFirst() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $events = array(); + $client->getCurlMulti()->getEventDispatcher()->addListener( + CurlMultiProxy::ADD_REQUEST, + function ($e) use (&$events) { + $events[] = $e; + } + ); + $request = $client->get(); + $request->getEventDispatcher()->addListener('request.complete', function () use ($client) { + $client->get('/foo')->send(); + }); + $request->send(); + $received = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(2, count($received)); + $this->assertEquals($this->getServer()->getUrl(), $received[0]->getUrl()); + $this->assertEquals($this->getServer()->getUrl() . 'foo', $received[1]->getUrl()); + $this->assertEquals(2, count($events)); + } + + public function testTrimsDownMaxHandleCount() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 307 OK\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $client->setCurlMulti(new CurlMultiProxy(self::MAX_HANDLES, self::SELECT_TIMEOUT)); + $request = $client->get(); + $request->send(); + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $handles = $this->readAttribute($client->getCurlMulti(), 'handles'); + $this->assertEquals(2, count($handles)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php new file mode 100644 index 0000000..1272281 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlMultiTest.php @@ -0,0 +1,455 @@ +multi = new MockMulti(); + } + + public function tearDown() + { + unset($this->multi); + } + + public function testConstructorCreateMultiHandle() + { + $this->assertInternalType('resource', $this->multi->getHandle()); + $this->assertEquals('curl_multi', get_resource_type($this->multi->getHandle())); + } + + public function testDestructorClosesMultiHandle() + { + $handle = $this->multi->getHandle(); + $this->multi->__destruct(); + $this->assertFalse(is_resource($handle)); + } + + public function testRequestsCanBeAddedAndCounted() + { + $multi = new CurlMulti(); + $request1 = new Request('GET', 'http://www.google.com/'); + $multi->add($request1); + $this->assertEquals(array($request1), $multi->all()); + $request2 = new Request('POST', 'http://www.google.com/'); + $multi->add($request2); + $this->assertEquals(array($request1, $request2), $multi->all()); + $this->assertEquals(2, count($multi)); + } + + public function testRequestsCanBeRemoved() + { + $request1 = new Request('GET', 'http://www.google.com/'); + $this->multi->add($request1); + $request2 = new Request('PUT', 'http://www.google.com/'); + $this->multi->add($request2); + $this->assertEquals(array($request1, $request2), $this->multi->all()); + $this->assertTrue($this->multi->remove($request1)); + $this->assertFalse($this->multi->remove($request1)); + $this->assertEquals(array($request2), $this->multi->all()); + } + + public function testsResetRemovesRequestsAndResetsState() + { + $this->multi->add(new Request('GET', 'http://www.google.com/')); + $this->multi->reset(); + $this->assertEquals(array(), $this->multi->all()); + } + + public function testSendsRequestsThroughCurl() + { + $this->getServer()->enqueue(array( + "HTTP/1.1 204 No content\r\n" . + "Content-Length: 0\r\n" . + "Server: Jetty(6.1.3)\r\n\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Type: text/html; charset=utf-8\r\n" . + "Content-Length: 4\r\n" . + "Server: Jetty(6.1.3)\r\n\r\n" . + "data" + )); + + $request1 = new Request('GET', $this->getServer()->getUrl()); + $request2 = new Request('GET', $this->getServer()->getUrl()); + $this->multi->add($request1); + $this->multi->add($request2); + $this->multi->send(); + + $response1 = $request1->getResponse(); + $response2 = $request2->getResponse(); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response1); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response2); + + $this->assertTrue($response1->getBody(true) == 'data' || $response2->getBody(true) == 'data'); + $this->assertTrue($response1->getBody(true) == '' || $response2->getBody(true) == ''); + $this->assertTrue($response1->getStatusCode() == '204' || $response2->getStatusCode() == '204'); + $this->assertNotEquals((string) $response1, (string) $response2); + } + + public function testSendsThroughCurlAndAggregatesRequestExceptions() + { + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n" . + "Content-Type: text/html; charset=utf-8\r\n" . + "Content-Length: 4\r\n" . + "Server: Jetty(6.1.3)\r\n" . + "\r\n" . + "data", + "HTTP/1.1 204 No content\r\n" . + "Content-Length: 0\r\n" . + "Server: Jetty(6.1.3)\r\n" . + "\r\n", + "HTTP/1.1 404 Not Found\r\n" . + "Content-Length: 0\r\n" . + "\r\n" + )); + + $request1 = new Request('GET', $this->getServer()->getUrl()); + $request2 = new Request('HEAD', $this->getServer()->getUrl()); + $request3 = new Request('GET', $this->getServer()->getUrl()); + $this->multi->add($request1); + $this->multi->add($request2); + $this->multi->add($request3); + + try { + $this->multi->send(); + $this->fail('MultiTransferException not thrown when aggregating request exceptions'); + } catch (MultiTransferException $e) { + + $this->assertTrue($e->containsRequest($request1)); + $this->assertTrue($e->containsRequest($request2)); + $this->assertTrue($e->containsRequest($request3)); + $this->assertInstanceOf('ArrayIterator', $e->getIterator()); + $this->assertEquals(1, count($e)); + $exceptions = $e->getIterator(); + + $response1 = $request1->getResponse(); + $response2 = $request2->getResponse(); + $response3 = $request3->getResponse(); + + $this->assertNotEquals((string) $response1, (string) $response2); + $this->assertNotEquals((string) $response3, (string) $response1); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response1); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response2); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response3); + + $failed = $exceptions[0]->getResponse(); + $this->assertEquals(404, $failed->getStatusCode()); + $this->assertEquals(1, count($e)); + + // Test the IteratorAggregate functionality + foreach ($e as $except) { + $this->assertEquals($failed, $except->getResponse()); + } + + $this->assertEquals(1, count($e->getFailedRequests())); + $this->assertEquals(2, count($e->getSuccessfulRequests())); + $this->assertEquals(3, count($e->getAllRequests())); + } + } + + public function testCurlErrorsAreCaught() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + try { + $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:9876/'); + $request->setClient(new Client()); + $request->getCurlOptions()->set(CURLOPT_FRESH_CONNECT, true); + $request->getCurlOptions()->set(CURLOPT_FORBID_REUSE, true); + $request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, 5); + $request->send(); + $this->fail('CurlException not thrown'); + } catch (CurlException $e) { + $m = $e->getMessage(); + $this->assertContains('[curl] ', $m); + $this->assertContains('[url] http://127.0.0.1:9876/', $m); + $this->assertInternalType('array', $e->getCurlInfo()); + } + } + + public function testRemovesQueuedRequests() + { + $request = RequestFactory::getInstance()->create('GET', 'http://127.0.0.1:9876/'); + $r = new Response(200); + $request->setClient(new Client()); + $request->setResponse($r, true); + $this->multi->add($request); + $this->multi->send(); + $this->assertSame($r, $request->getResponse()); + } + + public function testRemovesQueuedRequestsAddedInTransit() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $client = new Client($this->getServer()->getUrl()); + $r = $client->get(); + $r->getEventDispatcher()->addListener('request.receive.status_line', function (Event $event) use ($client) { + // Create a request using a queued response + $request = $client->get()->setResponse(new Response(200), true); + $request->send(); + }); + $r->send(); + $this->assertEquals(1, count($this->getServer()->getReceivedRequests(false))); + } + + public function testCatchesExceptionsBeforeSendingSingleRequest() + { + $client = new Client($this->getServer()->getUrl()); + $multi = new CurlMulti(); + $client->setCurlMulti($multi); + $request = $client->get(); + $request->getEventDispatcher()->addListener('request.before_send', function() { + throw new \RuntimeException('Testing!'); + }); + try { + $request->send(); + $this->fail('Did not throw'); + } catch (\RuntimeException $e) { + // Ensure it was removed + $this->assertEquals(0, count($multi)); + } + } + + /** + * @expectedException \Guzzle\Common\Exception\ExceptionCollection + * @expectedExceptionMessage Thrown before sending! + */ + public function testCatchesExceptionsBeforeSendingMultipleRequests() + { + $client = new Client($this->getServer()->getUrl()); + $request = $client->get(); + $request->getEventDispatcher()->addListener('request.before_send', function() { + throw new \RuntimeException('Thrown before sending!'); + }); + $client->send(array($request)); + } + + public function testCatchesExceptionsWhenRemovingQueuedRequests() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $r = $client->get(); + $r->getEventDispatcher()->addListener('request.sent', function() use ($client) { + // Create a request using a queued response + $client->get()->setResponse(new Response(404), true)->send(); + }); + try { + $r->send(); + $this->fail('Did not throw'); + } catch (BadResponseException $e) { + $this->assertCount(0, $client->getCurlMulti()); + } + } + + public function testCatchesExceptionsWhenRemovingQueuedRequestsBeforeSending() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $client = new Client($this->getServer()->getUrl()); + $r = $client->get(); + $r->getEventDispatcher()->addListener('request.before_send', function() use ($client) { + // Create a request using a queued response + $client->get()->setResponse(new Response(404), true)->send(); + }); + try { + $r->send(); + $this->fail('Did not throw'); + } catch (BadResponseException $e) { + $this->assertCount(0, $client->getCurlMulti()); + } + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage test + */ + public function testDoesNotCatchRandomExceptionsThrownDuringPerform() + { + $client = new Client($this->getServer()->getUrl()); + $multi = $this->getMock('Guzzle\\Http\\Curl\\CurlMulti', array('perform')); + $multi->expects($this->once()) + ->method('perform') + ->will($this->throwException(new \RuntimeException('test'))); + $multi->add($client->get()); + $multi->send(); + } + + public function testDoesNotSendRequestsDecliningToBeSent() + { + if (!defined('CURLOPT_TIMEOUT_MS')) { + $this->markTestSkipped('Update curl'); + } + + // Create a client that is bound to fail connecting + $client = new Client('http://127.0.0.1:123', array( + 'curl.CURLOPT_PORT' => 123, + 'curl.CURLOPT_CONNECTTIMEOUT_MS' => 1, + )); + + $request = $client->get(); + $multi = new CurlMulti(); + $multi->add($request); + + // Listen for request exceptions, and when they occur, first change the + // state of the request back to transferring, and then just allow it to + // exception out + $request->getEventDispatcher()->addListener('request.exception', function(Event $event) use ($multi) { + $retries = $event['request']->getParams()->get('retries'); + // Allow the first failure to retry + if ($retries == 0) { + $event['request']->setState('transfer'); + $event['request']->getParams()->set('retries', 1); + // Remove the request to try again + $multi->remove($event['request']); + $multi->add($event['request']); + } + }); + + try { + $multi->send(); + $this->fail('Did not throw an exception at all!?!'); + } catch (\Exception $e) { + $this->assertEquals(1, $request->getParams()->get('retries')); + } + } + + public function testDoesNotThrowExceptionsWhenRequestsRecoverWithRetry() + { + $this->getServer()->flush(); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get(); + $request->getEventDispatcher()->addListener('request.before_send', function(Event $event) { + $event['request']->setResponse(new Response(200)); + }); + + $multi = new CurlMulti(); + $multi->add($request); + $multi->send(); + $this->assertEquals(0, count($this->getServer()->getReceivedRequests(false))); + } + + public function testDoesNotThrowExceptionsWhenRequestsRecoverWithSuccess() + { + // Attempt a port that 99.9% is not listening + $client = new Client('http://127.0.0.1:123'); + $request = $client->get(); + // Ensure it times out quickly if needed + $request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, 1)->set(CURLOPT_CONNECTTIMEOUT_MS, 1); + + $request->getEventDispatcher()->addListener('request.exception', function(Event $event) use (&$count) { + $event['request']->setResponse(new Response(200)); + }); + + $multi = new CurlMulti(); + $multi->add($request); + $multi->send(); + + // Ensure that the exception was caught, and the response was set manually + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + } + + public function testHardResetReopensMultiHandle() + { + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + + $stream = fopen('php://temp', 'w+'); + $client = new Client($this->getServer()->getUrl()); + $client->getConfig()->set('curl.CURLOPT_VERBOSE', true)->set('curl.CURLOPT_STDERR', $stream); + + $request = $client->get(); + $multi = new CurlMulti(); + $multi->add($request); + $multi->send(); + $multi->reset(true); + $multi->add($request); + $multi->send(); + + rewind($stream); + $this->assertNotContains('Re-using existing connection', stream_get_contents($stream)); + } + + public function testThrowsMeaningfulExceptionsForCurlMultiErrors() + { + $multi = new CurlMulti(); + + // Set the state of the multi object to sending to trigger the exception + $reflector = new \ReflectionMethod('Guzzle\Http\Curl\CurlMulti', 'checkCurlResult'); + $reflector->setAccessible(true); + + // Successful + $reflector->invoke($multi, 0); + + // Known error + try { + $reflector->invoke($multi, CURLM_BAD_HANDLE); + $this->fail('Expected an exception here'); + } catch (CurlException $e) { + $this->assertContains('The passed-in handle is not a valid CURLM handle.', $e->getMessage()); + $this->assertContains('CURLM_BAD_HANDLE', $e->getMessage()); + $this->assertContains(strval(CURLM_BAD_HANDLE), $e->getMessage()); + } + + // Unknown error + try { + $reflector->invoke($multi, 255); + $this->fail('Expected an exception here'); + } catch (CurlException $e) { + $this->assertEquals('Unexpected cURL error: 255', $e->getMessage()); + } + } + + public function testRequestBeforeSendIncludesContentLengthHeaderIfEmptyBody() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = new Request('PUT', $this->getServer()->getUrl()); + $that = $this; + $request->getEventDispatcher()->addListener('request.before_send', function ($event) use ($that) { + $that->assertEquals(0, $event['request']->getHeader('Content-Length')); + }); + $this->multi->add($request); + $this->multi->send(); + } + + public function testRemovesConflictingTransferEncodingHeader() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put('/', null, fopen($this->getServer()->getUrl(), 'r')); + $request->setHeader('Content-Length', 4); + $request->send(); + $received = $this->getServer()->getReceivedRequests(true); + $this->assertFalse($received[1]->hasHeader('Transfer-Encoding')); + $this->assertEquals(4, (string) $received[1]->getHeader('Content-Length')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php new file mode 100644 index 0000000..c7b5ee6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/CurlVersionTest.php @@ -0,0 +1,39 @@ +getProperty('version'); + $refProperty->setAccessible(true); + $refProperty->setValue($instance, array()); + + $this->assertEquals($info, $instance->getAll()); + $this->assertEquals($info, $instance->getAll()); + + $this->assertEquals($info['version'], $instance->get('version')); + $this->assertFalse($instance->get('foo')); + } + + public function testIsSingleton() + { + $refObject = new \ReflectionClass('Guzzle\Http\Curl\CurlVersion'); + $refProperty = $refObject->getProperty('instance'); + $refProperty->setAccessible(true); + $refProperty->setValue(null, null); + + $this->assertInstanceOf('Guzzle\Http\Curl\CurlVersion', CurlVersion::getInstance()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php new file mode 100644 index 0000000..c69e0c9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Curl/RequestMediatorTest.php @@ -0,0 +1,67 @@ +events[] = $event; + } + + public function testEmitsEvents() + { + $request = new EntityEnclosingRequest('PUT', 'http://www.example.com'); + $request->setBody('foo'); + $request->setResponse(new Response(200)); + + // Ensure that IO events are emitted + $request->getCurlOptions()->set('emit_io', true); + + // Attach listeners for each event type + $request->getEventDispatcher()->addListener('curl.callback.progress', array($this, 'event')); + $request->getEventDispatcher()->addListener('curl.callback.read', array($this, 'event')); + $request->getEventDispatcher()->addListener('curl.callback.write', array($this, 'event')); + + $mediator = new RequestMediator($request, true); + + $mediator->progress('a', 'b', 'c', 'd'); + $this->assertEquals(1, count($this->events)); + $this->assertEquals('curl.callback.progress', $this->events[0]->getName()); + + $this->assertEquals(3, $mediator->writeResponseBody('foo', 'bar')); + $this->assertEquals(2, count($this->events)); + $this->assertEquals('curl.callback.write', $this->events[1]->getName()); + $this->assertEquals('bar', $this->events[1]['write']); + $this->assertSame($request, $this->events[1]['request']); + + $this->assertEquals('foo', $mediator->readRequestBody('a', 'b', 3)); + $this->assertEquals(3, count($this->events)); + $this->assertEquals('curl.callback.read', $this->events[2]->getName()); + $this->assertEquals('foo', $this->events[2]['read']); + $this->assertSame($request, $this->events[2]['request']); + } + + public function testDoesNotUseRequestResponseBodyWhenNotCustom() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 307 Foo\r\nLocation: /foo\r\nContent-Length: 2\r\n\r\nHI", + "HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 2\r\n\r\nFI", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", + )); + $client = new Client($this->getServer()->getUrl()); + $response = $client->get()->send(); + $this->assertEquals('test', $response->getBody(true)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php new file mode 100644 index 0000000..124a44d --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/EntityBodyTest.php @@ -0,0 +1,182 @@ +assertEquals('data', (string) $body); + $this->assertEquals(4, $body->getContentLength()); + $this->assertEquals('PHP', $body->getWrapper()); + $this->assertEquals('TEMP', $body->getStreamType()); + + $handle = fopen(__DIR__ . '/../../../../phpunit.xml.dist', 'r'); + if (!$handle) { + $this->fail('Could not open test file'); + } + $body = EntityBody::factory($handle); + $this->assertEquals(__DIR__ . '/../../../../phpunit.xml.dist', $body->getUri()); + $this->assertTrue($body->isLocal()); + $this->assertEquals(__DIR__ . '/../../../../phpunit.xml.dist', $body->getUri()); + $this->assertEquals(filesize(__DIR__ . '/../../../../phpunit.xml.dist'), $body->getContentLength()); + + // make sure that a body will return as the same object + $this->assertTrue($body === EntityBody::factory($body)); + } + + public function testFactoryCreatesTempStreamByDefault() + { + $body = EntityBody::factory(''); + $this->assertEquals('PHP', $body->getWrapper()); + $this->assertEquals('TEMP', $body->getStreamType()); + $body = EntityBody::factory(); + $this->assertEquals('PHP', $body->getWrapper()); + $this->assertEquals('TEMP', $body->getStreamType()); + } + + public function testFactoryCanCreateFromObject() + { + $body = EntityBody::factory(new QueryString(array('foo' => 'bar'))); + $this->assertEquals('foo=bar', (string) $body); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testFactoryEnsuresObjectsHaveToStringMethod() + { + EntityBody::factory(new \stdClass('a')); + } + + public function testHandlesCompression() + { + $body = EntityBody::factory('testing 123...testing 123'); + $this->assertFalse($body->getContentEncoding(), '-> getContentEncoding() must initially return FALSE'); + $size = $body->getContentLength(); + $body->compress(); + $this->assertEquals('gzip', $body->getContentEncoding(), '-> getContentEncoding() must return the correct encoding after compressing'); + $this->assertEquals(gzdeflate('testing 123...testing 123'), (string) $body); + $this->assertTrue($body->getContentLength() < $size); + $this->assertTrue($body->uncompress()); + $this->assertEquals('testing 123...testing 123', (string) $body); + $this->assertFalse($body->getContentEncoding(), '-> getContentEncoding() must reset to FALSE'); + + if (in_array('bzip2.*', stream_get_filters())) { + $this->assertTrue($body->compress('bzip2.compress')); + $this->assertEquals('compress', $body->getContentEncoding(), '-> compress() must set \'compress\' as the Content-Encoding'); + } + + $this->assertFalse($body->compress('non-existent'), '-> compress() must return false when a non-existent stream filter is used'); + + // Release the body + unset($body); + + // Use gzip compression on the initial content. This will include a + // gzip header which will need to be stripped when deflating the stream + $body = EntityBody::factory(gzencode('test')); + $this->assertSame($body, $body->setStreamFilterContentEncoding('zlib.deflate')); + $this->assertTrue($body->uncompress('zlib.inflate')); + $this->assertEquals('test', (string) $body); + unset($body); + + // Test using a very long string + $largeString = ''; + for ($i = 0; $i < 25000; $i++) { + $largeString .= chr(rand(33, 126)); + } + $body = EntityBody::factory($largeString); + $this->assertEquals($largeString, (string) $body); + $this->assertTrue($body->compress()); + $this->assertNotEquals($largeString, (string) $body); + $compressed = (string) $body; + $this->assertTrue($body->uncompress()); + $this->assertEquals($largeString, (string) $body); + $this->assertEquals($compressed, gzdeflate($largeString)); + + $body = EntityBody::factory(fopen(__DIR__ . '/../TestData/compress_test', 'w')); + $this->assertFalse($body->compress()); + unset($body); + + unlink(__DIR__ . '/../TestData/compress_test'); + } + + public function testDeterminesContentType() + { + // Test using a string/temp stream + $body = EntityBody::factory('testing 123...testing 123'); + $this->assertNull($body->getContentType()); + + // Use a local file + $body = EntityBody::factory(fopen(__FILE__, 'r')); + $this->assertContains('text/x-', $body->getContentType()); + } + + public function testCreatesMd5Checksum() + { + $body = EntityBody::factory('testing 123...testing 123'); + $this->assertEquals(md5('testing 123...testing 123'), $body->getContentMd5()); + + $server = $this->getServer()->enqueue( + "HTTP/1.1 200 OK" . "\r\n" . + "Content-Length: 3" . "\r\n\r\n" . + "abc" + ); + + $body = EntityBody::factory(fopen($this->getServer()->getUrl(), 'r')); + $this->assertFalse($body->getContentMd5()); + } + + public function testSeeksToOriginalPosAfterMd5() + { + $body = EntityBody::factory('testing 123'); + $body->seek(4); + $this->assertEquals(md5('testing 123'), $body->getContentMd5()); + $this->assertEquals(4, $body->ftell()); + $this->assertEquals('ing 123', $body->read(1000)); + } + + public function testGetTypeFormBodyFactoring() + { + $body = EntityBody::factory(array('key1' => 'val1', 'key2' => 'val2')); + $this->assertEquals('key1=val1&key2=val2', (string) $body); + } + + public function testAllowsCustomRewind() + { + $body = EntityBody::factory('foo'); + $rewound = false; + $body->setRewindFunction(function ($body) use (&$rewound) { + $rewound = true; + return $body->seek(0); + }); + $body->seek(2); + $this->assertTrue($body->rewind()); + $this->assertTrue($rewound); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testCustomRewindFunctionMustBeCallable() + { + $body = EntityBody::factory(); + $body->setRewindFunction('foo'); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php new file mode 100644 index 0000000..df3e4b7 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/CurlExceptionTest.php @@ -0,0 +1,27 @@ +assertNull($e->getError()); + $this->assertNull($e->getErrorNo()); + $this->assertSame($e, $e->setError('test', 12)); + $this->assertEquals('test', $e->getError()); + $this->assertEquals(12, $e->getErrorNo()); + + $handle = new CurlHandle(curl_init(), array()); + $e->setCurlHandle($handle); + $this->assertSame($handle, $e->getCurlHandle()); + $handle->close(); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php new file mode 100644 index 0000000..12cfd36 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/ExceptionTest.php @@ -0,0 +1,66 @@ +setRequest($request); + $this->assertEquals($request, $e->getRequest()); + } + + /** + * @covers Guzzle\Http\Exception\BadResponseException + */ + public function testBadResponseException() + { + $e = new BadResponseException('Message'); + $response = new Response(200); + $e->setResponse($response); + $this->assertEquals($response, $e->getResponse()); + } + + /** + * @covers Guzzle\Http\Exception\BadResponseException::factory + */ + public function testCreatesGenericErrorExceptionOnError() + { + $request = new Request('GET', 'http://www.example.com'); + $response = new Response(307); + $e = BadResponseException::factory($request, $response); + $this->assertInstanceOf('Guzzle\Http\Exception\BadResponseException', $e); + } + + /** + * @covers Guzzle\Http\Exception\BadResponseException::factory + */ + public function testCreatesClientErrorExceptionOnClientError() + { + $request = new Request('GET', 'http://www.example.com'); + $response = new Response(404); + $e = BadResponseException::factory($request, $response); + $this->assertInstanceOf('Guzzle\Http\Exception\ClientErrorResponseException', $e); + } + + /** + * @covers Guzzle\Http\Exception\BadResponseException::factory + */ + public function testCreatesServerErrorExceptionOnServerError() + { + $request = new Request('GET', 'http://www.example.com'); + $response = new Response(503); + $e = BadResponseException::factory($request, $response); + $this->assertInstanceOf('Guzzle\Http\Exception\ServerErrorResponseException', $e); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php new file mode 100644 index 0000000..fa4ec26 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Exception/MultiTransferExceptionTest.php @@ -0,0 +1,51 @@ +addSuccessfulRequest($r1); + $e->addFailedRequest($r2); + $this->assertEquals(array($r1), $e->getSuccessfulRequests()); + $this->assertEquals(array($r2), $e->getSuccessfulRequests()); + $this->assertEquals(array($r1, $r2), $e->getAllRequests()); + $this->assertTrue($e->containsRequest($r1)); + $this->assertTrue($e->containsRequest($r2)); + $this->assertFalse($e->containsRequest(new Request('POST', '/foo'))); + } + + public function testCanSetRequests() + { + $s = array($r1 = new Request('GET', 'http://www.foo.com')); + $f = array($r2 = new Request('GET', 'http://www.foo.com')); + $e = new MultiTransferException(); + $e->setSuccessfulRequests($s); + $e->setFailedRequests($f); + $this->assertEquals(array($r1), $e->getSuccessfulRequests()); + $this->assertEquals(array($r2), $e->getSuccessfulRequests()); + } + + public function testAssociatesExceptionsWithRequests() + { + $r1 = new Request('GET', 'http://www.foo.com'); + $re1 = new \Exception('foo'); + $re2 = new \Exception('bar'); + $e = new MultiTransferException(); + $e->add($re2); + $e->addFailedRequestWithException($r1, $re1); + $this->assertSame($re1, $e->getExceptionForFailedRequest($r1)); + $this->assertNull($e->getExceptionForFailedRequest(new Request('POST', '/foo'))); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php new file mode 100644 index 0000000..cd6355f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/IoEmittingEntityBodyTest.php @@ -0,0 +1,47 @@ +decorated = EntityBody::factory('hello'); + $this->body = new IoEmittingEntityBody($this->decorated); + } + + public function testEmitsReadEvents() + { + $e = null; + $this->body->getEventDispatcher()->addListener('body.read', function ($event) use (&$e) { + $e = $event; + }); + $this->assertEquals('hel', $this->body->read(3)); + $this->assertEquals('hel', $e['read']); + $this->assertEquals(3, $e['length']); + $this->assertSame($this->body, $e['body']); + } + + public function testEmitsWriteEvents() + { + $e = null; + $this->body->getEventDispatcher()->addListener('body.write', function ($event) use (&$e) { + $e = $event; + }); + $this->body->seek(0, SEEK_END); + $this->assertEquals(5, $this->body->write('there')); + $this->assertEquals('there', $e['write']); + $this->assertEquals(5, $e['result']); + $this->assertSame($this->body, $e['body']); + $this->assertEquals('hellothere', (string) $this->body); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php new file mode 100644 index 0000000..9447d8c --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/AbstractMessageTest.php @@ -0,0 +1,136 @@ +mock = $this->getMockForAbstractClass('Guzzle\Http\Message\AbstractMessage'); + } + + public function tearDown() + { + $this->mock = $this->request = null; + } + + public function testGetParams() + { + $request = new Request('GET', 'http://example.com'); + $this->assertInstanceOf('Guzzle\\Common\\Collection', $request->getParams()); + } + + public function testAddHeaders() + { + $this->mock->setHeader('A', 'B'); + + $this->assertEquals($this->mock, $this->mock->addHeaders(array( + 'X-Data' => '123' + ))); + + $this->assertTrue($this->mock->hasHeader('X-Data') !== false); + $this->assertTrue($this->mock->hasHeader('A') !== false); + } + + public function testAllowsHeaderToSetAsHeader() + { + $h = new Header('A', 'B'); + $this->mock->setHeader('A', $h); + $this->assertSame($h, $this->mock->getHeader('A')); + } + + public function testGetHeader() + { + $this->mock->setHeader('Test', '123'); + $this->assertEquals('123', $this->mock->getHeader('Test')); + } + + public function testGetHeaders() + { + $this->assertSame($this->mock, $this->mock->setHeaders(array('a' => 'b', 'c' => 'd'))); + $h = $this->mock->getHeaders(); + $this->assertArrayHasKey('a', $h->toArray()); + $this->assertArrayHasKey('c', $h->toArray()); + $this->assertInstanceOf('Guzzle\Http\Message\Header\HeaderInterface', $h->get('a')); + $this->assertInstanceOf('Guzzle\Http\Message\Header\HeaderInterface', $h->get('c')); + } + + public function testGetHeaderLinesUsesGlue() + { + $this->mock->setHeaders(array('a' => 'b', 'c' => 'd')); + $this->mock->addHeader('a', 'e'); + $this->mock->getHeader('a')->setGlue('!'); + $this->assertEquals(array( + 'a: b! e', + 'c: d' + ), $this->mock->getHeaderLines()); + } + + public function testHasHeader() + { + $this->assertFalse($this->mock->hasHeader('Foo')); + $this->mock->setHeader('Foo', 'Bar'); + $this->assertEquals(true, $this->mock->hasHeader('Foo')); + $this->mock->setHeader('foo', 'yoo'); + $this->assertEquals(true, $this->mock->hasHeader('Foo')); + $this->assertEquals(true, $this->mock->hasHeader('foo')); + $this->assertEquals(false, $this->mock->hasHeader('bar')); + } + + public function testRemoveHeader() + { + $this->mock->setHeader('Foo', 'Bar'); + $this->assertEquals(true, $this->mock->hasHeader('Foo')); + $this->mock->removeHeader('Foo'); + $this->assertFalse($this->mock->hasHeader('Foo')); + } + + public function testReturnsNullWhenHeaderIsNotFound() + { + $this->assertNull($this->mock->getHeader('foo')); + } + + public function testAddingHeadersPreservesOriginalHeaderCase() + { + $this->mock->addHeaders(array( + 'test' => '123', + 'Test' => 'abc' + )); + $this->mock->addHeader('test', '456'); + $this->mock->addHeader('test', '789'); + + $header = $this->mock->getHeader('test'); + $this->assertContains('123', $header->toArray()); + $this->assertContains('456', $header->toArray()); + $this->assertContains('789', $header->toArray()); + $this->assertContains('abc', $header->toArray()); + } + + public function testCanStoreEmptyHeaders() + { + $this->mock->setHeader('Content-Length', 0); + $this->assertTrue($this->mock->hasHeader('Content-Length')); + $this->assertEquals(0, (string) $this->mock->getHeader('Content-Length')); + } + + public function testCanSetCustomHeaderFactory() + { + $f = new Header\HeaderFactory(); + $this->mock->setHeaderFactory($f); + $this->assertSame($f, $this->readAttribute($this->mock, 'headerFactory')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php new file mode 100644 index 0000000..191b022 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/EntityEnclosingRequestTest.php @@ -0,0 +1,434 @@ +client = new Client(); + } + + public function tearDown() + { + $this->client = null; + } + + public function testConstructorConfiguresRequest() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com', array( + 'X-Test' => '123' + )); + $request->setBody('Test'); + $this->assertEquals('123', $request->getHeader('X-Test')); + $this->assertNull($request->getHeader('Expect')); + } + + public function testCanSetBodyWithoutOverridingContentType() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com', array('Content-Type' => 'foooooo')); + $request->setBody('{"a":"b"}'); + $this->assertEquals('foooooo', $request->getHeader('Content-Type')); + } + + public function testRequestIncludesBodyInMessage() + { + + $request = RequestFactory::getInstance()->create('PUT', 'http://www.guzzle-project.com/', null, 'data'); + $this->assertEquals("PUT / HTTP/1.1\r\n" + . "Host: www.guzzle-project.com\r\n" + . "Content-Length: 4\r\n\r\n" + . "data", (string) $request); + } + + public function testRequestIncludesPostBodyInMessageOnlyWhenNoPostFiles() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array( + 'foo' => 'bar' + )); + $this->assertEquals("POST / HTTP/1.1\r\n" + . "Host: www.guzzle-project.com\r\n" + . "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n\r\n" + . "foo=bar", (string) $request); + + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array( + 'foo' => '@' . __FILE__ + )); + $this->assertEquals("POST / HTTP/1.1\r\n" + . "Host: www.guzzle-project.com\r\n" + . "Content-Type: multipart/form-data\r\n" + . "Expect: 100-Continue\r\n\r\n", (string) $request); + } + + public function testAddsPostFieldsAndSetsContentLength() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/', null, array( + 'data' => '123' + )); + $this->assertEquals("POST / HTTP/1.1\r\n" + . "Host: www.guzzle-project.com\r\n" + . "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n\r\n" + . "data=123", (string) $request); + } + + public function testAddsPostFilesAndSetsContentType() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/') + ->addPostFiles(array( + 'file' => __FILE__ + ))->addPostFields(array( + 'a' => 'b' + )); + $message = (string) $request; + $this->assertEquals('multipart/form-data', $request->getHeader('Content-Type')); + $this->assertEquals('100-Continue', $request->getHeader('Expect')); + } + + public function testRequestBodyContainsPostFiles() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/'); + $request->addPostFields(array( + 'test' => '123' + )); + $this->assertContains("\r\n\r\ntest=123", (string) $request); + } + + public function testRequestBodyAddsContentLength() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.test.com/'); + $request->setBody(EntityBody::factory('test')); + $this->assertEquals(4, (string) $request->getHeader('Content-Length')); + $this->assertFalse($request->hasHeader('Transfer-Encoding')); + } + + public function testRequestBodyDoesNotUseContentLengthWhenChunked() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.test.com/', array( + 'Transfer-Encoding' => 'chunked' + ), 'test'); + $this->assertNull($request->getHeader('Content-Length')); + $this->assertTrue($request->hasHeader('Transfer-Encoding')); + } + + public function testRequestHasMutableBody() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.guzzle-project.com/', null, 'data'); + $body = $request->getBody(); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $body); + $this->assertSame($body, $request->getBody()); + + $newBody = EntityBody::factory('foobar'); + $request->setBody($newBody); + $this->assertEquals('foobar', (string) $request->getBody()); + $this->assertSame($newBody, $request->getBody()); + } + + public function testSetPostFields() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/'); + $this->assertInstanceOf('Guzzle\\Http\\QueryString', $request->getPostFields()); + + $fields = new QueryString(array( + 'a' => 'b' + )); + $request->addPostFields($fields); + $this->assertEquals($fields->getAll(), $request->getPostFields()->getAll()); + $this->assertEquals(array(), $request->getPostFiles()); + } + + public function testSetPostFiles() + { + $request = RequestFactory::getInstance()->create('POST', $this->getServer()->getUrl()) + ->setClient(new Client()) + ->addPostFiles(array(__FILE__)) + ->addPostFields(array( + 'test' => 'abc' + )); + + $request->getCurlOptions()->set('debug', true); + + $this->assertEquals(array( + 'test' => 'abc' + ), $request->getPostFields()->getAll()); + + $files = $request->getPostFiles(); + $post = $files['file'][0]; + $this->assertEquals('file', $post->getFieldName()); + $this->assertContains('text/x-', $post->getContentType()); + $this->assertEquals(__FILE__, $post->getFilename()); + + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request->send(); + + $this->assertNotNull($request->getHeader('Content-Length')); + $this->assertContains('multipart/form-data; boundary=', (string) $request->getHeader('Content-Type'), '-> cURL must add the boundary'); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testSetPostFilesThrowsExceptionWhenFileIsNotFound() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFiles(array( + 'file' => 'filenotfound.ini' + )); + } + + /** + * @expectedException Guzzle\Http\Exception\RequestException + */ + public function testThrowsExceptionWhenNonStringsAreAddedToPost() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFile('foo', new \stdClass()); + } + + public function testAllowsContentTypeInPostUploads() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFile('foo', __FILE__, 'text/plain'); + + $this->assertEquals(array( + new PostFile('foo', __FILE__, 'text/plain') + ), $request->getPostFile('foo')); + } + + public function testGuessesContentTypeOfPostUpload() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFile('foo', __FILE__); + $file = $request->getPostFile('foo'); + $this->assertContains('text/x-', $file[0]->getContentType()); + } + + public function testAllowsContentDispositionFieldsInPostUploadsWhenSettingInBulk() + { + $postFile = new PostFile('foo', __FILE__, 'text/x-php'); + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/') + ->addPostFiles(array('foo' => $postFile)); + + $this->assertEquals(array($postFile), $request->getPostFile('foo')); + } + + public function testPostRequestsUseApplicationXwwwForUrlEncodedForArrays() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/'); + $request->setPostField('a', 'b'); + $this->assertContains("\r\n\r\na=b", (string) $request); + $this->assertEquals('application/x-www-form-urlencoded; charset=utf-8', $request->getHeader('Content-Type')); + } + + public function testProcessMethodAddsContentType() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/'); + $request->setPostField('a', 'b'); + $this->assertEquals('application/x-www-form-urlencoded; charset=utf-8', $request->getHeader('Content-Type')); + } + + public function testPostRequestsUseMultipartFormDataWithFiles() + { + $request = RequestFactory::getInstance()->create('POST', 'http://www.guzzle-project.com/'); + $request->addPostFiles(array('file' => __FILE__)); + $this->assertEquals('multipart/form-data', $request->getHeader('Content-Type')); + } + + public function testCanSendMultipleRequestsUsingASingleRequestObject() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 201 Created\r\nContent-Length: 0\r\n\r\n", + )); + + // Send the first request + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl()) + ->setBody('test') + ->setClient(new Client()); + $request->send(); + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + + // Send the second request + $request->setBody('abcdefg', 'application/json', false); + $request->send(); + $this->assertEquals(201, $request->getResponse()->getStatusCode()); + + // Ensure that the same request was sent twice with different bodies + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(2, count($requests)); + $this->assertEquals(4, (string) $requests[0]->getHeader('Content-Length')); + $this->assertEquals(7, (string) $requests[1]->getHeader('Content-Length')); + } + + public function testRemovingPostFieldRebuildsPostFields() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com'); + $request->setPostField('test', 'value'); + $request->removePostField('test'); + $this->assertNull($request->getPostField('test')); + } + + public function testUsesChunkedTransferWhenBodyLengthCannotBeDetermined() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $request->setBody(fopen($this->getServer()->getUrl(), 'r')); + $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding')); + $this->assertFalse($request->hasHeader('Content-Length')); + } + + /** + * @expectedException \Guzzle\Http\Exception\RequestException + */ + public function testThrowsExceptionWhenContentLengthCannotBeDeterminedAndUsingHttp1() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request->setProtocolVersion('1.0'); + $request->setBody(fopen($this->getServer()->getUrl(), 'r')); + } + + public function testAllowsNestedPostData() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFields(array( + 'a' => array('b', 'c') + )); + $this->assertEquals(array( + 'a' => array('b', 'c') + ), $request->getPostFields()->getAll()); + } + + public function testAllowsEmptyFields() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFields(array( + 'a' => '' + )); + $this->assertEquals(array( + 'a' => '' + ), $request->getPostFields()->getAll()); + } + + /** + * @expectedException \Guzzle\Http\Exception\RequestException + */ + public function testFailsOnInvalidFiles() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFiles(array( + 'a' => new \stdClass() + )); + } + + public function testHandlesEmptyStrings() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFields(array( + 'a' => '', + 'b' => null, + 'c' => 'Foo' + )); + $this->assertEquals(array( + 'a' => '', + 'b' => null, + 'c' => 'Foo' + ), $request->getPostFields()->getAll()); + } + + public function testHoldsPostFiles() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFile('foo', __FILE__); + $request->addPostFile(new PostFile('foo', __FILE__)); + + $this->assertArrayHasKey('foo', $request->getPostFiles()); + $foo = $request->getPostFile('foo'); + $this->assertEquals(2, count($foo)); + $this->assertEquals(__FILE__, $foo[0]->getFilename()); + $this->assertEquals(__FILE__, $foo[1]->getFilename()); + + $request->removePostFile('foo'); + $this->assertEquals(array(), $request->getPostFiles()); + } + + public function testAllowsAtPrefixWhenAddingPostFiles() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->addPostFiles(array( + 'foo' => '@' . __FILE__ + )); + $foo = $request->getPostFile('foo'); + $this->assertEquals(__FILE__, $foo[0]->getFilename()); + } + + public function testSetStateToTransferWithEmptyBodySetsContentLengthToZero() + { + $request = new EntityEnclosingRequest('POST', 'http://test.com/'); + $request->setState($request::STATE_TRANSFER); + $this->assertEquals('0', (string) $request->getHeader('Content-Length')); + } + + public function testSettingExpectHeaderCutoffChangesRequest() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $request->setHeader('Expect', '100-Continue'); + $request->setExpectHeaderCutoff(false); + $this->assertNull($request->getHeader('Expect')); + // There is not body, so remove the expect header + $request->setHeader('Expect', '100-Continue'); + $request->setExpectHeaderCutoff(10); + $this->assertNull($request->getHeader('Expect')); + // The size is less than the cutoff + $request->setBody('foo'); + $this->assertNull($request->getHeader('Expect')); + // The size is greater than the cutoff + $request->setBody('foobazbarbamboo'); + $this->assertNotNull($request->getHeader('Expect')); + } + + public function testStrictRedirectsCanBeSpecifiedOnEntityEnclosingRequests() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $request->configureRedirects(true); + $this->assertTrue($request->getParams()->get(RedirectPlugin::STRICT_REDIRECTS)); + } + + public function testCanDisableRedirects() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/'); + $request->configureRedirects(false, false); + $this->assertTrue($request->getParams()->get(RedirectPlugin::DISABLE)); + } + + public function testSetsContentTypeWhenSettingBodyByGuessingFromEntityBody() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/foo'); + $request->setBody(EntityBody::factory(fopen(__FILE__, 'r'))); + $this->assertEquals('text/x-php', (string) $request->getHeader('Content-Type')); + } + + public function testDoesNotCloneBody() + { + $request = new EntityEnclosingRequest('PUT', 'http://test.com/foo'); + $request->setBody('test'); + $newRequest = clone $request; + $newRequest->setBody('foo'); + $this->assertInternalType('string', (string) $request->getBody()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php new file mode 100644 index 0000000..62ca555 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/HeaderFactoryTest.php @@ -0,0 +1,29 @@ +createHeader('Foo', 'Bar'); + $this->assertInstanceOf('Guzzle\Http\Message\Header', $h); + $this->assertEquals('Foo', $h->getName()); + $this->assertEquals('Bar', (string) $h); + } + + public function testCreatesSpecificHeaders() + { + $f = new HeaderFactory(); + $h = $f->createHeader('Link', '; rel="test"'); + $this->assertInstanceOf('Guzzle\Http\Message\Header\Link', $h); + $this->assertEquals('Link', $h->getName()); + $this->assertEquals('; rel="test"', (string) $h); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php new file mode 100644 index 0000000..c834d10 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/Header/LinkTest.php @@ -0,0 +1,63 @@ +; rel=front; type="image/jpeg", ; rel=back; type="image/jpeg", ; rel=side; type="image/jpeg"'); + $links = $link->getLinks(); + $this->assertEquals(array( + array( + 'rel' => 'front', + 'type' => 'image/jpeg', + 'url' => 'http:/.../front.jpeg', + ), + array( + 'rel' => 'back', + 'type' => 'image/jpeg', + 'url' => 'http://.../back.jpeg', + ), + array( + 'rel' => 'side', + 'type' => 'image/jpeg', + 'url' => 'http://.../side.jpeg?test=1' + ) + ), $links); + + $this->assertEquals(array( + 'rel' => 'back', + 'type' => 'image/jpeg', + 'url' => 'http://.../back.jpeg', + ), $link->getLink('back')); + + $this->assertTrue($link->hasLink('front')); + $this->assertFalse($link->hasLink('foo')); + } + + public function testCanAddLink() + { + $link = new Link('Link', '; rel=a; type="image/jpeg"'); + $link->addLink('http://test.com', 'test', array('foo' => 'bar')); + $this->assertEquals( + '; rel=a; type="image/jpeg", ; rel="test"; foo="bar"', + (string) $link + ); + } + + public function testCanParseLinksWithCommas() + { + $link = new Link('Link', '; rel="previous"; title="start, index"'); + $this->assertEquals(array( + array( + 'rel' => 'previous', + 'title' => 'start, index', + 'url' => 'http://example.com/TheBook/chapter1', + ) + ), $link->getLinks()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php new file mode 100644 index 0000000..a3f511b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparison.php @@ -0,0 +1,135 @@ +toArray(); + } + + foreach ($filteredHeaders as $k => $v) { + if ($k[0] == '_') { + // This header should be ignored + $ignore[] = str_replace('_', '', $k); + } elseif ($k[0] == '!') { + // This header must not be present + $absent[] = str_replace('!', '', $k); + } else { + $expected[$k] = $v; + } + } + + return $this->compareArray($expected, $actualHeaders, $ignore, $absent); + } + + /** + * Check if an array of HTTP headers matches another array of HTTP headers while taking * into account as a wildcard + * + * @param array $expected Expected HTTP headers (allows wildcard values) + * @param array|Collection $actual Actual HTTP header array + * @param array $ignore Headers to ignore from the comparison + * @param array $absent Array of headers that must not be present + * + * @return array|bool Returns an array of the differences or FALSE if none + */ + public function compareArray(array $expected, $actual, array $ignore = array(), array $absent = array()) + { + $differences = array(); + + // Add information about headers that were present but weren't supposed to be + foreach ($absent as $header) { + if ($this->hasKey($header, $actual)) { + $differences["++ {$header}"] = $actual[$header]; + unset($actual[$header]); + } + } + + // Check if expected headers are missing + foreach ($expected as $header => $value) { + if (!$this->hasKey($header, $actual)) { + $differences["- {$header}"] = $value; + } + } + + // Flip the ignore array so it works with the case insensitive helper + $ignore = array_flip($ignore); + // Allow case-insensitive comparisons in wildcards + $expected = array_change_key_case($expected); + + // Compare the expected and actual HTTP headers in no particular order + foreach ($actual as $key => $value) { + + // If this is to be ignored, the skip it + if ($this->hasKey($key, $ignore)) { + continue; + } + + // If the header was not expected + if (!$this->hasKey($key, $expected)) { + $differences["+ {$key}"] = $value; + continue; + } + + // Check values and take wildcards into account + $lkey = strtolower($key); + $pos = is_string($expected[$lkey]) ? strpos($expected[$lkey], '*') : false; + + foreach ((array) $actual[$key] as $v) { + if (($pos === false && $v != $expected[$lkey]) || $pos > 0 && substr($v, 0, $pos) != substr($expected[$lkey], 0, $pos)) { + $differences[$key] = "{$value} != {$expected[$lkey]}"; + } + } + } + + return empty($differences) ? false : $differences; + } + + /** + * Case insensitive check if an array have a key + * + * @param string $key Key to check + * @param array $array Array to check + * + * @return bool + */ + protected function hasKey($key, $array) + { + if ($array instanceof Collection) { + $keys = $array->getKeys(); + } else { + $keys = array_keys($array); + } + + foreach ($keys as $k) { + if (!strcasecmp($k, $key)) { + return true; + } + } + + return false; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php new file mode 100644 index 0000000..86c4fe8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderComparisonTest.php @@ -0,0 +1,115 @@ + 'Foo' + ), array( + 'Content-Length' => 'Foo' + ), false), + + // Missing header + array(array( + 'X-Foo' => 'Bar' + ), array(), array( + '- X-Foo' => 'Bar' + )), + + // Extra headers is present + array(array( + 'X-Foo' => 'Bar' + ), array( + 'X-Foo' => 'Bar', + 'X-Baz' => 'Jar' + ), array( + '+ X-Baz' => 'Jar' + )), + + // Header is present but must be absent + array(array( + '!X-Foo' => '*' + ), array( + 'X-Foo' => 'Bar' + ), array( + '++ X-Foo' => 'Bar' + )), + + // Different values + array(array( + 'X-Foo' => 'Bar' + ), array( + 'X-Foo' => 'Baz' + ), array( + 'X-Foo' => 'Baz != Bar' + )), + + // Wildcard search passes + array(array( + 'X-Foo' => '*' + ), array( + 'X-Foo' => 'Bar' + ), false), + + // Wildcard search fails + array(array( + 'X-Foo' => '*' + ), array(), array( + '- X-Foo' => '*' + )), + + // Ignore extra header if present + array(array( + 'X-Foo' => '*', + '_X-Bar' => '*', + ), array( + 'X-Foo' => 'Baz', + 'X-Bar' => 'Jar' + ), false), + + // Ignore extra header if present and is not + array(array( + 'X-Foo' => '*', + '_X-Bar' => '*', + ), array( + 'X-Foo' => 'Baz' + ), false), + + // Case insensitive + array(array( + 'X-Foo' => '*', + '_X-Bar' => '*', + ), array( + 'x-foo' => 'Baz', + 'x-BAR' => 'baz' + ), false), + + // Case insensitive with collection + array(array( + 'X-Foo' => '*', + '_X-Bar' => '*', + ), new Collection(array( + 'x-foo' => 'Baz', + 'x-BAR' => 'baz' + )), false), + ); + } + + /** + * @dataProvider filterProvider + */ + public function testComparesHeaders($filters, $headers, $result) + { + $compare = new HeaderComparison(); + $this->assertEquals($result, $compare->compare($filters, $headers)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php new file mode 100644 index 0000000..c750234 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/HeaderTest.php @@ -0,0 +1,162 @@ + array('foo', 'Foo'), + 'Zoo' => 'bar', + ); + + public function testStoresHeaderName() + { + $i = new Header('Zoo', $this->test); + $this->assertEquals('Zoo', $i->getName()); + } + + public function testConvertsToString() + { + $i = new Header('Zoo', $this->test); + $this->assertEquals('foo, Foo, bar', (string) $i); + $i->setGlue(';'); + $this->assertEquals('foo; Foo; bar', (string) $i); + } + + public function testNormalizesGluedHeaders() + { + $h = new Header('Zoo', array('foo, Faz', 'bar')); + $result = $h->normalize(true)->toArray(); + natsort($result); + $this->assertEquals(array('bar', 'foo', 'Faz'), $result); + } + + public function testCanSearchForValues() + { + $h = new Header('Zoo', $this->test); + $this->assertTrue($h->hasValue('foo')); + $this->assertTrue($h->hasValue('Foo')); + $this->assertTrue($h->hasValue('bar')); + $this->assertFalse($h->hasValue('moo')); + $this->assertFalse($h->hasValue('FoO')); + } + + public function testIsCountable() + { + $h = new Header('Zoo', $this->test); + $this->assertEquals(3, count($h)); + } + + public function testCanBeIterated() + { + $h = new Header('Zoo', $this->test); + $results = array(); + foreach ($h as $key => $value) { + $results[$key] = $value; + } + $this->assertEquals(array( + 'foo', 'Foo', 'bar' + ), $results); + } + + public function testAllowsFalseyValues() + { + // Allows 0 + $h = new Header('Foo', 0, ';'); + $this->assertEquals('0', (string) $h); + $this->assertEquals(1, count($h)); + $this->assertEquals(';', $h->getGlue()); + + // Does not add a null header by default + $h = new Header('Foo'); + $this->assertEquals('', (string) $h); + $this->assertEquals(0, count($h)); + + // Allows null array for a single null header + $h = new Header('Foo', array(null)); + $this->assertEquals('', (string) $h); + + // Allows empty string + $h = new Header('Foo', ''); + $this->assertEquals('', (string) $h); + $this->assertEquals(1, count($h)); + $this->assertEquals(1, count($h->normalize()->toArray())); + } + + public function testCanRemoveValues() + { + $h = new Header('Foo', array('Foo', 'baz', 'bar')); + $h->removeValue('bar'); + $this->assertTrue($h->hasValue('Foo')); + $this->assertFalse($h->hasValue('bar')); + $this->assertTrue($h->hasValue('baz')); + } + + public function testAllowsArrayInConstructor() + { + $h = new Header('Foo', array('Testing', '123', 'Foo=baz')); + $this->assertEquals(array('Testing', '123', 'Foo=baz'), $h->toArray()); + } + + public function parseParamsProvider() + { + $res1 = array( + array( + '' => '', + 'rel' => 'front', + 'type' => 'image/jpeg', + ), + array( + '' => '', + 'rel' => 'back', + 'type' => 'image/jpeg', + ), + ); + + return array( + array( + '; rel="front"; type="image/jpeg", ; rel=back; type="image/jpeg"', + $res1 + ), + array( + '; rel="front"; type="image/jpeg",; rel=back; type="image/jpeg"', + $res1 + ), + array( + 'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"', + array( + array('foo' => 'baz', 'bar' => '123'), + array('boo' => ''), + array('test' => '123'), + array('foobar' => 'foo;bar') + ) + ), + array( + '; rel="side"; type="image/jpeg",; rel=side; type="image/jpeg"', + array( + array('' => '', 'rel' => 'side', 'type' => 'image/jpeg'), + array('' => '', 'rel' => 'side', 'type' => 'image/jpeg') + ) + ), + array( + '', + array() + ) + ); + } + + /** + * @dataProvider parseParamsProvider + */ + public function testParseParams($header, $result) + { + $response = new Response(200, array('Link' => $header)); + $this->assertEquals($result, $response->getHeader('Link')->parseParams()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php new file mode 100644 index 0000000..be048cb --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/PostFileTest.php @@ -0,0 +1,88 @@ +assertEquals('foo', $file->getFieldName()); + $this->assertEquals(__FILE__, $file->getFilename()); + $this->assertEquals('boo', $file->getPostName()); + $this->assertEquals('x-foo', $file->getContentType()); + } + + public function testRemovesLeadingAtSymbolFromPath() + { + $file = new PostFile('foo', '@' . __FILE__); + $this->assertEquals(__FILE__, $file->getFilename()); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testEnsuresFileIsReadable() + { + $file = new PostFile('foo', '/foo/baz/bar'); + } + + public function testCanChangeContentType() + { + $file = new PostFile('foo', '@' . __FILE__); + $file->setContentType('Boo'); + $this->assertEquals('Boo', $file->getContentType()); + } + + public function testCanChangeFieldName() + { + $file = new PostFile('foo', '@' . __FILE__); + $file->setFieldName('Boo'); + $this->assertEquals('Boo', $file->getFieldName()); + } + + public function testReturnsCurlValueString() + { + $file = new PostFile('foo', __FILE__); + if (version_compare(phpversion(), '5.5.0', '<')) { + $this->assertContains('@' . __FILE__ . ';filename=PostFileTest.php;type=text/x-', $file->getCurlValue()); + } else { + $c = $file->getCurlValue(); + $this->assertEquals(__FILE__, $c->getFilename()); + $this->assertEquals('PostFileTest.php', $c->getPostFilename()); + $this->assertContains('text/x-', $c->getMimeType()); + } + } + + public function testReturnsCurlValueStringAndPostname() + { + $file = new PostFile('foo', __FILE__, null, 'NewPostFileTest.php'); + if (version_compare(phpversion(), '5.5.0', '<')) { + $this->assertContains('@' . __FILE__ . ';filename=NewPostFileTest.php;type=text/x-', $file->getCurlValue()); + } else { + $c = $file->getCurlValue(); + $this->assertEquals(__FILE__, $c->getFilename()); + $this->assertEquals('NewPostFileTest.php', $c->getPostFilename()); + $this->assertContains('text/x-', $c->getMimeType()); + } + } + + public function testContentDispositionFilePathIsStripped() + { + $this->getServer()->flush(); + $client = new Client($this->getServer()->getUrl()); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = $client->post()->addPostFile('file', __FILE__); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(false); + $this->assertContains('POST / HTTP/1.1', $requests[0]); + $this->assertContains('Content-Disposition: form-data; name="file"; filename="PostFileTest.php"', $requests[0]); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php new file mode 100644 index 0000000..80b8d54 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestFactoryTest.php @@ -0,0 +1,616 @@ +assertSame($factory, RequestFactory::getInstance()); + } + + public function testCreatesNewGetRequests() + { + $request = RequestFactory::getInstance()->create('GET', 'http://www.google.com/'); + $this->assertInstanceOf('Guzzle\\Http\\Message\\MessageInterface', $request); + $this->assertInstanceOf('Guzzle\\Http\\Message\\RequestInterface', $request); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Request', $request); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com/', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('/', $request->getPath()); + $this->assertEquals('/', $request->getResource()); + + // Create a GET request with a custom receiving body + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $b = EntityBody::factory(); + $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl(), null, $b); + $request->setClient(new Client()); + $response = $request->send(); + $this->assertSame($b, $response->getBody()); + } + + public function testCreatesPutRequests() + { + // Test using a string + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, 'Data'); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com/path?q=1&v=2', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('/path', $request->getPath()); + $this->assertEquals('/path?q=1&v=2', $request->getResource()); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody()); + $this->assertEquals('Data', (string) $request->getBody()); + unset($request); + + // Test using an EntityBody + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, EntityBody::factory('Data')); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('Data', (string) $request->getBody()); + + // Test using a resource + $resource = fopen('php://temp', 'w+'); + fwrite($resource, 'Data'); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, $resource); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('Data', (string) $request->getBody()); + + // Test using an object that can be cast as a string + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, Url::factory('http://www.example.com/')); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('http://www.example.com/', (string) $request->getBody()); + } + + public function testCreatesHeadAndDeleteRequests() + { + $request = RequestFactory::getInstance()->create('DELETE', 'http://www.test.com/'); + $this->assertEquals('DELETE', $request->getMethod()); + $request = RequestFactory::getInstance()->create('HEAD', 'http://www.test.com/'); + $this->assertEquals('HEAD', $request->getMethod()); + } + + public function testCreatesOptionsRequests() + { + $request = RequestFactory::getInstance()->create('OPTIONS', 'http://www.example.com/'); + $this->assertEquals('OPTIONS', $request->getMethod()); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + } + + public function testCreatesNewPutRequestWithBody() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', null, 'Data'); + $this->assertEquals('Data', (string) $request->getBody()); + } + + public function testCreatesNewPostRequestWithFields() + { + // Use an array + $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, array( + 'a' => 'b' + )); + $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll()); + unset($request); + + // Use a collection + $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, new Collection(array( + 'a' => 'b' + ))); + $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll()); + + // Use a QueryString + $request = RequestFactory::getInstance()->create('POST', 'http://www.google.com/path?q=1&v=2', null, new QueryString(array( + 'a' => 'b' + ))); + $this->assertEquals(array('a' => 'b'), $request->getPostFields()->getAll()); + + $request = RequestFactory::getInstance()->create('POST', 'http://www.test.com/', null, array( + 'a' => 'b', + 'file' => '@' . __FILE__ + )); + + $this->assertEquals(array( + 'a' => 'b' + ), $request->getPostFields()->getAll()); + + $files = $request->getPostFiles(); + $this->assertInstanceOf('Guzzle\Http\Message\PostFile', $files['file'][0]); + } + + public function testCreatesFromParts() + { + $parts = parse_url('http://michael:123@www.google.com:8080/path?q=1&v=2'); + + $request = RequestFactory::getInstance()->fromParts('PUT', $parts, null, 'Data'); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com:8080/path?q=1&v=2', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('www.google.com:8080', $request->getHeader('Host')); + $this->assertEquals('/path', $request->getPath()); + $this->assertEquals('/path?q=1&v=2', $request->getResource()); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody()); + $this->assertEquals('Data', (string) $request->getBody()); + $this->assertEquals('michael', $request->getUsername()); + $this->assertEquals('123', $request->getPassword()); + $this->assertEquals('8080', $request->getPort()); + $this->assertEquals(array( + 'scheme' => 'http', + 'host' => 'www.google.com', + 'port' => 8080, + 'path' => '/path', + 'query' => 'q=1&v=2', + ), parse_url($request->getUrl())); + } + + public function testCreatesFromMessage() + { + $auth = base64_encode('michael:123'); + $message = "PUT /path?q=1&v=2 HTTP/1.1\r\nHost: www.google.com:8080\r\nContent-Length: 4\r\nAuthorization: Basic {$auth}\r\n\r\nData"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com:8080/path?q=1&v=2', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('www.google.com:8080', $request->getHeader('Host')); + $this->assertEquals('/path', $request->getPath()); + $this->assertEquals('/path?q=1&v=2', $request->getResource()); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody()); + $this->assertEquals('Data', (string) $request->getBody()); + $this->assertEquals("Basic {$auth}", (string) $request->getHeader('Authorization')); + $this->assertEquals('8080', $request->getPort()); + + // Test passing a blank message returns false + $this->assertFalse($request = RequestFactory::getInstance()->fromMessage('')); + + // Test passing a url with no port + $message = "PUT /path?q=1&v=2 HTTP/1.1\r\nHost: www.google.com\r\nContent-Length: 4\r\nAuthorization: Basic {$auth}\r\n\r\nData"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertInstanceOf('Guzzle\\Http\\Message\\EntityEnclosingRequest', $request); + $this->assertEquals('PUT', $request->getMethod()); + $this->assertEquals('http', $request->getScheme()); + $this->assertEquals('http://www.google.com/path?q=1&v=2', $request->getUrl()); + $this->assertEquals('www.google.com', $request->getHost()); + $this->assertEquals('/path', $request->getPath()); + $this->assertEquals('/path?q=1&v=2', $request->getResource()); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $request->getBody()); + $this->assertEquals('Data', (string) $request->getBody()); + $this->assertEquals("Basic {$auth}", (string) $request->getHeader('Authorization')); + $this->assertEquals(80, $request->getPort()); + } + + public function testCreatesNewTraceRequest() + { + $request = RequestFactory::getInstance()->create('TRACE', 'http://www.google.com/'); + $this->assertFalse($request instanceof \Guzzle\Http\Message\EntityEnclosingRequest); + $this->assertEquals('TRACE', $request->getMethod()); + } + + public function testCreatesProperTransferEncodingRequests() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/', array( + 'Transfer-Encoding' => 'chunked' + ), 'hello'); + $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding')); + $this->assertFalse($request->hasHeader('Content-Length')); + } + + public function testProperlyDealsWithDuplicateHeaders() + { + $parser = new MessageParser(); + + $message = "POST / http/1.1\r\n" + . "DATE:Mon, 09 Sep 2011 23:36:00 GMT\r\n" + . "host:host.foo.com\r\n" + . "ZOO:abc\r\n" + . "ZOO:123\r\n" + . "ZOO:HI\r\n" + . "zoo:456\r\n\r\n"; + + $parts = $parser->parseRequest($message); + $this->assertEquals(array ( + 'DATE' => 'Mon, 09 Sep 2011 23:36:00 GMT', + 'host' => 'host.foo.com', + 'ZOO' => array('abc', '123', 'HI'), + 'zoo' => '456', + ), $parts['headers']); + + $request = RequestFactory::getInstance()->fromMessage($message); + + $this->assertEquals(array( + 'abc', '123', 'HI', '456' + ), $request->getHeader('zoo')->toArray()); + } + + public function testCreatesHttpMessagesWithBodiesAndNormalizesLineEndings() + { + $message = "POST / http/1.1\r\n" + . "Content-Type:application/x-www-form-urlencoded; charset=utf8\r\n" + . "Date:Mon, 09 Sep 2011 23:36:00 GMT\r\n" + . "Host:host.foo.com\r\n\r\n" + . "foo=bar"; + + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertEquals('application/x-www-form-urlencoded; charset=utf8', (string) $request->getHeader('Content-Type')); + $this->assertEquals('foo=bar', (string) $request->getBody()); + + $message = "POST / http/1.1\n" + . "Content-Type:application/x-www-form-urlencoded; charset=utf8\n" + . "Date:Mon, 09 Sep 2011 23:36:00 GMT\n" + . "Host:host.foo.com\n\n" + . "foo=bar"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertEquals('foo=bar', (string) $request->getBody()); + + $message = "PUT / HTTP/1.1\r\nContent-Length: 0\r\n\r\n"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertTrue($request->hasHeader('Content-Length')); + $this->assertEquals(0, (string) $request->getHeader('Content-Length')); + } + + public function testBugPathIncorrectlyHandled() + { + $message = "POST /foo\r\n\r\nBODY"; + $request = RequestFactory::getInstance()->fromMessage($message); + $this->assertSame('POST', $request->getMethod()); + $this->assertSame('/foo', $request->getPath()); + $this->assertSame('BODY', (string) $request->getBody()); + } + + public function testHandlesChunkedTransferEncoding() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://www.foo.com/', array( + 'Transfer-Encoding' => 'chunked' + ), 'Test'); + $this->assertFalse($request->hasHeader('Content-Length')); + $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding')); + + $request = RequestFactory::getInstance()->create('POST', 'http://www.foo.com/', array( + 'transfer-encoding' => 'chunked' + ), array( + 'foo' => 'bar' + )); + + $this->assertFalse($request->hasHeader('Content-Length')); + $this->assertEquals('chunked', $request->getHeader('Transfer-Encoding')); + } + + public function testClonesRequestsWithMethodWithoutClient() + { + $f = RequestFactory::getInstance(); + $request = $f->create('GET', 'http://www.test.com', array('X-Foo' => 'Bar')); + $request->getParams()->replace(array('test' => '123')); + $request->getCurlOptions()->set('foo', 'bar'); + $cloned = $f->cloneRequestWithMethod($request, 'PUT'); + $this->assertEquals('PUT', $cloned->getMethod()); + $this->assertEquals('Bar', (string) $cloned->getHeader('X-Foo')); + $this->assertEquals('http://www.test.com', $cloned->getUrl()); + // Ensure params are cloned and cleaned up + $this->assertEquals(1, count($cloned->getParams()->getAll())); + $this->assertEquals('123', $cloned->getParams()->get('test')); + // Ensure curl options are cloned + $this->assertEquals('bar', $cloned->getCurlOptions()->get('foo')); + // Ensure event dispatcher is cloned + $this->assertNotSame($request->getEventDispatcher(), $cloned->getEventDispatcher()); + } + + public function testClonesRequestsWithMethodWithClient() + { + $f = RequestFactory::getInstance(); + $client = new Client(); + $request = $client->put('http://www.test.com', array('Content-Length' => 4), 'test'); + $cloned = $f->cloneRequestWithMethod($request, 'GET'); + $this->assertEquals('GET', $cloned->getMethod()); + $this->assertNull($cloned->getHeader('Content-Length')); + $this->assertEquals('http://www.test.com', $cloned->getUrl()); + $this->assertSame($request->getClient(), $cloned->getClient()); + } + + public function testClonesRequestsWithMethodWithClientWithEntityEnclosingChange() + { + $f = RequestFactory::getInstance(); + $client = new Client(); + $request = $client->put('http://www.test.com', array('Content-Length' => 4), 'test'); + $cloned = $f->cloneRequestWithMethod($request, 'POST'); + $this->assertEquals('POST', $cloned->getMethod()); + $this->assertEquals('test', (string) $cloned->getBody()); + } + + public function testCanDisableRedirects() + { + $this->getServer()->enqueue(array( + "HTTP/1.1 307\r\nLocation: " . $this->getServer()->getUrl() . "\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $response = $client->get('/', array(), array('allow_redirects' => false))->send(); + $this->assertEquals(307, $response->getStatusCode()); + } + + public function testCanAddCookies() + { + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/', array(), array('cookies' => array('Foo' => 'Bar'))); + $this->assertEquals('Bar', $request->getCookie('Foo')); + } + + public function testCanAddQueryString() + { + $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array( + 'query' => array('Foo' => 'Bar') + )); + $this->assertEquals('Bar', $request->getQuery()->get('Foo')); + } + + public function testCanSetDefaultQueryString() + { + $request = new Request('GET', 'http://www.foo.com?test=abc'); + RequestFactory::getInstance()->applyOptions($request, array( + 'query' => array('test' => '123', 'other' => 't123') + ), RequestFactory::OPTIONS_AS_DEFAULTS); + $this->assertEquals('abc', $request->getQuery()->get('test')); + $this->assertEquals('t123', $request->getQuery()->get('other')); + } + + public function testCanAddBasicAuth() + { + $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array( + 'auth' => array('michael', 'test') + )); + $this->assertEquals('michael', $request->getUsername()); + $this->assertEquals('test', $request->getPassword()); + } + + public function testCanAddDigestAuth() + { + $request = RequestFactory::getInstance()->create('GET', 'http://foo.com', array(), null, array( + 'auth' => array('michael', 'test', 'digest') + )); + $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH)); + $this->assertEquals('michael', $request->getUsername()); + $this->assertEquals('test', $request->getPassword()); + } + + public function testCanAddEvents() + { + $foo = null; + $client = new Client(); + $client->addSubscriber(new MockPlugin(array(new Response(200)))); + $request = $client->get($this->getServer()->getUrl(), array(), array( + 'events' => array( + 'request.before_send' => function () use (&$foo) { $foo = true; } + ) + )); + $request->send(); + $this->assertTrue($foo); + } + + public function testCanAddEventsWithPriority() + { + $foo = null; + $client = new Client(); + $client->addSubscriber(new MockPlugin(array(new Response(200)))); + $request = $client->get($this->getServer()->getUrl(), array(), array( + 'events' => array( + 'request.before_send' => array(function () use (&$foo) { $foo = true; }, 100) + ) + )); + $request->send(); + $this->assertTrue($foo); + } + + public function testCanAddPlugins() + { + $mock = new MockPlugin(array( + new Response(200), + new Response(200) + )); + $client = new Client(); + $client->addSubscriber($mock); + $request = $client->get('/', array(), array( + 'plugins' => array($mock) + )); + $request->send(); + } + + public function testCanDisableExceptions() + { + $client = new Client(); + $request = $client->get('/', array(), array( + 'plugins' => array(new MockPlugin(array(new Response(500)))), + 'exceptions' => false + )); + $this->assertEquals(500, $request->send()->getStatusCode()); + } + + public function testCanDisableExceptionsWithErrorListener() + { + $client = new Client(); + $client->getEventDispatcher()->addListener('request.error', function () {}); + $request = $client->get('/', array(), array( + 'plugins' => array(new MockPlugin(array(new Response(500)))), + 'exceptions' => false + )); + $this->assertEquals(500, $request->send()->getStatusCode()); + } + + public function testCanChangeSaveToLocation() + { + $r = EntityBody::factory(); + $client = new Client(); + $request = $client->get('/', array(), array( + 'plugins' => array(new MockPlugin(array(new Response(200, array(), 'testing')))), + 'save_to' => $r + )); + $request->send(); + $this->assertEquals('testing', (string) $r); + } + + public function testCanSetProxy() + { + $client = new Client(); + $request = $client->get('/', array(), array('proxy' => '192.168.16.121')); + $this->assertEquals('192.168.16.121', $request->getCurlOptions()->get(CURLOPT_PROXY)); + } + + public function testCanSetHeadersOption() + { + $client = new Client(); + $request = $client->get('/', array(), array('headers' => array('Foo' => 'Bar'))); + $this->assertEquals('Bar', (string) $request->getHeader('Foo')); + } + + public function testCanSetDefaultHeadersOptions() + { + $request = new Request('GET', 'http://www.foo.com', array('Foo' => 'Bar')); + RequestFactory::getInstance()->applyOptions($request, array( + 'headers' => array('Foo' => 'Baz', 'Bam' => 't123') + ), RequestFactory::OPTIONS_AS_DEFAULTS); + $this->assertEquals('Bar', (string) $request->getHeader('Foo')); + $this->assertEquals('t123', (string) $request->getHeader('Bam')); + } + + public function testCanSetBodyOption() + { + $client = new Client(); + $request = $client->put('/', array(), null, array('body' => 'test')); + $this->assertEquals('test', (string) $request->getBody()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesBodyOption() + { + $client = new Client(); + $client->get('/', array(), array('body' => 'test')); + } + + public function testCanSetTimeoutOption() + { + $client = new Client(); + $request = $client->get('/', array(), array('timeout' => 1.5)); + $this->assertEquals(1500, $request->getCurlOptions()->get(CURLOPT_TIMEOUT_MS)); + } + + public function testCanSetConnectTimeoutOption() + { + $client = new Client(); + $request = $client->get('/', array(), array('connect_timeout' => 1.5)); + $this->assertEquals(1500, $request->getCurlOptions()->get(CURLOPT_CONNECTTIMEOUT_MS)); + } + + public function testCanSetDebug() + { + $client = new Client(); + $request = $client->get('/', array(), array('debug' => true)); + $this->assertTrue($request->getCurlOptions()->get(CURLOPT_VERBOSE)); + } + + public function testCanSetVerifyToOff() + { + $client = new Client(); + $request = $client->get('/', array(), array('verify' => false)); + $this->assertNull($request->getCurlOptions()->get(CURLOPT_CAINFO)); + $this->assertSame(0, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST)); + $this->assertFalse($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)); + } + + public function testCanSetVerifyToOn() + { + $client = new Client(); + $request = $client->get('/', array(), array('verify' => true)); + $this->assertNotNull($request->getCurlOptions()->get(CURLOPT_CAINFO)); + $this->assertSame(2, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST)); + $this->assertTrue($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)); + } + + public function testCanSetVerifyToPath() + { + $client = new Client(); + $request = $client->get('/', array(), array('verify' => '/foo.pem')); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_CAINFO)); + $this->assertSame(2, $request->getCurlOptions()->get(CURLOPT_SSL_VERIFYHOST)); + $this->assertTrue($request->getCurlOptions()->get(CURLOPT_SSL_VERIFYPEER)); + } + + public function inputValidation() + { + return array_map(function ($option) { return array($option); }, array( + 'headers', 'query', 'cookies', 'auth', 'events', 'plugins', 'params' + )); + } + + /** + * @dataProvider inputValidation + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesInput($option) + { + $client = new Client(); + $client->get('/', array(), array($option => 'foo')); + } + + public function testCanAddRequestParams() + { + $client = new Client(); + $request = $client->put('/', array(), null, array('params' => array('foo' => 'test'))); + $this->assertEquals('test', $request->getParams()->get('foo')); + } + + public function testCanAddSslKey() + { + $client = new Client(); + $request = $client->get('/', array(), array('ssl_key' => '/foo.pem')); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLKEY)); + } + + public function testCanAddSslKeyPassword() + { + $client = new Client(); + $request = $client->get('/', array(), array('ssl_key' => array('/foo.pem', 'bar'))); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLKEY)); + $this->assertEquals('bar', $request->getCurlOptions()->get(CURLOPT_SSLKEYPASSWD)); + } + + public function testCanAddSslCert() + { + $client = new Client(); + $request = $client->get('/', array(), array('cert' => '/foo.pem')); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLCERT)); + } + + public function testCanAddSslCertPassword() + { + $client = new Client(); + $request = $client->get('/', array(), array('cert' => array('/foo.pem', 'bar'))); + $this->assertEquals('/foo.pem', $request->getCurlOptions()->get(CURLOPT_SSLCERT)); + $this->assertEquals('bar', $request->getCurlOptions()->get(CURLOPT_SSLCERTPASSWD)); + } + + public function testCreatesBodyWithoutZeroString() + { + $request = RequestFactory::getInstance()->create('PUT', 'http://test.com', array(), '0'); + $this->assertSame('0', (string) $request->getBody()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php new file mode 100644 index 0000000..5bf6248 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/RequestTest.php @@ -0,0 +1,639 @@ +client = new Client($this->getServer()->getUrl()); + $this->request = $this->client->get(); + } + + public function tearDown() + { + unset($this->request); + unset($this->client); + } + + public function testConstructorBuildsRequestWithArrayHeaders() + { + // Test passing an array of headers + $request = new Request('GET', 'http://www.guzzle-project.com/', array( + 'foo' => 'bar' + )); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('http://www.guzzle-project.com/', $request->getUrl()); + $this->assertEquals('bar', $request->getHeader('foo')); + } + + public function testDescribesEvents() + { + $this->assertInternalType('array', Request::getAllEvents()); + } + + public function testConstructorBuildsRequestWithCollectionHeaders() + { + $request = new Request('GET', 'http://www.guzzle-project.com/', new Collection(array( + 'foo' => 'bar' + ))); + $this->assertEquals('bar', $request->getHeader('foo')); + } + + public function testConstructorBuildsRequestWithNoHeaders() + { + $request = new Request('GET', 'http://www.guzzle-project.com/', null); + $this->assertFalse($request->hasHeader('foo')); + } + + public function testConstructorHandlesNonBasicAuth() + { + $request = new Request('GET', 'http://www.guzzle-project.com/', array( + 'Authorization' => 'Foo bar' + )); + $this->assertNull($request->getUserName()); + $this->assertNull($request->getPassword()); + $this->assertEquals('Foo bar', (string) $request->getHeader('Authorization')); + } + + public function testRequestsCanBeConvertedToRawMessageStrings() + { + $auth = base64_encode('michael:123'); + $message = "PUT /path?q=1&v=2 HTTP/1.1\r\n" + . "Host: www.google.com\r\n" + . "Authorization: Basic {$auth}\r\n" + . "Content-Length: 4\r\n\r\nData"; + + $request = RequestFactory::getInstance()->create('PUT', 'http://www.google.com/path?q=1&v=2', array( + 'Authorization' => 'Basic ' . $auth + ), 'Data'); + + $this->assertEquals($message, $request->__toString()); + } + + /** + * Add authorization after the fact and see that it was put in the message + */ + public function testRequestStringsIncludeAuth() + { + $auth = base64_encode('michael:123'); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = RequestFactory::getInstance()->create('PUT', $this->getServer()->getUrl(), null, 'Data') + ->setClient($this->client) + ->setAuth('michael', '123', CURLAUTH_BASIC); + $request->send(); + + $this->assertContains('Authorization: Basic ' . $auth, (string) $request); + } + + public function testGetEventDispatcher() + { + $d = $this->request->getEventDispatcher(); + $this->assertInstanceOf('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface', $d); + $this->assertEquals($d, $this->request->getEventDispatcher()); + } + + public function testRequestsManageClients() + { + $request = new Request('GET', 'http://test.com'); + $this->assertNull($request->getClient()); + $request->setClient($this->client); + $this->assertSame($this->client, $request->getClient()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage A client must be set on the request + */ + public function testRequestsRequireClients() + { + $request = new Request('GET', 'http://test.com'); + $request->send(); + } + + public function testSend() + { + $response = new Response(200, array( + 'Content-Length' => 3 + ), 'abc'); + $this->request->setResponse($response, true); + $r = $this->request->send(); + + $this->assertSame($response, $r); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $this->request->getResponse()); + $this->assertSame($r, $this->request->getResponse()); + $this->assertEquals('complete', $this->request->getState()); + } + + public function testGetResponse() + { + $this->assertNull($this->request->getResponse()); + $response = new Response(200, array('Content-Length' => 3), 'abc'); + + $this->request->setResponse($response); + $this->assertEquals($response, $this->request->getResponse()); + + $client = new Client('http://www.google.com'); + $request = $client->get('http://www.google.com/'); + $request->setResponse($response, true); + $request->send(); + $requestResponse = $request->getResponse(); + $this->assertSame($response, $requestResponse); + + // Try again, making sure it's still the same response + $this->assertSame($requestResponse, $request->getResponse()); + + $response = new Response(204); + $request = $client->get(); + $request->setResponse($response, true); + $request->send(); + $requestResponse = $request->getResponse(); + $this->assertSame($response, $requestResponse); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody()); + } + + public function testRequestThrowsExceptionOnBadResponse() + { + try { + $this->request->setResponse(new Response(404, array('Content-Length' => 3), 'abc'), true); + $this->request->send(); + $this->fail('Expected exception not thrown'); + } catch (BadResponseException $e) { + $this->assertInstanceOf('Guzzle\\Http\\Message\\RequestInterface', $e->getRequest()); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $e->getResponse()); + $this->assertContains('Client error response', $e->getMessage()); + } + } + + public function testManagesQuery() + { + $this->assertInstanceOf('Guzzle\\Http\\QueryString', $this->request->getQuery()); + $this->request->getQuery()->set('test', '123'); + $this->assertEquals('test=123', $this->request->getQuery(true)); + } + + public function testRequestHasMethod() + { + $this->assertEquals('GET', $this->request->getMethod()); + } + + public function testRequestHasScheme() + { + $this->assertEquals('http', $this->request->getScheme()); + $this->assertEquals($this->request, $this->request->setScheme('https')); + $this->assertEquals('https', $this->request->getScheme()); + } + + public function testRequestHasHost() + { + $this->assertEquals('127.0.0.1', $this->request->getHost()); + $this->assertEquals('127.0.0.1:8124', (string) $this->request->getHeader('Host')); + + $this->assertSame($this->request, $this->request->setHost('www2.google.com')); + $this->assertEquals('www2.google.com', $this->request->getHost()); + $this->assertEquals('www2.google.com:8124', (string) $this->request->getHeader('Host')); + + $this->assertSame($this->request, $this->request->setHost('www.test.com:8081')); + $this->assertEquals('www.test.com', $this->request->getHost()); + $this->assertEquals(8081, $this->request->getPort()); + } + + public function testRequestHasProtocol() + { + $this->assertEquals('1.1', $this->request->getProtocolVersion()); + $this->assertEquals($this->request, $this->request->setProtocolVersion('1.1')); + $this->assertEquals('1.1', $this->request->getProtocolVersion()); + $this->assertEquals($this->request, $this->request->setProtocolVersion('1.0')); + $this->assertEquals('1.0', $this->request->getProtocolVersion()); + } + + public function testRequestHasPath() + { + $this->assertEquals('/', $this->request->getPath()); + $this->assertEquals($this->request, $this->request->setPath('/index.html')); + $this->assertEquals('/index.html', $this->request->getPath()); + $this->assertEquals($this->request, $this->request->setPath('index.html')); + $this->assertEquals('/index.html', $this->request->getPath()); + } + + public function testPermitsFalsyComponents() + { + $request = new Request('GET', 'http://0/0?0'); + $this->assertSame('0', $request->getHost()); + $this->assertSame('/0', $request->getPath()); + $this->assertSame('0', $request->getQuery(true)); + + $request = new Request('GET', '0'); + $this->assertEquals('/0', $request->getPath()); + } + + public function testRequestHasPort() + { + $this->assertEquals(8124, $this->request->getPort()); + $this->assertEquals('127.0.0.1:8124', $this->request->getHeader('Host')); + + $this->assertEquals($this->request, $this->request->setPort('8080')); + $this->assertEquals('8080', $this->request->getPort()); + $this->assertEquals('127.0.0.1:8080', $this->request->getHeader('Host')); + + $this->request->setPort(80); + $this->assertEquals('127.0.0.1', $this->request->getHeader('Host')); + } + + public function testRequestHandlesAuthorization() + { + // Uninitialized auth + $this->assertEquals(null, $this->request->getUsername()); + $this->assertEquals(null, $this->request->getPassword()); + + // Set an auth + $this->assertSame($this->request, $this->request->setAuth('michael', '123')); + $this->assertEquals('michael', $this->request->getUsername()); + $this->assertEquals('123', $this->request->getPassword()); + + // Set an auth with blank password + $this->assertSame($this->request, $this->request->setAuth('michael', '')); + $this->assertEquals('michael', $this->request->getUsername()); + $this->assertEquals('', $this->request->getPassword()); + + // Remove the auth + $this->request->setAuth(false); + $this->assertEquals(null, $this->request->getUsername()); + $this->assertEquals(null, $this->request->getPassword()); + + // Make sure that the cURL based auth works too + $request = new Request('GET', $this->getServer()->getUrl()); + $request->setAuth('michael', 'password', CURLAUTH_DIGEST); + $this->assertEquals('michael:password', $request->getCurlOptions()->get(CURLOPT_USERPWD)); + $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH)); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesAuth() + { + $this->request->setAuth('foo', 'bar', 'bam'); + } + + public function testGetResourceUri() + { + $this->assertEquals('/', $this->request->getResource()); + $this->request->setPath('/index.html'); + $this->assertEquals('/index.html', $this->request->getResource()); + $this->request->getQuery()->add('v', '1'); + $this->assertEquals('/index.html?v=1', $this->request->getResource()); + } + + public function testRequestHasMutableUrl() + { + $url = 'http://www.test.com:8081/path?q=123#fragment'; + $u = Url::factory($url); + $this->assertSame($this->request, $this->request->setUrl($url)); + $this->assertEquals($url, $this->request->getUrl()); + + $this->assertSame($this->request, $this->request->setUrl($u)); + $this->assertEquals($url, $this->request->getUrl()); + } + + public function testRequestHasState() + { + $this->assertEquals(RequestInterface::STATE_NEW, $this->request->getState()); + $this->request->setState(RequestInterface::STATE_TRANSFER); + $this->assertEquals(RequestInterface::STATE_TRANSFER, $this->request->getState()); + } + + public function testSetManualResponse() + { + $response = new Response(200, array( + 'Date' => 'Sat, 16 Oct 2010 17:27:14 GMT', + 'Expires' => '-1', + 'Cache-Control' => 'private, max-age=0', + 'Content-Type' => 'text/html; charset=ISO-8859-1', + ), 'response body'); + + $this->assertSame($this->request, $this->request->setResponse($response), '-> setResponse() must use a fluent interface'); + $this->assertEquals('complete', $this->request->getState(), '-> setResponse() must change the state of the request to complete'); + $this->assertSame($response, $this->request->getResponse(), '-> setResponse() must set the exact same response that was passed in to it'); + } + + public function testRequestCanHaveManuallySetResponseBody() + { + $file = __DIR__ . '/../../TestData/temp.out'; + if (file_exists($file)) { + unlink($file); + } + + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata"); + $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl()); + $request->setClient($this->client); + $entityBody = EntityBody::factory(fopen($file, 'w+')); + $request->setResponseBody($entityBody); + $response = $request->send(); + $this->assertSame($entityBody, $response->getBody()); + + $this->assertTrue(file_exists($file)); + $this->assertEquals('data', file_get_contents($file)); + unlink($file); + + $this->assertEquals('data', $response->getBody(true)); + } + + public function testHoldsCookies() + { + $this->assertNull($this->request->getCookie('test')); + + // Set a cookie + $this->assertSame($this->request, $this->request->addCookie('test', 'abc')); + $this->assertEquals('abc', $this->request->getCookie('test')); + + // Multiple cookies by setting the Cookie header + $this->request->setHeader('Cookie', '__utma=1.638370270.1344367610.1374365610.1944450276.2; __utmz=1.1346368610.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); hl=de; PHPSESSID=ak93pqashi5uubuoq8fjv60897'); + $this->assertEquals('1.638370270.1344367610.1374365610.1944450276.2', $this->request->getCookie('__utma')); + $this->assertEquals('1.1346368610.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)', $this->request->getCookie('__utmz')); + $this->assertEquals('de', $this->request->getCookie('hl')); + $this->assertEquals('ak93pqashi5uubuoq8fjv60897', $this->request->getCookie('PHPSESSID')); + + // Unset the cookies by setting the Cookie header to null + $this->request->setHeader('Cookie', null); + $this->assertNull($this->request->getCookie('test')); + $this->request->removeHeader('Cookie'); + + // Set and remove a cookie + $this->assertSame($this->request, $this->request->addCookie('test', 'abc')); + $this->assertEquals('abc', $this->request->getCookie('test')); + $this->assertSame($this->request, $this->request->removeCookie('test')); + $this->assertNull($this->request->getCookie('test')); + + // Remove the cookie header + $this->assertSame($this->request, $this->request->addCookie('test', 'abc')); + $this->request->removeHeader('Cookie'); + $this->assertEquals('', (string) $this->request->getHeader('Cookie')); + + // Remove a cookie value + $this->request->addCookie('foo', 'bar')->addCookie('baz', 'boo'); + $this->request->removeCookie('foo'); + $this->assertEquals(array( + 'baz' => 'boo' + ), $this->request->getCookies()); + + $this->request->addCookie('foo', 'bar'); + $this->assertEquals('baz=boo; foo=bar', (string) $this->request->getHeader('Cookie')); + } + + /** + * @expectedException \Guzzle\Http\Exception\RequestException + * @expectedExceptionMessage Error completing request + */ + public function testRequestThrowsExceptionWhenSetToCompleteWithNoResponse() + { + $this->request->setState(RequestInterface::STATE_COMPLETE); + } + + public function testClonedRequestsUseNewInternalState() + { + $p = new AsyncPlugin(); + $this->request->getEventDispatcher()->addSubscriber($p); + $h = $this->request->getHeader('Host'); + + $r = clone $this->request; + $this->assertEquals(RequestInterface::STATE_NEW, $r->getState()); + $this->assertNotSame($r->getQuery(), $this->request->getQuery()); + $this->assertNotSame($r->getCurlOptions(), $this->request->getCurlOptions()); + $this->assertNotSame($r->getEventDispatcher(), $this->request->getEventDispatcher()); + $this->assertEquals($r->getHeaders(), $this->request->getHeaders()); + $this->assertNotSame($h, $r->getHeader('Host')); + $this->assertNotSame($r->getParams(), $this->request->getParams()); + $this->assertTrue($this->request->getEventDispatcher()->hasListeners('request.sent')); + } + + public function testRecognizesBasicAuthCredentialsInUrls() + { + $this->request->setUrl('http://michael:test@test.com/'); + $this->assertEquals('michael', $this->request->getUsername()); + $this->assertEquals('test', $this->request->getPassword()); + } + + public function testRequestCanBeSentUsingCurl() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\nConnection: close\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\nExpires: Thu, 01 Dec 1994 16:00:00 GMT\r\nConnection: close\r\n\r\ndata", + "HTTP/1.1 404 Not Found\r\nContent-Encoding: application/xml\r\nContent-Length: 48\r\n\r\nFile not found" + )); + + $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl()); + $request->setClient($this->client); + $response = $request->send(); + + $this->assertEquals('data', $response->getBody(true)); + $this->assertEquals(200, (int) $response->getStatusCode()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals(4, $response->getContentLength()); + $this->assertEquals('Thu, 01 Dec 1994 16:00:00 GMT', $response->getExpires()); + + // Test that the same handle can be sent twice without setting state to new + $response2 = $request->send(); + $this->assertNotSame($response, $response2); + + try { + $request = RequestFactory::getInstance()->create('GET', $this->getServer()->getUrl() . 'index.html'); + $request->setClient($this->client); + $response = $request->send(); + $this->fail('Request did not receive a 404 response'); + } catch (BadResponseException $e) { + } + + $requests = $this->getServer()->getReceivedRequests(true); + $messages = $this->getServer()->getReceivedRequests(false); + $port = $this->getServer()->getPort(); + + $userAgent = $this->client->getDefaultUserAgent(); + + $this->assertEquals('127.0.0.1:' . $port, $requests[0]->getHeader('Host')); + $this->assertEquals('127.0.0.1:' . $port, $requests[1]->getHeader('Host')); + $this->assertEquals('127.0.0.1:' . $port, $requests[2]->getHeader('Host')); + + $this->assertEquals('/', $requests[0]->getPath()); + $this->assertEquals('/', $requests[1]->getPath()); + $this->assertEquals('/index.html', $requests[2]->getPath()); + + $parts = explode("\r\n", $messages[0]); + $this->assertEquals('GET / HTTP/1.1', $parts[0]); + + $parts = explode("\r\n", $messages[1]); + $this->assertEquals('GET / HTTP/1.1', $parts[0]); + + $parts = explode("\r\n", $messages[2]); + $this->assertEquals('GET /index.html HTTP/1.1', $parts[0]); + } + + public function testThrowsExceptionsWhenUnsuccessfulResponseIsReceivedByDefault() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 404 Not found\r\nContent-Length: 0\r\n\r\n"); + + try { + $request = $this->client->get('/index.html'); + $response = $request->send(); + $this->fail('Request did not receive a 404 response'); + } catch (BadResponseException $e) { + $this->assertContains('Client error response', $e->getMessage()); + $this->assertContains('[status code] 404', $e->getMessage()); + $this->assertContains('[reason phrase] Not found', $e->getMessage()); + } + } + + public function testCanShortCircuitErrorHandling() + { + $request = $this->request; + $response = new Response(404); + $request->setResponse($response, true); + $out = ''; + $that = $this; + $request->getEventDispatcher()->addListener('request.error', function($event) use (&$out, $that) { + $out .= $event['request'] . "\n" . $event['response'] . "\n"; + $event->stopPropagation(); + }); + $request->send(); + $this->assertContains((string) $request, $out); + $this->assertContains((string) $request->getResponse(), $out); + $this->assertSame($response, $request->getResponse()); + } + + public function testCanOverrideUnsuccessfulResponses() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 404 NOT FOUND\r\n" . + "Content-Length: 0\r\n" . + "\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Length: 0\r\n" . + "\r\n" + )); + + $newResponse = null; + + $request = $this->request; + $request->getEventDispatcher()->addListener('request.error', function($event) use (&$newResponse) { + if ($event['response']->getStatusCode() == 404) { + $newRequest = clone $event['request']; + $newResponse = $newRequest->send(); + // Override the original response and bypass additional response processing + $event['response'] = $newResponse; + // Call $event['request']->setResponse($newResponse); to re-apply events + $event->stopPropagation(); + } + }); + + $request->send(); + + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $this->assertSame($newResponse, $request->getResponse()); + $this->assertEquals(2, count($this->getServer()->getReceivedRequests())); + } + + public function testCanRetrieveUrlObject() + { + $request = new Request('GET', 'http://www.example.com/foo?abc=d'); + $this->assertInstanceOf('Guzzle\Http\Url', $request->getUrl(true)); + $this->assertEquals('http://www.example.com/foo?abc=d', $request->getUrl()); + $this->assertEquals('http://www.example.com/foo?abc=d', (string) $request->getUrl(true)); + } + + public function testUnresolvedRedirectsReturnResponse() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 303 SEE OTHER\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n" + )); + $request = $this->request; + $this->assertEquals(303, $request->send()->getStatusCode()); + $request->getParams()->set(RedirectPlugin::DISABLE, true); + $this->assertEquals(301, $request->send()->getStatusCode()); + } + + public function testCanSendCustomRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $request = $this->client->createRequest('PROPFIND', $this->getServer()->getUrl(), array( + 'Content-Type' => 'text/plain' + ), 'foo'); + $response = $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('PROPFIND', $requests[0]->getMethod()); + $this->assertEquals(3, (string) $requests[0]->getHeader('Content-Length')); + $this->assertEquals('foo', (string) $requests[0]->getBody()); + } + + /** + * @expectedException \PHPUnit_Framework_Error_Warning + */ + public function testEnsuresFileCanBeCreated() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $this->client->get('/')->setResponseBody('/wefwefefefefwewefwe/wefwefwefefwe/wefwefewfw.txt')->send(); + } + + public function testAllowsFilenameForDownloadingContent() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $name = sys_get_temp_dir() . '/foo.txt'; + $this->client->get('/')->setResponseBody($name)->send(); + $this->assertEquals('test', file_get_contents($name)); + unlink($name); + } + + public function testUsesCustomResponseBodyWhenItIsCustom() + { + $en = EntityBody::factory(); + $request = $this->client->get(); + $request->setResponseBody($en); + $request->setResponse(new Response(200, array(), 'foo')); + $this->assertEquals('foo', (string) $en); + } + + public function testCanChangePortThroughScheme() + { + $request = new Request('GET', 'http://foo.com'); + $request->setScheme('https'); + $this->assertEquals('https://foo.com', (string) $request->getUrl()); + $this->assertEquals('foo.com', $request->getHost()); + $request->setScheme('http'); + $this->assertEquals('http://foo.com', (string) $request->getUrl()); + $this->assertEquals('foo.com', $request->getHost()); + $request->setPort(null); + $this->assertEquals('http://foo.com', (string) $request->getUrl()); + $this->assertEquals('foo.com', $request->getHost()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php new file mode 100644 index 0000000..08b4df8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Message/ResponseTest.php @@ -0,0 +1,677 @@ +response = new Response(200, new Collection(array( + 'Accept-Ranges' => 'bytes', + 'Age' => '12', + 'Allow' => 'GET, HEAD', + 'Cache-Control' => 'no-cache', + 'Content-Encoding' => 'gzip', + 'Content-Language' => 'da', + 'Content-Length' => '348', + 'Content-Location' => '/index.htm', + 'Content-Disposition' => 'attachment; filename=fname.ext', + 'Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ==', + 'Content-Range' => 'bytes 21010-47021/47022', + 'Content-Type' => 'text/html; charset=utf-8', + 'Date' => 'Tue, 15 Nov 1994 08:12:31 GMT', + 'ETag' => '737060cd8c284d8af7ad3082f209582d', + 'Expires' => 'Thu, 01 Dec 1994 16:00:00 GMT', + 'Last-Modified' => 'Tue, 15 Nov 1994 12:45:26 GMT', + 'Location' => 'http://www.w3.org/pub/WWW/People.html', + 'Pragma' => 'no-cache', + 'Proxy-Authenticate' => 'Basic', + 'Retry-After' => '120', + 'Server' => 'Apache/1.3.27 (Unix) (Red-Hat/Linux)', + 'Set-Cookie' => 'UserID=JohnDoe; Max-Age=3600; Version=1', + 'Trailer' => 'Max-Forwards', + 'Transfer-Encoding' => 'chunked', + 'Vary' => '*', + 'Via' => '1.0 fred, 1.1 nowhere.com (Apache/1.1)', + 'Warning' => '199 Miscellaneous warning', + 'WWW-Authenticate' => 'Basic' + )), 'body'); + } + + public function tearDown() + { + unset($this->response); + } + + public function testConstructor() + { + $params = new Collection(); + $body = EntityBody::factory(''); + $response = new Response(200, $params, $body); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals($body, $response->getBody()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", $response->getRawHeaders()); + + // Make sure Content-Length is set automatically + $response = new Response(200, $params); + $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", $response->getRawHeaders()); + + // Pass bodies to the response + $response = new Response(200, null, 'data'); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody()); + $response = new Response(200, null, EntityBody::factory('data')); + $this->assertInstanceOf('Guzzle\\Http\\EntityBody', $response->getBody()); + $this->assertEquals('data', $response->getBody(true)); + $response = new Response(200, null, '0'); + $this->assertSame('0', $response->getBody(true), 'getBody(true) should return "0" if response body is "0".'); + + // Make sure the proper exception is thrown + try { + //$response = new Response(200, null, array('foo' => 'bar')); + //$this->fail('Response did not throw exception when passing invalid body'); + } catch (HttpException $e) { + } + + // Ensure custom codes can be set + $response = new Response(2); + $this->assertEquals(2, $response->getStatusCode()); + $this->assertEquals('', $response->getReasonPhrase()); + + // Make sure the proper exception is thrown when sending invalid headers + try { + $response = new Response(200, 'adidas'); + $this->fail('Response did not throw exception when passing invalid $headers'); + } catch (BadResponseException $e) { + } + } + + public function test__toString() + { + $response = new Response(200); + $this->assertEquals("HTTP/1.1 200 OK\r\n\r\n", (string) $response); + + // Add another header + $response = new Response(200, array( + 'X-Test' => 'Guzzle' + )); + $this->assertEquals("HTTP/1.1 200 OK\r\nX-Test: Guzzle\r\n\r\n", (string) $response); + + $response = new Response(200, array( + 'Content-Length' => 4 + ), 'test'); + $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest", (string) $response); + } + + public function testFactory() + { + $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals(4, (string) $response->getContentLength()); + $this->assertEquals('test', $response->getBody(true)); + + // Make sure that automatic Content-Length works + $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest"); + $this->assertEquals(4, (string) $response->getContentLength()); + $this->assertEquals('test', $response->getBody(true)); + } + + public function testFactoryCanCreateHeadResponses() + { + $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n"); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('OK', $response->getReasonPhrase()); + $this->assertEquals(4, (string) $response->getContentLength()); + $this->assertEquals('', $response->getBody(true)); + } + + public function testFactoryRequiresMessage() + { + $this->assertFalse(Response::fromMessage('')); + } + + public function testGetBody() + { + $body = EntityBody::factory(''); + $response = new Response(403, new Collection(), $body); + $this->assertEquals($body, $response->getBody()); + $response->setBody('foo'); + $this->assertEquals('foo', $response->getBody(true)); + } + + public function testManagesStatusCode() + { + $response = new Response(403); + $this->assertEquals(403, $response->getStatusCode()); + } + + public function testGetMessage() + { + $response = new Response(200, new Collection(array( + 'Content-Length' => 4 + )), 'body'); + + $this->assertEquals("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nbody", $response->getMessage()); + } + + public function testGetRawHeaders() + { + $response = new Response(200, new Collection(array( + 'Keep-Alive' => 155, + 'User-Agent' => 'Guzzle', + 'Content-Length' => 4 + )), 'body'); + + $this->assertEquals("HTTP/1.1 200 OK\r\nKeep-Alive: 155\r\nUser-Agent: Guzzle\r\nContent-Length: 4\r\n\r\n", $response->getRawHeaders()); + } + + public function testHandlesStatusAndStatusCodes() + { + $response = new Response(200, new Collection(), 'body'); + $this->assertEquals('OK', $response->getReasonPhrase()); + + $this->assertSame($response, $response->setStatus(204)); + $this->assertEquals('No Content', $response->getReasonPhrase()); + $this->assertEquals(204, $response->getStatusCode()); + + $this->assertSame($response, $response->setStatus(204, 'Testing!')); + $this->assertEquals('Testing!', $response->getReasonPhrase()); + $this->assertEquals(204, $response->getStatusCode()); + + $response->setStatus(2000); + $this->assertEquals(2000, $response->getStatusCode()); + $this->assertEquals('', $response->getReasonPhrase()); + + $response->setStatus(200, 'Foo'); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('Foo', $response->getReasonPhrase()); + } + + public function testIsClientError() + { + $response = new Response(403); + $this->assertTrue($response->isClientError()); + $response = new Response(200); + $this->assertFalse($response->isClientError()); + } + + public function testIsError() + { + $response = new Response(403); + $this->assertTrue($response->isError()); + $response = new Response(200); + $this->assertFalse($response->isError()); + $response = new Response(500); + $this->assertTrue($response->isError()); + } + + public function testIsInformational() + { + $response = new Response(100); + $this->assertTrue($response->isInformational()); + $response = new Response(200); + $this->assertFalse($response->isInformational()); + } + + public function testIsRedirect() + { + $response = new Response(301); + $this->assertTrue($response->isRedirect()); + $response = new Response(200); + $this->assertFalse($response->isRedirect()); + } + + public function testIsServerError() + { + $response = new Response(500); + $this->assertTrue($response->isServerError()); + $response = new Response(400); + $this->assertFalse($response->isServerError()); + } + + public function testIsSuccessful() + { + $response = new Response(200); + $this->assertTrue($response->isSuccessful()); + $response = new Response(403); + $this->assertFalse($response->isSuccessful()); + } + + public function testGetAcceptRanges() + { + $this->assertEquals('bytes', $this->response->getAcceptRanges()); + } + + public function testCalculatesAge() + { + $this->assertEquals(12, $this->response->calculateAge()); + + $this->response->removeHeader('Age'); + $this->response->removeHeader('Date'); + $this->assertNull($this->response->calculateAge()); + + $this->response->setHeader('Date', gmdate(ClientInterface::HTTP_DATE, strtotime('-1 minute'))); + // If the test runs slowly, still pass with a +5 second allowance + $this->assertTrue($this->response->getAge() - 60 <= 5); + } + + public function testGetAllow() + { + $this->assertEquals('GET, HEAD', $this->response->getAllow()); + } + + public function testGetCacheControl() + { + $this->assertEquals('no-cache', $this->response->getCacheControl()); + } + + public function testGetContentEncoding() + { + $this->assertEquals('gzip', $this->response->getContentEncoding()); + } + + public function testGetContentLanguage() + { + $this->assertEquals('da', $this->response->getContentLanguage()); + } + + public function testGetContentLength() + { + $this->assertEquals('348', $this->response->getContentLength()); + } + + public function testGetContentLocation() + { + $this->assertEquals('/index.htm', $this->response->getContentLocation()); + } + + public function testGetContentDisposition() + { + $this->assertEquals('attachment; filename=fname.ext', $this->response->getContentDisposition()); + } + + public function testGetContentMd5() + { + $this->assertEquals('Q2hlY2sgSW50ZWdyaXR5IQ==', $this->response->getContentMd5()); + } + + public function testGetContentRange() + { + $this->assertEquals('bytes 21010-47021/47022', $this->response->getContentRange()); + } + + public function testGetContentType() + { + $this->assertEquals('text/html; charset=utf-8', $this->response->getContentType()); + } + + public function testGetDate() + { + $this->assertEquals('Tue, 15 Nov 1994 08:12:31 GMT', $this->response->getDate()); + } + + public function testGetEtag() + { + $this->assertEquals('737060cd8c284d8af7ad3082f209582d', $this->response->getEtag()); + } + + public function testGetExpires() + { + $this->assertEquals('Thu, 01 Dec 1994 16:00:00 GMT', $this->response->getExpires()); + } + + public function testGetLastModified() + { + $this->assertEquals('Tue, 15 Nov 1994 12:45:26 GMT', $this->response->getLastModified()); + } + + public function testGetLocation() + { + $this->assertEquals('http://www.w3.org/pub/WWW/People.html', $this->response->getLocation()); + } + + public function testGetPragma() + { + $this->assertEquals('no-cache', $this->response->getPragma()); + } + + public function testGetProxyAuthenticate() + { + $this->assertEquals('Basic', $this->response->getProxyAuthenticate()); + } + + public function testGetServer() + { + $this->assertEquals('Apache/1.3.27 (Unix) (Red-Hat/Linux)', $this->response->getServer()); + } + + public function testGetSetCookie() + { + $this->assertEquals('UserID=JohnDoe; Max-Age=3600; Version=1', $this->response->getSetCookie()); + } + + public function testGetMultipleSetCookie() + { + $this->response->addHeader('Set-Cookie', 'UserID=Mike; Max-Age=200'); + $this->assertEquals(array( + 'UserID=JohnDoe; Max-Age=3600; Version=1', + 'UserID=Mike; Max-Age=200', + ), $this->response->getHeader('Set-Cookie')->toArray()); + } + + public function testGetSetCookieNormalizesHeaders() + { + $this->response->addHeaders(array( + 'Set-Cooke' => 'boo', + 'set-cookie' => 'foo' + )); + + $this->assertEquals(array( + 'UserID=JohnDoe; Max-Age=3600; Version=1', + 'foo' + ), $this->response->getHeader('Set-Cookie')->toArray()); + + $this->response->addHeaders(array( + 'set-cookie' => 'fubu' + )); + $this->assertEquals( + array('UserID=JohnDoe; Max-Age=3600; Version=1', 'foo', 'fubu'), + $this->response->getHeader('Set-Cookie')->toArray() + ); + } + + public function testGetTrailer() + { + $this->assertEquals('Max-Forwards', $this->response->getTrailer()); + } + + public function testGetTransferEncoding() + { + $this->assertEquals('chunked', $this->response->getTransferEncoding()); + } + + public function testGetVary() + { + $this->assertEquals('*', $this->response->getVary()); + } + + public function testReturnsViaHeader() + { + $this->assertEquals('1.0 fred, 1.1 nowhere.com (Apache/1.1)', $this->response->getVia()); + } + public function testGetWarning() + { + $this->assertEquals('199 Miscellaneous warning', $this->response->getWarning()); + } + + public function testReturnsWwwAuthenticateHeader() + { + $this->assertEquals('Basic', $this->response->getWwwAuthenticate()); + } + + public function testReturnsConnectionHeader() + { + $this->assertEquals(null, $this->response->getConnection()); + $this->response->setHeader('Connection', 'close'); + $this->assertEquals('close', $this->response->getConnection()); + } + + public function testReturnsHeaders() + { + $this->assertEquals('Basic', $this->response->getHeader('WWW-Authenticate', null, true)); + $this->assertEquals('chunked', $this->response->getHeader('Transfer-Encoding', null, false)); + } + + public function testHasTransferInfo() + { + $stats = array ( + 'url' => 'http://www.google.com/', + 'content_type' => 'text/html; charset=ISO-8859-1', + 'http_code' => 200, + 'header_size' => 606, + 'request_size' => 53, + 'filetime' => -1, + 'ssl_verify_result' => 0, + 'redirect_count' => 0, + 'total_time' => 0.093284, + 'namelookup_time' => 0.001349, + 'connect_time' => 0.01635, + 'pretransfer_time' => 0.016358, + 'size_upload' => 0, + 'size_download' => 10330, + 'speed_download' => 110737, + 'speed_upload' => 0, + 'download_content_length' => -1, + 'upload_content_length' => 0, + 'starttransfer_time' => 0.07066, + 'redirect_time' => 0, + ); + + // Uninitialized state + $this->assertNull($this->response->getInfo('url')); + $this->assertEquals(array(), $this->response->getInfo()); + + // Set the stats + $this->response->setInfo($stats); + $this->assertEquals($stats, $this->response->getInfo()); + $this->assertEquals(606, $this->response->getInfo('header_size')); + $this->assertNull($this->response->getInfo('does_not_exist')); + } + + /** + * @return Response + */ + private function getResponse($code, array $headers = null, EntityBody $body = null) + { + return new Response($code, $headers, $body); + } + + public function testDeterminesIfItCanBeCached() + { + $this->assertTrue($this->getResponse(200)->canCache()); + $this->assertTrue($this->getResponse(410)->canCache()); + $this->assertFalse($this->getResponse(404)->canCache()); + $this->assertTrue($this->getResponse(200, array( + 'Cache-Control' => 'public' + ))->canCache()); + + // This has the no-store directive + $this->assertFalse($this->getResponse(200, array( + 'Cache-Control' => 'private, no-store' + ))->canCache()); + + // The body cannot be read, so it cannot be cached + $tmp = tempnam('/tmp', 'not-readable'); + $resource = fopen($tmp, 'w'); + $this->assertFalse($this->getResponse(200, array( + 'Transfer-Encoding' => 'chunked' + ), EntityBody::factory($resource, 10))->canCache()); + unlink($tmp); + + // The body is 0 length, cannot be read, so it can be cached + $tmp = tempnam('/tmp', 'not-readable'); + $resource = fopen($tmp, 'w'); + $this->assertTrue($this->getResponse(200, array(array( + 'Content-Length' => 0 + )), EntityBody::factory($resource, 0))->canCache()); + unlink($tmp); + } + + public function testDeterminesResponseMaxAge() + { + $this->assertEquals(null, $this->getResponse(200)->getMaxAge()); + + // Uses the response's s-maxage + $this->assertEquals(140, $this->getResponse(200, array( + 'Cache-Control' => 's-maxage=140' + ))->getMaxAge()); + + // Uses the response's max-age + $this->assertEquals(120, $this->getResponse(200, array( + 'Cache-Control' => 'max-age=120' + ))->getMaxAge()); + + // Uses the response's max-age + $this->assertEquals(120, $this->getResponse(200, array( + 'Cache-Control' => 'max-age=120', + 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day')) + ))->getMaxAge()); + + // Uses the Expires date + $this->assertGreaterThanOrEqual(82400, $this->getResponse(200, array( + 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day')) + ))->getMaxAge()); + + // Uses the Expires date + $this->assertGreaterThanOrEqual(82400, $this->getResponse(200, array( + 'Expires' => gmdate(ClientInterface::HTTP_DATE, strtotime('+1 day')) + ))->getMaxAge()); + } + + public function testDeterminesIfItCanValidate() + { + $response = new Response(200); + $this->assertFalse($response->canValidate()); + $response->setHeader('ETag', '123'); + $this->assertTrue($response->canValidate()); + $response->removeHeader('ETag'); + $this->assertFalse($response->canValidate()); + $response->setHeader('Last-Modified', '123'); + $this->assertTrue($response->canValidate()); + } + + public function testCalculatesFreshness() + { + $response = new Response(200); + $this->assertNull($response->isFresh()); + $this->assertNull($response->getFreshness()); + + $response->setHeader('Cache-Control', 'max-age=120'); + $response->setHeader('Age', 100); + $this->assertEquals(20, $response->getFreshness()); + $this->assertTrue($response->isFresh()); + + $response->setHeader('Age', 120); + $this->assertEquals(0, $response->getFreshness()); + $this->assertTrue($response->isFresh()); + + $response->setHeader('Age', 150); + $this->assertEquals(-30, $response->getFreshness()); + $this->assertFalse($response->isFresh()); + } + + public function testHandlesProtocols() + { + $this->assertSame($this->response, $this->response->setProtocol('HTTP', '1.0')); + $this->assertEquals('HTTP', $this->response->getProtocol()); + $this->assertEquals('1.0', $this->response->getProtocolVersion()); + } + + public function testComparesContentType() + { + $response = new Response(200, array( + 'Content-Type' => 'text/html; charset=ISO-8859-4' + )); + + $this->assertTrue($response->isContentType('text/html')); + $this->assertTrue($response->isContentType('TExT/html')); + $this->assertTrue($response->isContentType('charset=ISO-8859-4')); + $this->assertFalse($response->isContentType('application/xml')); + } + + public function testResponseDeterminesIfMethodIsAllowedBaseOnAllowHeader() + { + $response = new Response(200, array( + 'Allow' => 'OPTIONS, POST, deletE,GET' + )); + + $this->assertTrue($response->isMethodAllowed('get')); + $this->assertTrue($response->isMethodAllowed('GET')); + $this->assertTrue($response->isMethodAllowed('options')); + $this->assertTrue($response->isMethodAllowed('post')); + $this->assertTrue($response->isMethodAllowed('Delete')); + $this->assertFalse($response->isMethodAllowed('put')); + $this->assertFalse($response->isMethodAllowed('PUT')); + + $response = new Response(200); + $this->assertFalse($response->isMethodAllowed('get')); + } + + public function testParsesJsonResponses() + { + $response = new Response(200, array(), '{"foo": "bar"}'); + $this->assertEquals(array('foo' => 'bar'), $response->json()); + // Return array when null is a service response + $response = new Response(200); + $this->assertEquals(array(), $response->json()); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage Unable to parse response body into JSON: 4 + */ + public function testThrowsExceptionWhenFailsToParseJsonResponse() + { + $response = new Response(200, array(), '{"foo": "'); + $response->json(); + } + + public function testParsesXmlResponses() + { + $response = new Response(200, array(), 'bar'); + $this->assertEquals('bar', (string) $response->xml()->foo); + // Always return a SimpleXMLElement from the xml method + $response = new Response(200); + $this->assertEmpty((string) $response->xml()->foo); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage Unable to parse response body into XML: String could not be parsed as XML + */ + public function testThrowsExceptionWhenFailsToParseXmlResponse() + { + $response = new Response(200, array(), 'xml(); + } + + public function testResponseIsSerializable() + { + $response = new Response(200, array('Foo' => 'bar'), 'test'); + $r = unserialize(serialize($response)); + $this->assertEquals(200, $r->getStatusCode()); + $this->assertEquals('bar', (string) $r->getHeader('Foo')); + $this->assertEquals('test', (string) $r->getBody()); + } + + public function testPreventsComplexExternalEntities() + { + $xml = ']>&test;'; + $response = new Response(200, array(), $xml); + + $oldCwd = getcwd(); + chdir(__DIR__); + try { + $xml = $response->xml(); + chdir($oldCwd); + $this->markTestIncomplete('Did not throw the expected exception! XML resolved as: ' . $xml->asXML()); + } catch (\Exception $e) { + chdir($oldCwd); + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php new file mode 100644 index 0000000..7228453 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/MimetypesTest.php @@ -0,0 +1,31 @@ +assertEquals('text/x-php', Mimetypes::getInstance()->fromExtension('php')); + } + + public function testGetsFromFilename() + { + $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(__FILE__)); + } + + public function testGetsFromCaseInsensitiveFilename() + { + $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(strtoupper(__FILE__))); + } + + public function testReturnsNullWhenNoMatchFound() + { + $this->assertNull(Mimetypes::getInstance()->fromExtension('foobar')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php new file mode 100644 index 0000000..549d3ed --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/CommaAggregatorTest.php @@ -0,0 +1,30 @@ +aggregate($key, $value, $query); + $this->assertEquals(array('test%20123' => 'foo%20123,baz,bar'), $result); + } + + public function testEncodes() + { + $query = new QueryString(); + $query->useUrlEncoding(false); + $a = new Ag(); + $key = 'test 123'; + $value = array('foo 123', 'baz', 'bar'); + $result = $a->aggregate($key, $value, $query); + $this->assertEquals(array('test 123' => 'foo 123,baz,bar'), $result); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php new file mode 100644 index 0000000..6a4d9d9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/DuplicateAggregatorTest.php @@ -0,0 +1,30 @@ +aggregate($key, $value, $query); + $this->assertEquals(array('facet%201' => array('size%20a', 'width%20b')), $result); + } + + public function testEncodes() + { + $query = new QueryString(); + $query->useUrlEncoding(false); + $a = new Ag(); + $key = 'facet 1'; + $value = array('size a', 'width b'); + $result = $a->aggregate($key, $value, $query); + $this->assertEquals(array('facet 1' => array('size a', 'width b')), $result); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php new file mode 100644 index 0000000..1e7f0c2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryAggregator/PhpAggregatorTest.php @@ -0,0 +1,32 @@ +useUrlEncoding(false); + $a = new Ag(); + $key = 't'; + $value = array( + 'v1' => 'a', + 'v2' => 'b', + 'v3' => array( + 'v4' => 'c', + 'v5' => 'd', + ) + ); + $result = $a->aggregate($key, $value, $query); + $this->assertEquals(array( + 't[v1]' => 'a', + 't[v2]' => 'b', + 't[v3][v4]' => 'c', + 't[v3][v5]' => 'd', + ), $result); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php new file mode 100644 index 0000000..948db44 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/QueryStringTest.php @@ -0,0 +1,233 @@ +q = new QueryString(); + } + + public function testGetFieldSeparator() + { + $this->assertEquals('&', $this->q->getFieldSeparator()); + } + + public function testGetValueSeparator() + { + $this->assertEquals('=', $this->q->getValueSeparator()); + } + + public function testIsUrlEncoding() + { + $this->assertEquals('RFC 3986', $this->q->getUrlEncoding()); + $this->assertTrue($this->q->isUrlEncoding()); + $this->assertEquals('foo%20bar', $this->q->encodeValue('foo bar')); + + $this->q->useUrlEncoding(QueryString::FORM_URLENCODED); + $this->assertTrue($this->q->isUrlEncoding()); + $this->assertEquals(QueryString::FORM_URLENCODED, $this->q->getUrlEncoding()); + $this->assertEquals('foo+bar', $this->q->encodeValue('foo bar')); + + $this->assertSame($this->q, $this->q->useUrlEncoding(false)); + $this->assertFalse($this->q->isUrlEncoding()); + $this->assertFalse($this->q->isUrlEncoding()); + } + + public function testSetFieldSeparator() + { + $this->assertEquals($this->q, $this->q->setFieldSeparator('/')); + $this->assertEquals('/', $this->q->getFieldSeparator()); + } + + public function testSetValueSeparator() + { + $this->assertEquals($this->q, $this->q->setValueSeparator('/')); + $this->assertEquals('/', $this->q->getValueSeparator()); + } + + public function testUrlEncode() + { + $params = array( + 'test' => 'value', + 'test 2' => 'this is a test?', + 'test3' => array('v1', 'v2', 'v3'), + 'ሴ' => 'bar' + ); + $encoded = array( + 'test' => 'value', + 'test%202' => rawurlencode('this is a test?'), + 'test3%5B0%5D' => 'v1', + 'test3%5B1%5D' => 'v2', + 'test3%5B2%5D' => 'v3', + '%E1%88%B4' => 'bar' + ); + $this->q->replace($params); + $this->assertEquals($encoded, $this->q->urlEncode()); + + // Disable encoding + $testData = array('test 2' => 'this is a test'); + $this->q->replace($testData); + $this->q->useUrlEncoding(false); + $this->assertEquals($testData, $this->q->urlEncode()); + } + + public function testToString() + { + // Check with no parameters + $this->assertEquals('', $this->q->__toString()); + + $params = array( + 'test' => 'value', + 'test 2' => 'this is a test?', + 'test3' => array('v1', 'v2', 'v3'), + 'test4' => null, + ); + $this->q->replace($params); + $this->assertEquals('test=value&test%202=this%20is%20a%20test%3F&test3%5B0%5D=v1&test3%5B1%5D=v2&test3%5B2%5D=v3&test4', $this->q->__toString()); + $this->q->useUrlEncoding(false); + $this->assertEquals('test=value&test 2=this is a test?&test3[0]=v1&test3[1]=v2&test3[2]=v3&test4', $this->q->__toString()); + + // Use an alternative aggregator + $this->q->setAggregator(new CommaAggregator()); + $this->assertEquals('test=value&test 2=this is a test?&test3=v1,v2,v3&test4', $this->q->__toString()); + } + + public function testAllowsMultipleValuesPerKey() + { + $q = new QueryString(); + $q->add('facet', 'size'); + $q->add('facet', 'width'); + $q->add('facet.field', 'foo'); + // Use the duplicate aggregator + $q->setAggregator(new DuplicateAggregator()); + $this->assertEquals('facet=size&facet=width&facet.field=foo', $q->__toString()); + } + + public function testAllowsNestedQueryData() + { + $this->q->replace(array( + 'test' => 'value', + 't' => array( + 'v1' => 'a', + 'v2' => 'b', + 'v3' => array( + 'v4' => 'c', + 'v5' => 'd', + ) + ) + )); + + $this->q->useUrlEncoding(false); + $this->assertEquals('test=value&t[v1]=a&t[v2]=b&t[v3][v4]=c&t[v3][v5]=d', $this->q->__toString()); + } + + public function parseQueryProvider() + { + return array( + // Ensure that multiple query string values are allowed per value + array('q=a&q=b', array('q' => array('a', 'b'))), + // Ensure that PHP array style query string values are parsed + array('q[]=a&q[]=b', array('q' => array('a', 'b'))), + // Ensure that a single PHP array style query string value is parsed into an array + array('q[]=a', array('q' => array('a'))), + // Ensure that decimals are allowed in query strings + array('q.a=a&q.b=b', array( + 'q.a' => 'a', + 'q.b' => 'b' + )), + // Ensure that query string values are percent decoded + array('q%20a=a%20b', array('q a' => 'a b')), + // Ensure null values can be added + array('q&a', array('q' => false, 'a' => false)), + ); + } + + /** + * @dataProvider parseQueryProvider + */ + public function testParsesQueryStrings($query, $data) + { + $query = QueryString::fromString($query); + $this->assertEquals($data, $query->getAll()); + } + + public function testProperlyDealsWithDuplicateQueryStringValues() + { + $query = QueryString::fromString('foo=a&foo=b&?µ=c'); + $this->assertEquals(array('a', 'b'), $query->get('foo')); + $this->assertEquals('c', $query->get('?µ')); + } + + public function testAllowsBlankQueryStringValues() + { + $query = QueryString::fromString('foo'); + $this->assertEquals('foo', (string) $query); + $query->set('foo', QueryString::BLANK); + $this->assertEquals('foo', (string) $query); + } + + public function testAllowsFalsyQueryStringValues() + { + $query = QueryString::fromString('0'); + $this->assertEquals('0', (string) $query); + $query->set('0', QueryString::BLANK); + $this->assertSame('0', (string) $query); + } + + public function testFromStringIgnoresQuestionMark() + { + $query = QueryString::fromString('foo=baz&bar=boo'); + $this->assertEquals('foo=baz&bar=boo', (string) $query); + } + + public function testConvertsPlusSymbolsToSpaces() + { + $query = QueryString::fromString('var=foo+bar'); + $this->assertEquals('foo bar', $query->get('var')); + } + + public function testFromStringDoesntMangleZeroes() + { + $query = QueryString::fromString('var=0'); + $this->assertSame('0', $query->get('var')); + } + + public function testAllowsZeroValues() + { + $query = new QueryString(array( + 'foo' => 0, + 'baz' => '0', + 'bar' => null, + 'boo' => false, + 'bam' => '' + )); + $this->assertEquals('foo=0&baz=0&bar&boo&bam=', (string) $query); + } + + public function testFromStringDoesntStripTrailingEquals() + { + $query = QueryString::fromString('data=mF0b3IiLCJUZWFtIERldiJdfX0='); + $this->assertEquals('mF0b3IiLCJUZWFtIERldiJdfX0=', $query->get('data')); + } + + public function testGuessesIfDuplicateAggregatorShouldBeUsed() + { + $query = QueryString::fromString('test=a&test=b'); + $this->assertEquals('test=a&test=b', (string) $query); + } + + public function testGuessesIfDuplicateAggregatorShouldBeUsedAndChecksForPhpStyle() + { + $query = QueryString::fromString('test[]=a&test[]=b'); + $this->assertEquals('test%5B0%5D=a&test%5B1%5D=b', (string) $query); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php new file mode 100644 index 0000000..6bb3fed --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/ReadLimitEntityBodyTest.php @@ -0,0 +1,81 @@ +decorated = EntityBody::factory(fopen(__FILE__, 'r')); + $this->body = new ReadLimitEntityBody($this->decorated, 10, 3); + } + + public function testReturnsSubsetWhenCastToString() + { + $body = EntityBody::factory('foo_baz_bar'); + $limited = new ReadLimitEntityBody($body, 3, 4); + $this->assertEquals('baz', (string) $limited); + } + + public function testReturnsSubsetOfEmptyBodyWhenCastToString() + { + $body = EntityBody::factory(''); + $limited = new ReadLimitEntityBody($body, 0, 10); + $this->assertEquals('', (string) $limited); + } + + public function testSeeksWhenConstructed() + { + $this->assertEquals(3, $this->body->ftell()); + } + + public function testAllowsBoundedSeek() + { + $this->body->seek(100); + $this->assertEquals(13, $this->body->ftell()); + $this->body->seek(0); + $this->assertEquals(3, $this->body->ftell()); + $this->assertEquals(false, $this->body->seek(1000, SEEK_END)); + } + + public function testReadsOnlySubsetOfData() + { + $data = $this->body->read(100); + $this->assertEquals(10, strlen($data)); + $this->assertFalse($this->body->read(1000)); + + $this->body->setOffset(10); + $newData = $this->body->read(100); + $this->assertEquals(10, strlen($newData)); + $this->assertNotSame($data, $newData); + } + + public function testClaimsConsumedWhenReadLimitIsReached() + { + $this->assertFalse($this->body->isConsumed()); + $this->body->read(1000); + $this->assertTrue($this->body->isConsumed()); + } + + public function testContentLengthIsBounded() + { + $this->assertEquals(10, $this->body->getContentLength()); + } + + public function testContentMd5IsBasedOnSubsection() + { + $this->assertNotSame($this->body->getContentMd5(), $this->decorated->getContentMd5()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php new file mode 100644 index 0000000..886236d --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/RedirectPluginTest.php @@ -0,0 +1,277 @@ +getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + // Create a client that uses the default redirect behavior + $client = new Client($this->getServer()->getUrl()); + $history = new HistoryPlugin(); + $client->addSubscriber($history); + + $request = $client->get('/foo'); + $response = $request->send(); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertContains('/redirect2', $response->getEffectiveUrl()); + + // Ensure that two requests were sent + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('/foo', $requests[0]->getResource()); + $this->assertEquals('GET', $requests[0]->getMethod()); + $this->assertEquals('/redirect1', $requests[1]->getResource()); + $this->assertEquals('GET', $requests[1]->getMethod()); + $this->assertEquals('/redirect2', $requests[2]->getResource()); + $this->assertEquals('GET', $requests[2]->getMethod()); + + // Ensure that the redirect count was incremented + $this->assertEquals(2, $request->getParams()->get(RedirectPlugin::REDIRECT_COUNT)); + $this->assertCount(3, $history); + $requestHistory = $history->getAll(); + + $this->assertEquals(301, $requestHistory[0]['response']->getStatusCode()); + $this->assertEquals('/redirect1', (string) $requestHistory[0]['response']->getHeader('Location')); + $this->assertEquals(301, $requestHistory[1]['response']->getStatusCode()); + $this->assertEquals('/redirect2', (string) $requestHistory[1]['response']->getHeader('Location')); + $this->assertEquals(200, $requestHistory[2]['response']->getStatusCode()); + } + + public function testCanLimitNumberOfRedirects() + { + // Flush the server and queue up a redirect followed by a successful response + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect3\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect4\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect5\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect6\r\nContent-Length: 0\r\n\r\n" + )); + + try { + $client = new Client($this->getServer()->getUrl()); + $client->get('/foo')->send(); + $this->fail('Did not throw expected exception'); + } catch (TooManyRedirectsException $e) { + $this->assertContains( + "5 redirects were issued for this request:\nGET /foo HTTP/1.1\r\n", + $e->getMessage() + ); + } + } + + public function testDefaultBehaviorIsToRedirectWithGetForEntityEnclosingRequests() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $client->post('/foo', array('X-Baz' => 'bar'), 'testing')->send(); + + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('GET', $requests[1]->getMethod()); + $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz')); + $this->assertEquals('GET', $requests[2]->getMethod()); + } + + public function testCanRedirectWithStrictRfcCompliance() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/foo', array('X-Baz' => 'bar'), 'testing'); + $request->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, true); + $request->send(); + + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('POST', $requests[1]->getMethod()); + $this->assertEquals('bar', (string) $requests[1]->getHeader('X-Baz')); + $this->assertEquals('POST', $requests[2]->getMethod()); + } + + public function testRedirect303WithGet() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/foo'); + $request->send(); + + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('GET', $requests[1]->getMethod()); + } + + public function testRedirect303WithGetWithStrictRfcCompliance() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 303 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/foo'); + $request->getParams()->set(RedirectPlugin::STRICT_REDIRECTS, true); + $request->send(); + + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $requests[0]->getMethod()); + $this->assertEquals('GET', $requests[1]->getMethod()); + } + + public function testRewindsStreamWhenRedirectingIfNeeded() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put(); + $request->configureRedirects(true); + $body = EntityBody::factory('foo'); + $body->read(1); + $request->setBody($body); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('foo', (string) $requests[0]->getBody()); + } + + /** + * @expectedException \Guzzle\Http\Exception\CouldNotRewindStreamException + */ + public function testThrowsExceptionWhenStreamCannotBeRewound() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect\r\nContent-Length: 0\r\n\r\n" + )); + + $client = new Client($this->getServer()->getUrl()); + $request = $client->put(); + $request->configureRedirects(true); + $body = EntityBody::factory(fopen($this->getServer()->getUrl(), 'r')); + $body->read(1); + $request->setBody($body)->send(); + } + + public function testRedirectsCanBeDisabledPerRequest() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array("HTTP/1.1 301 Foo\r\nLocation: /foo\r\nContent-Length: 0\r\n\r\n")); + $client = new Client($this->getServer()->getUrl()); + $request = $client->put(); + $request->configureRedirects(false, 0); + $this->assertEquals(301, $request->send()->getStatusCode()); + } + + public function testCanRedirectWithNoLeadingSlashAndQuery() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: redirect?foo=bar\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('?foo=bar'); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals($this->getServer()->getUrl() . '?foo=bar', $requests[0]->getUrl()); + $this->assertEquals($this->getServer()->getUrl() . 'redirect?foo=bar', $requests[1]->getUrl()); + // Ensure that the history on the actual request is correct + $this->assertEquals($this->getServer()->getUrl() . '?foo=bar', $request->getUrl()); + } + + public function testRedirectWithStrictRfc386Compliance() + { + // Flush the server and queue up a redirect followed by a successful response + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: redirect\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/foo'); + $request->send(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('/redirect', $requests[1]->getResource()); + } + + public function testResetsHistoryEachSend() + { + // Flush the server and queue up a redirect followed by a successful response + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect2\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + + // Create a client that uses the default redirect behavior + $client = new Client($this->getServer()->getUrl()); + $history = new HistoryPlugin(); + $client->addSubscriber($history); + + $request = $client->get('/foo'); + $response = $request->send(); + $this->assertEquals(3, count($history)); + $this->assertTrue($request->getParams()->hasKey('redirect.count')); + $this->assertContains('/redirect2', $response->getEffectiveUrl()); + + $request->send(); + $this->assertFalse($request->getParams()->hasKey('redirect.count')); + } + + public function testHandlesRedirectsWithSpacesProperly() + { + // Flush the server and queue up a redirect followed by a successful response + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 301 Moved Permanently\r\nLocation: /redirect 1\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + )); + $client = new Client($this->getServer()->getUrl()); + $request = $client->get('/foo'); + $request->send(); + $reqs = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('/redirect%201', $reqs[1]->getResource()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php new file mode 100644 index 0000000..94eb59a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/Server.php @@ -0,0 +1,191 @@ +port = $port ?: self::DEFAULT_PORT; + $this->client = new Client($this->getUrl()); + register_shutdown_function(array($this, 'stop')); + } + + /** + * Flush the received requests from the server + * @throws RuntimeException + */ + public function flush() + { + $this->client->delete('guzzle-server/requests')->send(); + } + + /** + * Queue an array of responses or a single response on the server. + * + * Any currently queued responses will be overwritten. Subsequent requests + * on the server will return queued responses in FIFO order. + * + * @param array|Response $responses A single or array of Responses to queue + * @throws BadResponseException + */ + public function enqueue($responses) + { + $data = array(); + foreach ((array) $responses as $response) { + + // Create the response object from a string + if (is_string($response)) { + $response = Response::fromMessage($response); + } elseif (!($response instanceof Response)) { + throw new BadResponseException('Responses must be strings or implement Response'); + } + + $data[] = array( + 'statusCode' => $response->getStatusCode(), + 'reasonPhrase' => $response->getReasonPhrase(), + 'headers' => $response->getHeaders()->toArray(), + 'body' => $response->getBody(true) + ); + } + + $request = $this->client->put('guzzle-server/responses', null, json_encode($data)); + $request->send(); + } + + /** + * Check if the server is running + * + * @return bool + */ + public function isRunning() + { + if ($this->running) { + return true; + } + + try { + $this->client->get('guzzle-server/perf', array(), array('timeout' => 5))->send(); + $this->running = true; + return true; + } catch (\Exception $e) { + return false; + } + } + + /** + * Get the URL to the server + * + * @return string + */ + public function getUrl() + { + return 'http://127.0.0.1:' . $this->getPort() . '/'; + } + + /** + * Get the port that the server is listening on + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Get all of the received requests + * + * @param bool $hydrate Set to TRUE to turn the messages into + * actual {@see RequestInterface} objects. If $hydrate is FALSE, + * requests will be returned as strings. + * + * @return array + * @throws RuntimeException + */ + public function getReceivedRequests($hydrate = false) + { + $response = $this->client->get('guzzle-server/requests')->send(); + $data = array_filter(explode(self::REQUEST_DELIMITER, $response->getBody(true))); + if ($hydrate) { + $data = array_map(function($message) { + return RequestFactory::getInstance()->fromMessage($message); + }, $data); + } + + return $data; + } + + /** + * Start running the node.js server in the background + */ + public function start() + { + if (!$this->isRunning()) { + exec('node ' . __DIR__ . \DIRECTORY_SEPARATOR + . 'server.js ' . $this->port + . ' >> /tmp/server.log 2>&1 &'); + // Wait at most 5 seconds for the server the setup before + // proceeding. + $start = time(); + while (!$this->isRunning() && time() - $start < 5); + if (!$this->running) { + throw new RuntimeException( + 'Unable to contact server.js. Have you installed node.js v0.5.0+? node must be in your path.' + ); + } + } + } + + /** + * Stop running the node.js server + */ + public function stop() + { + if (!$this->isRunning()) { + return false; + } + + $this->running = false; + $this->client->delete('guzzle-server')->send(); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php new file mode 100644 index 0000000..091314b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/StaticClientTest.php @@ -0,0 +1,67 @@ +assertTrue(class_exists('FooBazBar')); + $this->assertSame($client, $this->readAttribute('Guzzle\Http\StaticClient', 'client')); + } + + public function requestProvider() + { + return array_map( + function ($m) { return array($m); }, + array('GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS') + ); + } + + /** + * @dataProvider requestProvider + */ + public function testSendsRequests($method) + { + $mock = new MockPlugin(array(new Response(200))); + call_user_func('Guzzle\Http\StaticClient::' . $method, 'http://foo.com', array( + 'plugins' => array($mock) + )); + $requests = $mock->getReceivedRequests(); + $this->assertCount(1, $requests); + $this->assertEquals($method, $requests[0]->getMethod()); + } + + public function testCanCreateStreamsUsingDefaultFactory() + { + $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest")); + $stream = StaticClient::get($this->getServer()->getUrl(), array('stream' => true)); + $this->assertInstanceOf('Guzzle\Stream\StreamInterface', $stream); + $this->assertEquals('test', (string) $stream); + } + + public function testCanCreateStreamsUsingCustomFactory() + { + $stream = $this->getMockBuilder('Guzzle\Stream\StreamRequestFactoryInterface') + ->setMethods(array('fromRequest')) + ->getMockForAbstractClass(); + $resource = new Stream(fopen('php://temp', 'r+')); + $stream->expects($this->once()) + ->method('fromRequest') + ->will($this->returnValue($resource)); + $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ntest")); + $result = StaticClient::get($this->getServer()->getUrl(), array('stream' => $stream)); + $this->assertSame($resource, $result); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php new file mode 100644 index 0000000..28f2671 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/UrlTest.php @@ -0,0 +1,303 @@ +assertEquals('', (string) $url); + } + + public function testPortIsDeterminedFromScheme() + { + $this->assertEquals(80, Url::factory('http://www.test.com/')->getPort()); + $this->assertEquals(443, Url::factory('https://www.test.com/')->getPort()); + $this->assertEquals(null, Url::factory('ftp://www.test.com/')->getPort()); + $this->assertEquals(8192, Url::factory('http://www.test.com:8192/')->getPort()); + } + + public function testCloneCreatesNewInternalObjects() + { + $u1 = Url::factory('http://www.test.com/'); + $u2 = clone $u1; + $this->assertNotSame($u1->getQuery(), $u2->getQuery()); + } + + public function testValidatesUrlPartsInFactory() + { + $url = Url::factory('/index.php'); + $this->assertEquals('/index.php', (string) $url); + $this->assertFalse($url->isAbsolute()); + + $url = 'http://michael:test@test.com:80/path/123?q=abc#test'; + $u = Url::factory($url); + $this->assertEquals('http://michael:test@test.com/path/123?q=abc#test', (string) $u); + $this->assertTrue($u->isAbsolute()); + } + + public function testAllowsFalsyUrlParts() + { + $url = Url::factory('http://0:50/0?0#0'); + $this->assertSame('0', $url->getHost()); + $this->assertEquals(50, $url->getPort()); + $this->assertSame('/0', $url->getPath()); + $this->assertEquals('0', (string) $url->getQuery()); + $this->assertSame('0', $url->getFragment()); + $this->assertEquals('http://0:50/0?0#0', (string) $url); + + $url = Url::factory(''); + $this->assertSame('', (string) $url); + + $url = Url::factory('0'); + $this->assertSame('0', (string) $url); + } + + public function testBuildsRelativeUrlsWithFalsyParts() + { + $url = Url::buildUrl(array( + 'host' => '0', + 'path' => '0', + )); + + $this->assertSame('//0/0', $url); + + $url = Url::buildUrl(array( + 'path' => '0', + )); + $this->assertSame('0', $url); + } + + public function testUrlStoresParts() + { + $url = Url::factory('http://test:pass@www.test.com:8081/path/path2/?a=1&b=2#fragment'); + $this->assertEquals('http', $url->getScheme()); + $this->assertEquals('test', $url->getUsername()); + $this->assertEquals('pass', $url->getPassword()); + $this->assertEquals('www.test.com', $url->getHost()); + $this->assertEquals(8081, $url->getPort()); + $this->assertEquals('/path/path2/', $url->getPath()); + $this->assertEquals('fragment', $url->getFragment()); + $this->assertEquals('a=1&b=2', (string) $url->getQuery()); + + $this->assertEquals(array( + 'fragment' => 'fragment', + 'host' => 'www.test.com', + 'pass' => 'pass', + 'path' => '/path/path2/', + 'port' => 8081, + 'query' => 'a=1&b=2', + 'scheme' => 'http', + 'user' => 'test' + ), $url->getParts()); + } + + public function testHandlesPathsCorrectly() + { + $url = Url::factory('http://www.test.com'); + $this->assertEquals('', $url->getPath()); + $url->setPath('test'); + $this->assertEquals('test', $url->getPath()); + + $url->setPath('/test/123/abc'); + $this->assertEquals(array('test', '123', 'abc'), $url->getPathSegments()); + + $parts = parse_url('http://www.test.com/test'); + $parts['path'] = ''; + $this->assertEquals('http://www.test.com', Url::buildUrl($parts)); + $parts['path'] = 'test'; + $this->assertEquals('http://www.test.com/test', Url::buildUrl($parts)); + } + + public function testAddsQueryStringIfPresent() + { + $this->assertEquals('?foo=bar', Url::buildUrl(array( + 'query' => 'foo=bar' + ))); + } + + public function testAddsToPath() + { + // Does nothing here + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(false)); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(null)); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(array())); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath(new \stdClass())); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('')); + $this->assertEquals('http://e.com/base?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('/')); + $this->assertEquals('http://e.com/baz/foo', (string) Url::factory('http://e.com/baz/')->addPath('foo')); + $this->assertEquals('http://e.com/base/relative?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('relative')); + $this->assertEquals('http://e.com/base/relative?a=1', (string) Url::factory('http://e.com/base?a=1')->addPath('/relative')); + $this->assertEquals('http://e.com/base/0', (string) Url::factory('http://e.com/base')->addPath('0')); + $this->assertEquals('http://e.com/base/0/1', (string) Url::factory('http://e.com/base')->addPath('0')->addPath('1')); + } + + /** + * URL combination data provider + * + * @return array + */ + public function urlCombineDataProvider() + { + return array( + array('http://www.example.com/', 'http://www.example.com/', 'http://www.example.com/'), + array('http://www.example.com/path', '/absolute', 'http://www.example.com/absolute'), + array('http://www.example.com/path', '/absolute?q=2', 'http://www.example.com/absolute?q=2'), + array('http://www.example.com/path', 'more', 'http://www.example.com/path/more'), + array('http://www.example.com/path', 'more?q=1', 'http://www.example.com/path/more?q=1'), + array('http://www.example.com/', '?q=1', 'http://www.example.com/?q=1'), + array('http://www.example.com/path', 'http://test.com', 'http://test.com'), + array('http://www.example.com:8080/path', 'http://test.com', 'http://test.com'), + array('http://www.example.com:8080/path', '?q=2#abc', 'http://www.example.com:8080/path?q=2#abc'), + array('http://u:a@www.example.com/path', 'test', 'http://u:a@www.example.com/path/test'), + array('http://www.example.com/path', 'http://u:a@www.example.com/', 'http://u:a@www.example.com/'), + array('/path?q=2', 'http://www.test.com/', 'http://www.test.com/path?q=2'), + array('http://api.flickr.com/services/', 'http://www.flickr.com/services/oauth/access_token', 'http://www.flickr.com/services/oauth/access_token'), + array('http://www.example.com/?foo=bar', 'some/path', 'http://www.example.com/some/path?foo=bar'), + array('http://www.example.com/?foo=bar', 'some/path?boo=moo', 'http://www.example.com/some/path?boo=moo&foo=bar'), + array('http://www.example.com/some/', 'path?foo=bar&foo=baz', 'http://www.example.com/some/path?foo=bar&foo=baz'), + ); + } + + /** + * @dataProvider urlCombineDataProvider + */ + public function testCombinesUrls($a, $b, $c) + { + $this->assertEquals($c, (string) Url::factory($a)->combine($b)); + } + + public function testHasGettersAndSetters() + { + $url = Url::factory('http://www.test.com/'); + $this->assertEquals('example.com', $url->setHost('example.com')->getHost()); + $this->assertEquals('8080', $url->setPort(8080)->getPort()); + $this->assertEquals('/foo/bar', $url->setPath(array('foo', 'bar'))->getPath()); + $this->assertEquals('a', $url->setPassword('a')->getPassword()); + $this->assertEquals('b', $url->setUsername('b')->getUsername()); + $this->assertEquals('abc', $url->setFragment('abc')->getFragment()); + $this->assertEquals('https', $url->setScheme('https')->getScheme()); + $this->assertEquals('a=123', (string) $url->setQuery('a=123')->getQuery()); + $this->assertEquals('https://b:a@example.com:8080/foo/bar?a=123#abc', (string) $url); + $this->assertEquals('b=boo', (string) $url->setQuery(new QueryString(array( + 'b' => 'boo' + )))->getQuery()); + $this->assertEquals('https://b:a@example.com:8080/foo/bar?b=boo#abc', (string) $url); + } + + public function testSetQueryAcceptsArray() + { + $url = Url::factory('http://www.test.com'); + $url->setQuery(array('a' => 'b')); + $this->assertEquals('http://www.test.com?a=b', (string) $url); + } + + public function urlProvider() + { + return array( + array('/foo/..', '/'), + array('//foo//..', '/'), + array('/foo/../..', '/'), + array('/foo/../.', '/'), + array('/./foo/..', '/'), + array('/./foo', '/foo'), + array('/./foo/', '/foo/'), + array('/./foo/bar/baz/pho/../..', '/foo/bar'), + array('*', '*'), + array('/foo', '/foo'), + array('/abc/123/../foo/', '/abc/foo/'), + array('/a/b/c/./../../g', '/a/g'), + array('/b/c/./../../g', '/g'), + array('/b/c/./../../g', '/g'), + array('/c/./../../g', '/g'), + array('/./../../g', '/g'), + ); + } + + /** + * @dataProvider urlProvider + */ + public function testNormalizesPaths($path, $result) + { + $url = Url::factory('http://www.example.com/'); + $url->setPath($path)->normalizePath(); + $this->assertEquals($result, $url->getPath()); + } + + public function testSettingHostWithPortModifiesPort() + { + $url = Url::factory('http://www.example.com'); + $url->setHost('foo:8983'); + $this->assertEquals('foo', $url->getHost()); + $this->assertEquals(8983, $url->getPort()); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesUrlCanBeParsed() + { + Url::factory('foo:////'); + } + + public function testConvertsSpecialCharsInPathWhenCastingToString() + { + $url = Url::factory('http://foo.com/baz bar?a=b'); + $url->addPath('?'); + $this->assertEquals('http://foo.com/baz%20bar/%3F?a=b', (string) $url); + } + + /** + * @link http://tools.ietf.org/html/rfc3986#section-5.4.1 + */ + public function rfc3986UrlProvider() + { + $result = array( + array('g', 'http://a/b/c/g'), + array('./g', 'http://a/b/c/g'), + array('g/', 'http://a/b/c/g/'), + array('/g', 'http://a/g'), + array('?y', 'http://a/b/c/d;p?y'), + array('g?y', 'http://a/b/c/g?y'), + array('#s', 'http://a/b/c/d;p?q#s'), + array('g#s', 'http://a/b/c/g#s'), + array('g?y#s', 'http://a/b/c/g?y#s'), + array(';x', 'http://a/b/c/;x'), + array('g;x', 'http://a/b/c/g;x'), + array('g;x?y#s', 'http://a/b/c/g;x?y#s'), + array('', 'http://a/b/c/d;p?q'), + array('.', 'http://a/b/c'), + array('./', 'http://a/b/c/'), + array('..', 'http://a/b'), + array('../', 'http://a/b/'), + array('../g', 'http://a/b/g'), + array('../..', 'http://a/'), + array('../../', 'http://a/'), + array('../../g', 'http://a/g') + ); + + // This support was added in PHP 5.4.7: https://bugs.php.net/bug.php?id=62844 + if (version_compare(PHP_VERSION, '5.4.7', '>=')) { + $result[] = array('//g', 'http://g'); + } + + return $result; + } + + /** + * @dataProvider rfc3986UrlProvider + */ + public function testCombinesUrlsUsingRfc3986($relative, $result) + { + $a = Url::factory('http://a/b/c/d;p?q'); + $b = Url::factory($relative); + $this->assertEquals($result, trim((string) $a->combine($b, true), '=')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js new file mode 100644 index 0000000..4156f1a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Http/server.js @@ -0,0 +1,146 @@ +/** + * Guzzle node.js test server to return queued responses to HTTP requests and + * expose a RESTful API for enqueueing responses and retrieving the requests + * that have been received. + * + * - Delete all requests that have been received: + * DELETE /guzzle-server/requests + * Host: 127.0.0.1:8124 + * + * - Enqueue responses + * PUT /guzzle-server/responses + * Host: 127.0.0.1:8124 + * + * [{ "statusCode": 200, "reasonPhrase": "OK", "headers": {}, "body": "" }] + * + * - Get the received requests + * GET /guzzle-server/requests + * Host: 127.0.0.1:8124 + * + * - Shutdown the server + * DELETE /guzzle-server + * Host: 127.0.0.1:8124 + * + * @package Guzzle PHP + * @license See the LICENSE file that was distributed with this source code. + */ + +var http = require("http"); + +/** + * Guzzle node.js server + * @class + */ +var GuzzleServer = function(port, log) { + + this.port = port; + this.log = log; + this.responses = []; + this.requests = []; + var that = this; + + var controlRequest = function(request, req, res) { + if (req.url == '/guzzle-server/perf') { + res.writeHead(200, "OK", {"Content-Length": 16}); + res.end("Body of response"); + } else if (req.method == "DELETE") { + if (req.url == "/guzzle-server/requests") { + // Clear the received requests + that.requests = []; + res.writeHead(200, "OK", { "Content-Length": 0 }); + res.end(); + if (this.log) { + console.log("Flushing requests"); + } + } else if (req.url == "/guzzle-server") { + // Shutdown the server + res.writeHead(200, "OK", { "Content-Length": 0, "Connection": "close" }); + res.end(); + if (this.log) { + console.log("Shutting down"); + } + that.server.close(); + } + } else if (req.method == "GET") { + if (req.url === "/guzzle-server/requests") { + // Get received requests + var data = that.requests.join("\n----[request]\n"); + res.writeHead(200, "OK", { "Content-Length": data.length }); + res.end(data); + if (that.log) { + console.log("Sending receiving requests"); + } + } + } else if (req.method == "PUT") { + if (req.url == "/guzzle-server/responses") { + if (that.log) { + console.log("Adding responses..."); + } + // Received response to queue + var data = request.split("\r\n\r\n")[1]; + if (!data) { + if (that.log) { + console.log("No response data was provided"); + } + res.writeHead(400, "NO RESPONSES IN REQUEST", { "Content-Length": 0 }); + } else { + that.responses = eval("(" + data + ")"); + if (that.log) { + console.log(that.responses); + } + res.writeHead(200, "OK", { "Content-Length": 0 }); + } + res.end(); + } + } + }; + + var receivedRequest = function(request, req, res) { + if (req.url.indexOf("/guzzle-server") === 0) { + controlRequest(request, req, res); + } else if (req.url.indexOf("/guzzle-server") == -1 && !that.responses.length) { + res.writeHead(500); + res.end("No responses in queue"); + } else { + var response = that.responses.shift(); + res.writeHead(response.statusCode, response.reasonPhrase, response.headers); + res.end(response.body); + that.requests.push(request); + } + }; + + this.start = function() { + + that.server = http.createServer(function(req, res) { + + var request = req.method + " " + req.url + " HTTP/" + req.httpVersion + "\r\n"; + for (var i in req.headers) { + request += i + ": " + req.headers[i] + "\r\n"; + } + request += "\r\n"; + + // Receive each chunk of the request body + req.addListener("data", function(chunk) { + request += chunk; + }); + + // Called when the request completes + req.addListener("end", function() { + receivedRequest(request, req, res); + }); + }); + that.server.listen(port, "127.0.0.1"); + + if (this.log) { + console.log("Server running at http://127.0.0.1:8124/"); + } + }; +}; + +// Get the port from the arguments +port = process.argv.length >= 3 ? process.argv[2] : 8124; +log = process.argv.length >= 4 ? process.argv[3] : false; + +// Start the server +server = new GuzzleServer(port, log); +server.start(); diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php new file mode 100644 index 0000000..990c0af --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/InflectorTest.php @@ -0,0 +1,37 @@ +assertSame(Inflector::getDefault(), Inflector::getDefault()); + } + + public function testSnake() + { + $this->assertEquals('camel_case', Inflector::getDefault()->snake('camelCase')); + $this->assertEquals('camel_case', Inflector::getDefault()->snake('CamelCase')); + $this->assertEquals('camel_case_words', Inflector::getDefault()->snake('CamelCaseWords')); + $this->assertEquals('camel_case_words', Inflector::getDefault()->snake('CamelCase_words')); + $this->assertEquals('test', Inflector::getDefault()->snake('test')); + $this->assertEquals('test', Inflector::getDefault()->snake('test')); + $this->assertEquals('expect100_continue', Inflector::getDefault()->snake('Expect100Continue')); + } + + public function testCamel() + { + $this->assertEquals('CamelCase', Inflector::getDefault()->camel('camel_case')); + $this->assertEquals('CamelCaseWords', Inflector::getDefault()->camel('camel_case_words')); + $this->assertEquals('Test', Inflector::getDefault()->camel('test')); + $this->assertEquals('Expect100Continue', ucfirst(Inflector::getDefault()->camel('expect100_continue'))); + // Get from cache + $this->assertEquals('Test', Inflector::getDefault()->camel('test', false)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php new file mode 100644 index 0000000..f00b7fa --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/MemoizingInflectorTest.php @@ -0,0 +1,46 @@ +getMock('Guzzle\Inflection\Inflector', array('snake', 'camel')); + $mock->expects($this->once())->method('snake')->will($this->returnValue('foo_bar')); + $mock->expects($this->once())->method('camel')->will($this->returnValue('FooBar')); + + $inflector = new MemoizingInflector($mock); + $this->assertEquals('foo_bar', $inflector->snake('FooBar')); + $this->assertEquals('foo_bar', $inflector->snake('FooBar')); + $this->assertEquals('FooBar', $inflector->camel('foo_bar')); + $this->assertEquals('FooBar', $inflector->camel('foo_bar')); + } + + public function testProtectsAgainstCacheOverflow() + { + $inflector = new MemoizingInflector(new Inflector(), 10); + for ($i = 1; $i < 11; $i++) { + $inflector->camel('foo_' . $i); + $inflector->snake('Foo' . $i); + } + + $cache = $this->readAttribute($inflector, 'cache'); + $this->assertEquals(10, count($cache['snake'])); + $this->assertEquals(10, count($cache['camel'])); + + $inflector->camel('baz!'); + $inflector->snake('baz!'); + + // Now ensure that 20% of the cache was removed (2), then the item was added + $cache = $this->readAttribute($inflector, 'cache'); + $this->assertEquals(9, count($cache['snake'])); + $this->assertEquals(9, count($cache['camel'])); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php new file mode 100644 index 0000000..ff2654c --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Inflection/PreComputedInflectorTest.php @@ -0,0 +1,45 @@ +getMock('Guzzle\Inflection\Inflector', array('snake', 'camel')); + $mock->expects($this->once())->method('snake')->with('Test')->will($this->returnValue('test')); + $mock->expects($this->once())->method('camel')->with('Test')->will($this->returnValue('Test')); + $inflector = new PreComputedInflector($mock, array('FooBar' => 'foo_bar'), array('foo_bar' => 'FooBar')); + $this->assertEquals('FooBar', $inflector->camel('foo_bar')); + $this->assertEquals('foo_bar', $inflector->snake('FooBar')); + $this->assertEquals('Test', $inflector->camel('Test')); + $this->assertEquals('test', $inflector->snake('Test')); + } + + public function testMirrorsPrecomputedValues() + { + $mock = $this->getMock('Guzzle\Inflection\Inflector', array('snake', 'camel')); + $mock->expects($this->never())->method('snake'); + $mock->expects($this->never())->method('camel'); + $inflector = new PreComputedInflector($mock, array('Zeep' => 'zeep'), array(), true); + $this->assertEquals('Zeep', $inflector->camel('zeep')); + $this->assertEquals('zeep', $inflector->snake('Zeep')); + } + + public function testMirrorsPrecomputedValuesByMerging() + { + $mock = $this->getMock('Guzzle\Inflection\Inflector', array('snake', 'camel')); + $mock->expects($this->never())->method('snake'); + $mock->expects($this->never())->method('camel'); + $inflector = new PreComputedInflector($mock, array('Zeep' => 'zeep'), array('foo' => 'Foo'), true); + $this->assertEquals('Zeep', $inflector->camel('zeep')); + $this->assertEquals('zeep', $inflector->snake('Zeep')); + $this->assertEquals('Foo', $inflector->camel('foo')); + $this->assertEquals('foo', $inflector->snake('Foo')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php new file mode 100644 index 0000000..8d6ae84 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/AppendIteratorTest.php @@ -0,0 +1,29 @@ + 1, + 'b' => 2 + )); + $b = new \ArrayIterator(array()); + $c = new \ArrayIterator(array( + 'c' => 3, + 'd' => 4 + )); + $i = new AppendIterator(); + $i->append($a); + $i->append($b); + $i->append($c); + $this->assertEquals(array(1, 2, 3, 4), iterator_to_array($i, false)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php new file mode 100644 index 0000000..ec4c129 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/ChunkedIteratorTest.php @@ -0,0 +1,52 @@ +assertEquals(11, count($chunks)); + foreach ($chunks as $j => $chunk) { + $this->assertEquals(range($j * 10, min(100, $j * 10 + 9)), $chunk); + } + } + + public function testChunksIteratorWithOddValues() + { + $chunked = new ChunkedIterator(new \ArrayIterator(array(1, 2, 3, 4, 5)), 2); + $chunks = iterator_to_array($chunked, false); + $this->assertEquals(3, count($chunks)); + $this->assertEquals(array(1, 2), $chunks[0]); + $this->assertEquals(array(3, 4), $chunks[1]); + $this->assertEquals(array(5), $chunks[2]); + } + + public function testMustNotTerminateWithTraversable() + { + $traversable = simplexml_load_string('')->foo; + $chunked = new ChunkedIterator($traversable, 2); + $actual = iterator_to_array($chunked, false); + $this->assertCount(2, $actual); + } + + public function testSizeOfZeroMakesIteratorInvalid() { + $chunked = new ChunkedIterator(new \ArrayIterator(range(1, 5)), 0); + $chunked->rewind(); + $this->assertFalse($chunked->valid()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSizeLowerZeroThrowsException() { + new ChunkedIterator(new \ArrayIterator(range(1, 5)), -1); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php new file mode 100644 index 0000000..73b4f69 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/FilterIteratorTest.php @@ -0,0 +1,28 @@ +assertEquals(range(1, 99, 2), iterator_to_array($i, false)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesCallable() + { + $i = new FilterIterator(new \ArrayIterator(), new \stdClass()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php new file mode 100644 index 0000000..4de4a6b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MapIteratorTest.php @@ -0,0 +1,28 @@ +assertEquals(range(0, 1000, 10), iterator_to_array($i, false)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesCallable() + { + $i = new MapIterator(new \ArrayIterator(), new \stdClass()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php new file mode 100644 index 0000000..5bcf06f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Iterator/MethodProxyIteratorTest.php @@ -0,0 +1,28 @@ +append('a'); + $proxy->append('b'); + $this->assertEquals(array('a', 'b'), $i->getArrayCopy()); + $this->assertEquals(array('a', 'b'), $proxy->getArrayCopy()); + } + + public function testUsesInnerIterator() + { + $i = new MethodProxyIterator(new ChunkedIterator(new \ArrayIterator(array(1, 2, 3, 4, 5)), 2)); + $this->assertEquals(3, count(iterator_to_array($i, false))); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php new file mode 100644 index 0000000..a66882f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ArrayLogAdapterTest.php @@ -0,0 +1,23 @@ +log('test', \LOG_NOTICE, '127.0.0.1'); + $this->assertEquals(array(array('message' => 'test', 'priority' => \LOG_NOTICE, 'extras' => '127.0.0.1')), $adapter->getLogs()); + } + + public function testClearLog() + { + $adapter = new ArrayLogAdapter(); + $adapter->log('test', \LOG_NOTICE, '127.0.0.1'); + $adapter->clearLogs(); + $this->assertEquals(array(), $adapter->getLogs()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php new file mode 100644 index 0000000..0177dc0 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/ClosureLogAdapterTest.php @@ -0,0 +1,30 @@ +adapter = new ClosureLogAdapter(function($message, $priority, $extras = null) use ($that, &$modified) { + $modified = array($message, $priority, $extras); + }); + $this->adapter->log('test', LOG_NOTICE, '127.0.0.1'); + $this->assertEquals(array('test', LOG_NOTICE, '127.0.0.1'), $modified); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testThrowsExceptionWhenNotCallable() + { + $this->adapter = new ClosureLogAdapter(123); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php new file mode 100644 index 0000000..3ff4b07 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/MessageFormatterTest.php @@ -0,0 +1,143 @@ +request = new EntityEnclosingRequest('POST', 'http://foo.com?q=test', array( + 'X-Foo' => 'bar', + 'Authorization' => 'Baz' + )); + $this->request->setBody(EntityBody::factory('Hello')); + + $this->response = new Response(200, array( + 'X-Test' => 'Abc' + ), 'Foo'); + + $this->handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle') + ->disableOriginalConstructor() + ->setMethods(array('getError', 'getErrorNo', 'getStderr', 'getInfo')) + ->getMock(); + + $this->handle->expects($this->any()) + ->method('getError') + ->will($this->returnValue('e')); + + $this->handle->expects($this->any()) + ->method('getErrorNo') + ->will($this->returnValue('123')); + + $this->handle->expects($this->any()) + ->method('getStderr') + ->will($this->returnValue('testing')); + + $this->handle->expects($this->any()) + ->method('getInfo') + ->will($this->returnValueMap(array( + array(CURLINFO_CONNECT_TIME, '123'), + array(CURLINFO_TOTAL_TIME, '456') + ))); + } + + public function logProvider() + { + return array( + // Uses the cache for the second time + array('{method} - {method}', 'POST - POST'), + array('{url}', 'http://foo.com?q=test'), + array('{port}', '80'), + array('{resource}', '/?q=test'), + array('{host}', 'foo.com'), + array('{hostname}', gethostname()), + array('{protocol}/{version}', 'HTTP/1.1'), + array('{code} {phrase}', '200 OK'), + array('{req_header_Foo}', ''), + array('{req_header_X-Foo}', 'bar'), + array('{req_header_Authorization}', 'Baz'), + array('{res_header_foo}', ''), + array('{res_header_X-Test}', 'Abc'), + array('{req_body}', 'Hello'), + array('{res_body}', 'Foo'), + array('{curl_stderr}', 'testing'), + array('{curl_error}', 'e'), + array('{curl_code}', '123'), + array('{connect_time}', '123'), + array('{total_time}', '456') + ); + } + + /** + * @dataProvider logProvider + */ + public function testFormatsMessages($template, $output) + { + $formatter = new MessageFormatter($template); + $this->assertEquals($output, $formatter->format($this->request, $this->response, $this->handle)); + } + + public function testFormatsRequestsAndResponses() + { + $formatter = new MessageFormatter(); + $formatter->setTemplate('{request}{response}'); + $this->assertEquals($this->request . $this->response, $formatter->format($this->request, $this->response)); + } + + public function testAddsTimestamp() + { + $formatter = new MessageFormatter('{ts}'); + $this->assertNotEmpty($formatter->format($this->request, $this->response)); + } + + public function testUsesResponseWhenNoHandleAndGettingCurlInformation() + { + $formatter = new MessageFormatter('{connect_time}/{total_time}'); + $response = $this->getMockBuilder('Guzzle\Http\Message\Response') + ->setConstructorArgs(array(200)) + ->setMethods(array('getInfo')) + ->getMock(); + $response->expects($this->exactly(2)) + ->method('getInfo') + ->will($this->returnValueMap(array( + array('connect_time', '1'), + array('total_time', '2'), + ))); + $this->assertEquals('1/2', $formatter->format($this->request, $response)); + } + + public function testUsesEmptyStringWhenNoHandleAndNoResponse() + { + $formatter = new MessageFormatter('{connect_time}/{total_time}'); + $this->assertEquals('/', $formatter->format($this->request)); + } + + public function testInjectsTotalTime() + { + $out = ''; + $formatter = new MessageFormatter('{connect_time}/{total_time}'); + $adapter = new ClosureLogAdapter(function ($m) use (&$out) { $out .= $m; }); + $log = new LogPlugin($adapter, $formatter); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHI"); + $client = new Client($this->getServer()->getUrl()); + $client->addSubscriber($log); + $client->get('/')->send(); + $this->assertNotEquals('/', $out); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php new file mode 100644 index 0000000..7b72dd6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/PsrLogAdapterTest.php @@ -0,0 +1,25 @@ +pushHandler($handler); + $adapter = new PsrLogAdapter($log); + $adapter->log('test!', LOG_INFO); + $this->assertTrue($handler->hasInfoRecords()); + $this->assertSame($log, $adapter->getLogObject()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php new file mode 100644 index 0000000..1b61283 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Log/Zf2LogAdapterTest.php @@ -0,0 +1,51 @@ +stream = fopen('php://temp', 'r+'); + $this->log = new Logger(); + $this->log->addWriter(new Stream($this->stream)); + $this->adapter = new Zf2LogAdapter($this->log); + + } + + public function testLogsMessagesToAdaptedObject() + { + // Test without a priority + $this->adapter->log('Zend_Test!', \LOG_NOTICE); + rewind($this->stream); + $contents = stream_get_contents($this->stream); + $this->assertEquals(1, substr_count($contents, 'Zend_Test!')); + + // Test with a priority + $this->adapter->log('Zend_Test!', \LOG_ALERT); + rewind($this->stream); + $contents = stream_get_contents($this->stream); + $this->assertEquals(2, substr_count($contents, 'Zend_Test!')); + } + + public function testExposesAdaptedLogObject() + { + $this->assertEquals($this->log, $this->adapter->getLogObject()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php new file mode 100644 index 0000000..3fb6527 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/CustomResponseModel.php @@ -0,0 +1,21 @@ +command = $command; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php new file mode 100644 index 0000000..aabb15f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ErrorResponseMock.php @@ -0,0 +1,25 @@ +command = $command; + $this->response = $response; + $this->message = 'Error from ' . $response; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php new file mode 100644 index 0000000..97a1974 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/ExceptionMock.php @@ -0,0 +1,11 @@ +multiHandle; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php new file mode 100644 index 0000000..11e22eb --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockObserver.php @@ -0,0 +1,65 @@ +events as $event) { + if ($event->getName() == $eventName) { + return true; + } + } + + return false; + } + + public function getLastEvent() + { + return end($this->events); + } + + public function count() + { + return count($this->events); + } + + public function getGrouped() + { + $events = array(); + foreach ($this->events as $event) { + if (!isset($events[$event->getName()])) { + $events[$event->getName()] = array(); + } + $events[$event->getName()][] = $event; + } + + return $events; + } + + public function getData($event, $key, $occurrence = 0) + { + $grouped = $this->getGrouped(); + if (isset($grouped[$event])) { + return $grouped[$event][$occurrence][$key]; + } + + return null; + } + + public function update(Event $event) + { + $this->events[] = $event; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php new file mode 100644 index 0000000..e011959 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Mock/MockSubject.php @@ -0,0 +1,7 @@ + 'allseeing-i.com', + 'path' => '/', + 'data' => array( + 'PHPSESSID' => '6c951590e7a9359bcedde25cda73e43c' + ), + 'max_age' => NULL, + 'expires' => 'Sat, 26-Jul-2008 17:00:42 GMT', + 'version' => NULL, + 'secure' => NULL, + 'discard' => NULL, + 'port' => NULL, + 'cookies' => array( + 'ASIHTTPRequestTestCookie' => 'This+is+the+value' + ), + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array('', false), + array('foo', false), + // Test setting a blank value for a cookie + array(array( + 'foo=', 'foo =', 'foo =;', 'foo= ;', 'foo =', 'foo= '), + array( + 'cookies' => array( + 'foo' => '' + ), + 'data' => array(), + 'discard' => null, + 'domain' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + // Test setting a value and removing quotes + array(array( + 'foo=1', 'foo =1', 'foo =1;', 'foo=1 ;', 'foo =1', 'foo= 1', 'foo = 1 ;', 'foo="1"', 'foo="1";', 'foo= "1";'), + array( + 'cookies' => array( + 'foo' => '1' + ), + 'data' => array(), + 'discard' => null, + 'domain' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + // Test setting multiple values + array(array( + 'foo=1; bar=2;', 'foo =1; bar = "2"', 'foo=1; bar=2'), + array( + 'cookies' => array( + 'foo' => '1', + 'bar' => '2', + ), + 'data' => array(), + 'discard' => null, + 'domain' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + // Tests getting the domain and path from a reference request + array(array( + 'foo=1; port="80,8081"; httponly', 'foo=1; port="80,8081"; domain=www.test.com; HttpOnly;', 'foo=1; ; domain=www.test.com; path=/path; port="80,8081"; HttpOnly;'), + array( + 'cookies' => array( + 'foo' => 1 + ), + 'data' => array(), + 'discard' => null, + 'domain' => 'www.test.com', + 'expires' => null, + 'max_age' => null, + 'path' => '/path', + 'port' => array('80', '8081'), + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => true + ), + 'http://www.test.com/path/' + ), + // Some of the following tests are based on http://framework.zend.com/svn/framework/standard/trunk/tests/Zend/Http/CookieTest.php + array( + 'justacookie=foo; domain=example.com', + array( + 'cookies' => array( + 'justacookie' => 'foo' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'expires=tomorrow; secure; path=/Space Out/; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=.example.com', + array( + 'cookies' => array( + 'expires' => 'tomorrow' + ), + 'domain' => '.example.com', + 'path' => '/Space Out/', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'data' => array(), + 'discard' => null, + 'port' => null, + 'secure' => true, + 'version' => null, + 'max_age' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'domain=unittests; expires=Tue, 21-Nov-2006 08:33:44 GMT; domain=example.com; path=/some value/', + array( + 'cookies' => array( + 'domain' => 'unittests' + ), + 'domain' => 'example.com', + 'path' => '/some value/', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'secure' => false, + 'data' => array(), + 'discard' => null, + 'max_age' => null, + 'port' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'path=indexAction; path=/; domain=.foo.com; expires=Tue, 21-Nov-2006 08:33:44 GMT', + array( + 'cookies' => array( + 'path' => 'indexAction' + ), + 'domain' => '.foo.com', + 'path' => '/', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'secure' => false, + 'data' => array(), + 'discard' => null, + 'max_age' => null, + 'port' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'secure=sha1; secure; SECURE; domain=some.really.deep.domain.com; version=1; Max-Age=86400', + array( + 'cookies' => array( + 'secure' => 'sha1' + ), + 'domain' => 'some.really.deep.domain.com', + 'path' => '/', + 'secure' => true, + 'data' => array(), + 'discard' => null, + 'expires' => time() + 86400, + 'max_age' => 86400, + 'port' => null, + 'version' => 1, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + array( + 'PHPSESSID=123456789+abcd%2Cef; secure; discard; domain=.localdomain; path=/foo/baz; expires=Tue, 21-Nov-2006 08:33:44 GMT;', + array( + 'cookies' => array( + 'PHPSESSID' => '123456789+abcd%2Cef' + ), + 'domain' => '.localdomain', + 'path' => '/foo/baz', + 'expires' => 'Tue, 21-Nov-2006 08:33:44 GMT', + 'secure' => true, + 'data' => array(), + 'discard' => true, + 'max_age' => null, + 'port' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ) + ), + // rfc6265#section-5.1.4 + array( + 'cookie=value', + array( + 'cookies' => array( + 'cookie' => 'value' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/some/path', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ), + 'http://example.com/some/path/test.html' + ), + array( + 'empty=path', + array( + 'cookies' => array( + 'empty' => 'path' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ), + 'http://example.com/test.html' + ), + array( + 'baz=qux', + array( + 'cookies' => array( + 'baz' => 'qux' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ), + 'http://example.com?query=here' + ), + array( + 'test=noSlashPath; path=someString', + array( + 'cookies' => array( + 'test' => 'noSlashPath' + ), + 'domain' => 'example.com', + 'data' => array(), + 'discard' => null, + 'expires' => null, + 'max_age' => null, + 'path' => '/real/path', + 'port' => null, + 'secure' => null, + 'version' => null, + 'comment' => null, + 'comment_url' => null, + 'http_only' => false + ), + 'http://example.com/real/path/' + ), + ); + } + + /** + * @dataProvider cookieParserDataProvider + */ + public function testParseCookie($cookie, $parsed, $url = null) + { + $c = $this->cookieParserClass; + $parser = new $c(); + + $request = null; + if ($url) { + $url = Url::factory($url); + $host = $url->getHost(); + $path = $url->getPath(); + } else { + $host = ''; + $path = ''; + } + + foreach ((array) $cookie as $c) { + $p = $parser->parseCookie($c, $host, $path); + + // Remove expires values from the assertion if they are relatively equal by allowing a 5 minute difference + if ($p['expires'] != $parsed['expires']) { + if (abs($p['expires'] - $parsed['expires']) < 300) { + unset($p['expires']); + unset($parsed['expires']); + } + } + + if (is_array($parsed)) { + foreach ($parsed as $key => $value) { + $this->assertEquals($parsed[$key], $p[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true)); + } + + foreach ($p as $key => $value) { + $this->assertEquals($p[$key], $parsed[$key], 'Comparing ' . $key . ' ' . var_export($value, true) . ' : ' . var_export($parsed, true) . ' | ' . var_export($p, true)); + } + } else { + $this->assertEquals($parsed, $p); + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php new file mode 100644 index 0000000..75d336f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Cookie/CookieParserTest.php @@ -0,0 +1,22 @@ +parseCookie('foo=baz+bar', null, null, true); + $this->assertEquals(array( + 'foo' => 'baz bar' + ), $result['cookies']); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php new file mode 100644 index 0000000..da58bb4 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserProvider.php @@ -0,0 +1,225 @@ + 'GET', + 'protocol' => 'HTTP', + 'version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => '', + 'port' => '', + 'path' => '/', + 'query' => '' + ), + 'headers' => array(), + 'body' => '' + )), + // Path and query string, multiple header values per header and case sensitive storage + array("HEAD /path?query=foo HTTP/1.0\r\nHost: example.com\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\nX-Foo: Baz\r\n\r\n", array( + 'method' => 'HEAD', + 'protocol' => 'HTTP', + 'version' => '1.0', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '', + 'path' => '/path', + 'query' => 'query=foo' + ), + 'headers' => array( + 'Host' => 'example.com', + 'X-Foo' => array('foo', 'foo', 'Baz'), + 'x-foo' => 'Bar' + ), + 'body' => '' + )), + // Includes a body + array("PUT / HTTP/1.0\r\nhost: example.com:443\r\nContent-Length: 4\r\n\r\ntest", array( + 'method' => 'PUT', + 'protocol' => 'HTTP', + 'version' => '1.0', + 'request_url' => array( + 'scheme' => 'https', + 'host' => 'example.com', + 'port' => '443', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'host' => 'example.com:443', + 'Content-Length' => '4' + ), + 'body' => 'test' + )), + // Includes Authorization headers + array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nAuthorization: Basic {$auth}\r\n\r\n", array( + 'method' => 'GET', + 'protocol' => 'HTTP', + 'version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '8080', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'Host' => 'example.com:8080', + 'Authorization' => "Basic {$auth}" + ), + 'body' => '' + )), + // Include authorization header + array("GET / HTTP/1.1\r\nHost: example.com:8080\r\nauthorization: Basic {$auth}\r\n\r\n", array( + 'method' => 'GET', + 'protocol' => 'HTTP', + 'version' => '1.1', + 'request_url' => array( + 'scheme' => 'http', + 'host' => 'example.com', + 'port' => '8080', + 'path' => '/', + 'query' => '' + ), + 'headers' => array( + 'Host' => 'example.com:8080', + 'authorization' => "Basic {$auth}" + ), + 'body' => '' + )), + ); + } + + public function responseProvider() + { + return array( + // Empty request + array('', false), + + array("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", array( + 'protocol' => 'HTTP', + 'version' => '1.1', + 'code' => '200', + 'reason_phrase' => 'OK', + 'headers' => array( + 'Content-Length' => 0 + ), + 'body' => '' + )), + array("HTTP/1.0 400 Bad Request\r\nContent-Length: 0\r\n\r\n", array( + 'protocol' => 'HTTP', + 'version' => '1.0', + 'code' => '400', + 'reason_phrase' => 'Bad Request', + 'headers' => array( + 'Content-Length' => 0 + ), + 'body' => '' + )), + array("HTTP/1.0 100 Continue\r\n\r\n", array( + 'protocol' => 'HTTP', + 'version' => '1.0', + 'code' => '100', + 'reason_phrase' => 'Continue', + 'headers' => array(), + 'body' => '' + )), + array("HTTP/1.1 204 No Content\r\nX-Foo: foo\r\nx-foo: Bar\r\nX-Foo: foo\r\n\r\n", array( + 'protocol' => 'HTTP', + 'version' => '1.1', + 'code' => '204', + 'reason_phrase' => 'No Content', + 'headers' => array( + 'X-Foo' => array('foo', 'foo'), + 'x-foo' => 'Bar' + ), + 'body' => '' + )), + array("HTTP/1.1 200 Ok that is great!\r\nContent-Length: 4\r\n\r\nTest", array( + 'protocol' => 'HTTP', + 'version' => '1.1', + 'code' => '200', + 'reason_phrase' => 'Ok that is great!', + 'headers' => array( + 'Content-Length' => 4 + ), + 'body' => 'Test' + )), + ); + } + + public function compareRequestResults($result, $expected) + { + if (!$result) { + $this->assertFalse($expected); + return; + } + + $this->assertEquals($result['method'], $expected['method']); + $this->assertEquals($result['protocol'], $expected['protocol']); + $this->assertEquals($result['version'], $expected['version']); + $this->assertEquals($result['request_url'], $expected['request_url']); + $this->assertEquals($result['body'], $expected['body']); + $this->compareHttpHeaders($result['headers'], $expected['headers']); + } + + public function compareResponseResults($result, $expected) + { + if (!$result) { + $this->assertFalse($expected); + return; + } + + $this->assertEquals($result['protocol'], $expected['protocol']); + $this->assertEquals($result['version'], $expected['version']); + $this->assertEquals($result['code'], $expected['code']); + $this->assertEquals($result['reason_phrase'], $expected['reason_phrase']); + $this->assertEquals($result['body'], $expected['body']); + $this->compareHttpHeaders($result['headers'], $expected['headers']); + } + + protected function normalizeHeaders($headers) + { + $normalized = array(); + foreach ($headers as $key => $value) { + $key = strtolower($key); + if (!isset($normalized[$key])) { + $normalized[$key] = $value; + } elseif (!is_array($normalized[$key])) { + $normalized[$key] = array($value); + } else { + $normalized[$key][] = $value; + } + } + + foreach ($normalized as $key => &$value) { + if (is_array($value)) { + sort($value); + } + } + + return $normalized; + } + + public function compareHttpHeaders($result, $expected) + { + // Aggregate all headers case-insensitively + $result = $this->normalizeHeaders($result); + $expected = $this->normalizeHeaders($expected); + $this->assertEquals($result, $expected); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php new file mode 100644 index 0000000..2f52228 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/MessageParserTest.php @@ -0,0 +1,58 @@ +compareRequestResults($parts, $parser->parseRequest($message)); + } + + /** + * @dataProvider responseProvider + */ + public function testParsesResponses($message, $parts) + { + $parser = new MessageParser(); + $this->compareResponseResults($parts, $parser->parseResponse($message)); + } + + public function testParsesRequestsWithMissingProtocol() + { + $parser = new MessageParser(); + $parts = $parser->parseRequest("GET /\r\nHost: Foo.com\r\n\r\n"); + $this->assertEquals('GET', $parts['method']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['version']); + } + + public function testParsesRequestsWithMissingVersion() + { + $parser = new MessageParser(); + $parts = $parser->parseRequest("GET / HTTP\r\nHost: Foo.com\r\n\r\n"); + $this->assertEquals('GET', $parts['method']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['version']); + } + + public function testParsesResponsesWithMissingReasonPhrase() + { + $parser = new MessageParser(); + $parts = $parser->parseResponse("HTTP/1.1 200\r\n\r\n"); + $this->assertEquals('200', $parts['code']); + $this->assertEquals('', $parts['reason_phrase']); + $this->assertEquals('HTTP', $parts['protocol']); + $this->assertEquals('1.1', $parts['version']); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php new file mode 100644 index 0000000..6706e20 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/Message/PeclHttpMessageParserTest.php @@ -0,0 +1,36 @@ +markTestSkipped('pecl_http is not available.'); + } + } + + /** + * @dataProvider requestProvider + */ + public function testParsesRequests($message, $parts) + { + $parser = new PeclHttpMessageParser(); + $this->compareRequestResults($parts, $parser->parseRequest($message)); + } + + /** + * @dataProvider responseProvider + */ + public function testParsesResponses($message, $parts) + { + $parser = new PeclHttpMessageParser(); + $this->compareResponseResults($parts, $parser->parseResponse($message)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php new file mode 100644 index 0000000..7675efb --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/ParserRegistryTest.php @@ -0,0 +1,33 @@ +registerParser('foo', $c); + $this->assertSame($c, $r->getParser('foo')); + } + + public function testReturnsNullWhenNotFound() + { + $r = new ParserRegistry(); + $this->assertNull($r->getParser('FOO')); + } + + public function testReturnsLazyLoadedDefault() + { + $r = new ParserRegistry(); + $c = $r->getParser('cookie'); + $this->assertInstanceOf('Guzzle\Parser\Cookie\CookieParser', $c); + $this->assertSame($c, $r->getParser('cookie')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php new file mode 100644 index 0000000..a05fc2e --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/AbstractUriTemplateTest.php @@ -0,0 +1,113 @@ + 'value', + 'hello' => 'Hello World!', + 'empty' => '', + 'path' => '/foo/bar', + 'x' => '1024', + 'y' => '768', + 'null' => null, + 'list' => array('red', 'green', 'blue'), + 'keys' => array( + "semi" => ';', + "dot" => '.', + "comma" => ',' + ), + 'empty_keys' => array(), + ); + + return array_map(function($t) use ($params) { + $t[] = $params; + return $t; + }, array( + array('foo', 'foo'), + array('{var}', 'value'), + array('{hello}', 'Hello%20World%21'), + array('{+var}', 'value'), + array('{+hello}', 'Hello%20World!'), + array('{+path}/here', '/foo/bar/here'), + array('here?ref={+path}', 'here?ref=/foo/bar'), + array('X{#var}', 'X#value'), + array('X{#hello}', 'X#Hello%20World!'), + array('map?{x,y}', 'map?1024,768'), + array('{x,hello,y}', '1024,Hello%20World%21,768'), + array('{+x,hello,y}', '1024,Hello%20World!,768'), + array('{+path,x}/here', '/foo/bar,1024/here'), + array('{#x,hello,y}', '#1024,Hello%20World!,768'), + array('{#path,x}/here', '#/foo/bar,1024/here'), + array('X{.var}', 'X.value'), + array('X{.x,y}', 'X.1024.768'), + array('{/var}', '/value'), + array('{/var,x}/here', '/value/1024/here'), + array('{;x,y}', ';x=1024;y=768'), + array('{;x,y,empty}', ';x=1024;y=768;empty'), + array('{?x,y}', '?x=1024&y=768'), + array('{?x,y,empty}', '?x=1024&y=768&empty='), + array('?fixed=yes{&x}', '?fixed=yes&x=1024'), + array('{&x,y,empty}', '&x=1024&y=768&empty='), + array('{var:3}', 'val'), + array('{var:30}', 'value'), + array('{list}', 'red,green,blue'), + array('{list*}', 'red,green,blue'), + array('{keys}', 'semi,%3B,dot,.,comma,%2C'), + array('{keys*}', 'semi=%3B,dot=.,comma=%2C'), + array('{+path:6}/here', '/foo/b/here'), + array('{+list}', 'red,green,blue'), + array('{+list*}', 'red,green,blue'), + array('{+keys}', 'semi,;,dot,.,comma,,'), + array('{+keys*}', 'semi=;,dot=.,comma=,'), + array('{#path:6}/here', '#/foo/b/here'), + array('{#list}', '#red,green,blue'), + array('{#list*}', '#red,green,blue'), + array('{#keys}', '#semi,;,dot,.,comma,,'), + array('{#keys*}', '#semi=;,dot=.,comma=,'), + array('X{.var:3}', 'X.val'), + array('X{.list}', 'X.red,green,blue'), + array('X{.list*}', 'X.red.green.blue'), + array('X{.keys}', 'X.semi,%3B,dot,.,comma,%2C'), + array('X{.keys*}', 'X.semi=%3B.dot=..comma=%2C'), + array('{/var:1,var}', '/v/value'), + array('{/list}', '/red,green,blue'), + array('{/list*}', '/red/green/blue'), + array('{/list*,path:4}', '/red/green/blue/%2Ffoo'), + array('{/keys}', '/semi,%3B,dot,.,comma,%2C'), + array('{/keys*}', '/semi=%3B/dot=./comma=%2C'), + array('{;hello:5}', ';hello=Hello'), + array('{;list}', ';list=red,green,blue'), + array('{;list*}', ';list=red;list=green;list=blue'), + array('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'), + array('{;keys*}', ';semi=%3B;dot=.;comma=%2C'), + array('{?var:3}', '?var=val'), + array('{?list}', '?list=red,green,blue'), + array('{?list*}', '?list=red&list=green&list=blue'), + array('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'), + array('{?keys*}', '?semi=%3B&dot=.&comma=%2C'), + array('{&var:3}', '&var=val'), + array('{&list}', '&list=red,green,blue'), + array('{&list*}', '&list=red&list=green&list=blue'), + array('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'), + array('{&keys*}', '&semi=%3B&dot=.&comma=%2C'), + array('{.null}', ''), + array('{.null,var}', '.value'), + array('X{.empty_keys*}', 'X'), + array('X{.empty_keys}', 'X'), + // Test that missing expansions are skipped + array('test{&missing*}', 'test'), + // Test that multiple expansions can be set + array('http://{var}/{var:2}{?keys*}', 'http://value/va?semi=%3B&dot=.&comma=%2C'), + // Test more complex query string stuff + array('http://www.test.com{+path}{?var,keys*}', 'http://www.test.com/foo/bar?var=value&semi=%3B&dot=.&comma=%2C') + )); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php new file mode 100644 index 0000000..633c5d5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/PeclUriTemplateTest.php @@ -0,0 +1,27 @@ +markTestSkipped('uri_template PECL extension must be installed to test PeclUriTemplate'); + } + } + + /** + * @dataProvider templateProvider + */ + public function testExpandsUriTemplates($template, $expansion, $params) + { + $uri = new PeclUriTemplate($template); + $this->assertEquals($expansion, $uri->expand($template, $params)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php new file mode 100644 index 0000000..5130d6f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Parser/UriTemplate/UriTemplateTest.php @@ -0,0 +1,106 @@ +assertEquals($expansion, $uri->expand($template, $params)); + } + + public function expressionProvider() + { + return array( + array( + '{+var*}', array( + 'operator' => '+', + 'values' => array( + array('value' => 'var', 'modifier' => '*') + ) + ), + ), + array( + '{?keys,var,val}', array( + 'operator' => '?', + 'values' => array( + array('value' => 'keys', 'modifier' => ''), + array('value' => 'var', 'modifier' => ''), + array('value' => 'val', 'modifier' => '') + ) + ), + ), + array( + '{+x,hello,y}', array( + 'operator' => '+', + 'values' => array( + array('value' => 'x', 'modifier' => ''), + array('value' => 'hello', 'modifier' => ''), + array('value' => 'y', 'modifier' => '') + ) + ) + ) + ); + } + + /** + * @dataProvider expressionProvider + */ + public function testParsesExpressions($exp, $data) + { + $template = new UriTemplate($exp); + + // Access the config object + $class = new \ReflectionClass($template); + $method = $class->getMethod('parseExpression'); + $method->setAccessible(true); + + $exp = substr($exp, 1, -1); + $this->assertEquals($data, $method->invokeArgs($template, array($exp))); + } + + /** + * @ticket https://github.com/guzzle/guzzle/issues/90 + */ + public function testAllowsNestedArrayExpansion() + { + $template = new UriTemplate(); + + $result = $template->expand('http://example.com{+path}{/segments}{?query,data*,foo*}', array( + 'path' => '/foo/bar', + 'segments' => array('one', 'two'), + 'query' => 'test', + 'data' => array( + 'more' => array('fun', 'ice cream') + ), + 'foo' => array( + 'baz' => array( + 'bar' => 'fizz', + 'test' => 'buzz' + ), + 'bam' => 'boo' + ) + )); + + $this->assertEquals('http://example.com/foo/bar/one,two?query=test&more%5B0%5D=fun&more%5B1%5D=ice%20cream&baz%5Bbar%5D=fizz&baz%5Btest%5D=buzz&bam=boo', $result); + } + + /** + * @ticket https://github.com/guzzle/guzzle/issues/426 + */ + public function testSetRegex() + { + $template = new UriTemplate(); + $template->setRegex('/\<\$(.+)\>/'); + $this->assertSame('/foo', $template->expand('/<$a>', array('a' => 'foo'))); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php new file mode 100644 index 0000000..16990a5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Async/AsyncPluginTest.php @@ -0,0 +1,93 @@ +assertArrayHasKey('request.before_send', $events); + $this->assertArrayHasKey('request.exception', $events); + $this->assertArrayHasKey('curl.callback.progress', $events); + } + + public function testEnablesProgressCallbacks() + { + $p = new AsyncPlugin(); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com'); + $event = new Event(array( + 'request' => $request + )); + $p->onBeforeSend($event); + $this->assertEquals(true, $request->getCurlOptions()->get('progress')); + } + + public function testAddsTimesOutAfterSending() + { + $p = new AsyncPlugin(); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com'); + $handle = CurlHandle::factory($request); + $event = new Event(array( + 'request' => $request, + 'handle' => $handle->getHandle(), + 'uploaded' => 10, + 'upload_size' => 10, + 'downloaded' => 0 + )); + $p->onCurlProgress($event); + } + + public function testEnsuresRequestIsSet() + { + $p = new AsyncPlugin(); + $event = new Event(array( + 'uploaded' => 10, + 'upload_size' => 10, + 'downloaded' => 0 + )); + $p->onCurlProgress($event); + } + + public function testMasksCurlExceptions() + { + $p = new AsyncPlugin(); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com'); + $e = new CurlException('Error'); + $event = new Event(array( + 'request' => $request, + 'exception' => $e + )); + $p->onRequestTimeout($event); + $this->assertEquals(RequestInterface::STATE_COMPLETE, $request->getState()); + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $this->assertTrue($request->getResponse()->hasHeader('X-Guzzle-Async')); + } + + public function testEnsuresIntegration() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 204 FOO\r\nContent-Length: 4\r\n\r\ntest"); + $client = new Client($this->getServer()->getUrl()); + $request = $client->post('/', null, array( + 'foo' => 'bar' + )); + $request->getEventDispatcher()->addSubscriber(new AsyncPlugin()); + $request->send(); + $this->assertEquals('', $request->getResponse()->getBody(true)); + $this->assertTrue($request->getResponse()->hasHeader('X-Guzzle-Async')); + $received = $this->getServer()->getReceivedRequests(true); + $this->assertEquals('POST', $received[0]->getMethod()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php new file mode 100644 index 0000000..72af263 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/AbstractBackoffStrategyTest.php @@ -0,0 +1,86 @@ +getMockBuilder('Guzzle\Plugin\Backoff\AbstractBackoffStrategy') + ->setMethods(array('getDelay', 'makesDecision')) + ->getMockForAbstractClass(); + } + + public function testReturnsZeroWhenNoNextAndGotNull() + { + $request = new Request('GET', 'http://www.foo.com'); + $mock = $this->getMockStrategy(); + $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(null)); + $this->assertEquals(0, $mock->getBackoffPeriod(0, $request)); + } + + public function testReturnsFalse() + { + $request = new Request('GET', 'http://www.foo.com'); + $mock = $this->getMockStrategy(); + $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(false)); + $this->assertEquals(false, $mock->getBackoffPeriod(0, $request)); + } + + public function testReturnsNextValueWhenNullOrTrue() + { + $request = new Request('GET', 'http://www.foo.com'); + $mock = $this->getMockStrategy(); + $mock->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(null)); + $mock->expects($this->any())->method('makesDecision')->will($this->returnValue(false)); + + $mock2 = $this->getMockStrategy(); + $mock2->expects($this->atLeastOnce())->method('getDelay')->will($this->returnValue(10)); + $mock2->expects($this->atLeastOnce())->method('makesDecision')->will($this->returnValue(true)); + $mock->setNext($mock2); + + $this->assertEquals(10, $mock->getBackoffPeriod(0, $request)); + } + + public function testReturnsFalseWhenNullAndNoNext() + { + $request = new Request('GET', 'http://www.foo.com'); + $s = new TruncatedBackoffStrategy(2); + $this->assertFalse($s->getBackoffPeriod(0, $request)); + } + + public function testHasNext() + { + $a = new TruncatedBackoffStrategy(2); + $b = new TruncatedBackoffStrategy(2); + $a->setNext($b); + $this->assertSame($b, $a->getNext()); + } + + public function testSkipsOtherDecisionsInChainWhenOneReturnsTrue() + { + $a = new CallbackBackoffStrategy(function () { return null; }, true); + $b = new CallbackBackoffStrategy(function () { return true; }, true); + $c = new CallbackBackoffStrategy(function () { return null; }, true); + $d = new CallbackBackoffStrategy(function () { return 10; }, false); + $a->setNext($b); + $b->setNext($c); + $c->setNext($d); + $this->assertEquals(10, $a->getBackoffPeriod(2, new Request('GET', 'http://www.foo.com'))); + } + + public function testReturnsZeroWhenDecisionMakerReturnsTrueButNoFurtherStrategiesAreInTheChain() + { + $a = new CallbackBackoffStrategy(function () { return null; }, true); + $b = new CallbackBackoffStrategy(function () { return true; }, true); + $a->setNext($b); + $this->assertSame(0, $a->getBackoffPeriod(2, new Request('GET', 'http://www.foo.com'))); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php new file mode 100644 index 0000000..a64dd82 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffLoggerTest.php @@ -0,0 +1,110 @@ +message = ''; + } + + public function testHasEventList() + { + $this->assertEquals(1, count(BackoffLogger::getSubscribedEvents())); + } + + public function testLogsEvents() + { + list($logPlugin, $request, $response) = $this->getMocks(); + + $response = $this->getMockBuilder('Guzzle\Http\Message\Response') + ->setConstructorArgs(array(503)) + ->setMethods(array('getInfo')) + ->getMock(); + + $response->expects($this->any()) + ->method('getInfo') + ->will($this->returnValue(2)); + + $handle = $this->getMockHandle(); + + $event = new Event(array( + 'request' => $request, + 'response' => $response, + 'retries' => 1, + 'delay' => 3, + 'handle' => $handle + )); + + $logPlugin->onRequestRetry($event); + $this->assertContains( + '] PUT http://www.example.com - 503 Service Unavailable - Retries: 1, Delay: 3, Time: 2, 2, cURL: 30 Foo', + $this->message + ); + } + + public function testCanSetTemplate() + { + $l = new BackoffLogger(new ClosureLogAdapter(function () {})); + $l->setTemplate('foo'); + $t = $this->readAttribute($l, 'formatter'); + $this->assertEquals('foo', $this->readAttribute($t, 'template')); + } + + /** + * @return array + */ + protected function getMocks() + { + $that = $this; + $logger = new ClosureLogAdapter(function ($message) use ($that) { + $that->message .= $message . "\n"; + }); + $logPlugin = new BackoffLogger($logger); + $response = new Response(503); + $request = RequestFactory::getInstance()->create('PUT', 'http://www.example.com', array( + 'Content-Length' => 3, + 'Foo' => 'Bar' + )); + + return array($logPlugin, $request, $response); + } + + /** + * @return CurlHandle + */ + protected function getMockHandle() + { + $handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle') + ->disableOriginalConstructor() + ->setMethods(array('getError', 'getErrorNo', 'getInfo')) + ->getMock(); + + $handle->expects($this->once()) + ->method('getError') + ->will($this->returnValue('Foo')); + + $handle->expects($this->once()) + ->method('getErrorNo') + ->will($this->returnValue(30)); + + $handle->expects($this->any()) + ->method('getInfo') + ->will($this->returnValue(2)); + + return $handle; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php new file mode 100644 index 0000000..496e49e --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/BackoffPluginTest.php @@ -0,0 +1,297 @@ +retried = false; + } + + public static function getSubscribedEvents() + { + return array(BackoffPlugin::RETRY_EVENT => 'onRequestRetry'); + } + + public function onRequestRetry(Event $event) + { + $this->retried = $event; + } + + public function testHasEventList() + { + $this->assertEquals(1, count(BackoffPlugin::getAllEvents())); + } + + public function testCreatesDefaultExponentialBackoffPlugin() + { + $plugin = BackoffPlugin::getExponentialBackoff(3, array(204), array(10)); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\BackoffPlugin', $plugin); + $strategy = $this->readAttribute($plugin, 'strategy'); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\TruncatedBackoffStrategy', $strategy); + $this->assertEquals(3, $this->readAttribute($strategy, 'max')); + $strategy = $this->readAttribute($strategy, 'next'); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\HttpBackoffStrategy', $strategy); + $this->assertEquals(array(204 => true), $this->readAttribute($strategy, 'errorCodes')); + $strategy = $this->readAttribute($strategy, 'next'); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\CurlBackoffStrategy', $strategy); + $this->assertEquals(array(10 => true), $this->readAttribute($strategy, 'errorCodes')); + $strategy = $this->readAttribute($strategy, 'next'); + $this->assertInstanceOf('Guzzle\Plugin\Backoff\ExponentialBackoffStrategy', $strategy); + } + + public function testDoesNotRetryUnlessStrategyReturnsNumber() + { + $request = new Request('GET', 'http://www.example.com'); + $request->setState('transfer'); + + $mock = $this->getMockBuilder('Guzzle\Plugin\Backoff\BackoffStrategyInterface') + ->setMethods(array('getBackoffPeriod')) + ->getMockForAbstractClass(); + + $mock->expects($this->once()) + ->method('getBackoffPeriod') + ->will($this->returnValue(false)); + + $plugin = new BackoffPlugin($mock); + $plugin->addSubscriber($this); + $plugin->onRequestSent(new Event(array('request' => $request))); + $this->assertFalse($this->retried); + } + + public function testUpdatesRequestForRetry() + { + $request = new Request('GET', 'http://www.example.com'); + $request->setState('transfer'); + $response = new Response(500); + $handle = $this->getMockBuilder('Guzzle\Http\Curl\CurlHandle')->disableOriginalConstructor()->getMock(); + $e = new CurlException(); + $e->setCurlHandle($handle); + + $plugin = new BackoffPlugin(new ConstantBackoffStrategy(10)); + $plugin->addSubscriber($this); + + $event = new Event(array( + 'request' => $request, + 'response' => $response, + 'exception' => $e + )); + + $plugin->onRequestSent($event); + $this->assertEquals(array( + 'request' => $request, + 'response' => $response, + 'handle' => $handle, + 'retries' => 1, + 'delay' => 10 + ), $this->readAttribute($this->retried, 'context')); + + $plugin->onRequestSent($event); + $this->assertEquals(array( + 'request' => $request, + 'response' => $response, + 'handle' => $handle, + 'retries' => 2, + 'delay' => 10 + ), $this->readAttribute($this->retried, 'context')); + } + + public function testDoesNothingWhenNotRetryingAndPollingRequest() + { + $request = new Request('GET', 'http://www.foo.com'); + $plugin = new BackoffPlugin(new ConstantBackoffStrategy(10)); + $plugin->onRequestPoll(new Event(array('request' => $request))); + } + + public function testRetriesRequests() + { + // Create a script to return several 500 and 503 response codes + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata" + )); + + $plugin = new BackoffPlugin( + new TruncatedBackoffStrategy(3, + new HttpBackoffStrategy(null, + new CurlBackoffStrategy(null, + new ConstantBackoffStrategy(0.05) + ) + ) + ) + ); + + $client = new Client($this->getServer()->getUrl()); + $client->getEventDispatcher()->addSubscriber($plugin); + $request = $client->get(); + $request->send(); + + // Make sure it eventually completed successfully + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $this->assertEquals('data', $request->getResponse()->getBody(true)); + + // Check that three requests were made to retry this request + $this->assertEquals(3, count($this->getServer()->getReceivedRequests(false))); + $this->assertEquals(2, $request->getParams()->get(BackoffPlugin::RETRY_PARAM)); + } + + /** + * @expectedException \Guzzle\Http\Exception\ServerErrorResponseException + */ + public function testFailsOnTruncation() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n" + )); + + $plugin = new BackoffPlugin( + new TruncatedBackoffStrategy(2, + new HttpBackoffStrategy(null, + new ConstantBackoffStrategy(0.05) + ) + ) + ); + + $client = new Client($this->getServer()->getUrl()); + $client->addSubscriber($plugin); + $client->get()->send(); + } + + public function testRetriesRequestsWhenInParallel() + { + // Create a script to return several 500 and 503 response codes + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata" + )); + + $plugin = new BackoffPlugin( + new HttpBackoffStrategy(null, + new TruncatedBackoffStrategy(3, + new CurlBackoffStrategy(null, + new ConstantBackoffStrategy(0.1) + ) + ) + ) + ); + $client = new Client($this->getServer()->getUrl()); + $client->getEventDispatcher()->addSubscriber($plugin); + $requests = array(); + for ($i = 0; $i < 5; $i++) { + $requests[] = $client->get(); + } + $client->send($requests); + + $this->assertEquals(15, count($this->getServer()->getReceivedRequests(false))); + } + + /** + * @covers Guzzle\Plugin\Backoff\BackoffPlugin + * @covers Guzzle\Http\Curl\CurlMulti + */ + public function testRetriesPooledRequestsUsingDelayAndPollingEvent() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata" + )); + // Need to sleep for some time ensure that the polling works correctly in the observer + $plugin = new BackoffPlugin(new HttpBackoffStrategy(null, + new TruncatedBackoffStrategy(1, + new ConstantBackoffStrategy(0.5)))); + + $client = new Client($this->getServer()->getUrl()); + $client->getEventDispatcher()->addSubscriber($plugin); + $request = $client->get(); + $request->send(); + // Make sure it eventually completed successfully + $this->assertEquals('data', $request->getResponse()->getBody(true)); + // Check that two requests were made to retry this request + $this->assertEquals(2, count($this->getServer()->getReceivedRequests(false))); + } + + public function testSeeksToBeginningOfRequestBodyWhenRetrying() + { + // Create a request with a body + $request = new EntityEnclosingRequest('PUT', 'http://www.example.com'); + $request->setBody('abc'); + // Set the retry time to be something that will be retried always + $request->getParams()->set(BackoffPlugin::DELAY_PARAM, 2); + // Seek to the end of the stream + $request->getBody()->seek(3); + $this->assertEquals('', $request->getBody()->read(1)); + // Create a plugin that does not delay when retrying + $plugin = new BackoffPlugin(new ConstantBackoffStrategy(0)); + $plugin->onRequestPoll($this->getMockEvent($request)); + // Ensure that the stream was seeked to 0 + $this->assertEquals('a', $request->getBody()->read(1)); + } + + public function testDoesNotSeekOnRequestsWithNoBodyWhenRetrying() + { + // Create a request with a body + $request = new EntityEnclosingRequest('PUT', 'http://www.example.com'); + $request->getParams()->set(BackoffPlugin::DELAY_PARAM, 2); + $plugin = new BackoffPlugin(new ConstantBackoffStrategy(0)); + $plugin->onRequestPoll($this->getMockEvent($request)); + } + + protected function getMockEvent(RequestInterface $request) + { + // Create a mock curl multi object + $multi = $this->getMockBuilder('Guzzle\Http\Curl\CurlMulti') + ->setMethods(array('remove', 'add')) + ->getMock(); + + // Create an event that is expected for the Poll event + $event = new Event(array( + 'request' => $request, + 'curl_multi' => $multi + )); + $event->setName(CurlMultiInterface::POLLING_REQUEST); + + return $event; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php new file mode 100644 index 0000000..c0ce10d --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CallbackBackoffStrategyTest.php @@ -0,0 +1,31 @@ +getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $strategy = new CallbackBackoffStrategy(function () { return 10; }, true); + $this->assertTrue($strategy->makesDecision()); + $this->assertEquals(10, $strategy->getBackoffPeriod(0, $request)); + // Ensure it chains correctly when null is returned + $strategy = new CallbackBackoffStrategy(function () { return null; }, false); + $this->assertFalse($strategy->makesDecision()); + $this->assertFalse($strategy->getBackoffPeriod(0, $request)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php new file mode 100644 index 0000000..703eb4a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ConstantBackoffStrategyTest.php @@ -0,0 +1,20 @@ +assertFalse($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(3.5, $strategy->getBackoffPeriod(0, $request)); + $this->assertEquals(3.5, $strategy->getBackoffPeriod(1, $request)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php new file mode 100644 index 0000000..0a5c3e2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/CurlBackoffStrategyTest.php @@ -0,0 +1,36 @@ +assertNotEmpty(CurlBackoffStrategy::getDefaultFailureCodes()); + $strategy = new CurlBackoffStrategy(); + $this->assertTrue($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $e = new CurlException(); + $e->setError('foo', CURLE_BAD_CALLING_ORDER); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, null, $e)); + + foreach (CurlBackoffStrategy::getDefaultFailureCodes() as $code) { + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, null, $e->setError('foo', $code))); + } + } + + public function testIgnoresNonErrors() + { + $strategy = new CurlBackoffStrategy(); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, new Response(200))); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php new file mode 100644 index 0000000..09965bc --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ExponentialBackoffStrategyTest.php @@ -0,0 +1,23 @@ +assertFalse($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(1, $strategy->getBackoffPeriod(0, $request)); + $this->assertEquals(2, $strategy->getBackoffPeriod(1, $request)); + $this->assertEquals(4, $strategy->getBackoffPeriod(2, $request)); + $this->assertEquals(8, $strategy->getBackoffPeriod(3, $request)); + $this->assertEquals(16, $strategy->getBackoffPeriod(4, $request)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php new file mode 100644 index 0000000..ae68a4e --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/HttpBackoffStrategyTest.php @@ -0,0 +1,47 @@ +assertNotEmpty(HttpBackoffStrategy::getDefaultFailureCodes()); + $strategy = new HttpBackoffStrategy(); + $this->assertTrue($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + + $response = new Response(200); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response)); + $response->setStatus(400); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response)); + + foreach (HttpBackoffStrategy::getDefaultFailureCodes() as $code) { + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response->setStatus($code))); + } + } + + public function testAllowsCustomCodes() + { + $strategy = new HttpBackoffStrategy(array(204)); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $response = new Response(204); + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response)); + $response->setStatus(500); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response)); + } + + public function testIgnoresNonErrors() + { + $strategy = new HttpBackoffStrategy(); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php new file mode 100644 index 0000000..b4ce8e4 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/LinearBackoffStrategyTest.php @@ -0,0 +1,21 @@ +assertFalse($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request)); + $this->assertEquals(5, $strategy->getBackoffPeriod(1, $request)); + $this->assertEquals(10, $strategy->getBackoffPeriod(2, $request)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php new file mode 100644 index 0000000..dea5a68 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/ReasonPhraseBackoffStrategyTest.php @@ -0,0 +1,32 @@ +assertEmpty(ReasonPhraseBackoffStrategy::getDefaultFailureCodes()); + $strategy = new ReasonPhraseBackoffStrategy(array('Foo', 'Internal Server Error')); + $this->assertTrue($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $response = new Response(200); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request, $response)); + $response->setStatus(200, 'Foo'); + $this->assertEquals(0, $strategy->getBackoffPeriod(0, $request, $response)); + } + + public function testIgnoresNonErrors() + { + $strategy = new ReasonPhraseBackoffStrategy(); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertEquals(false, $strategy->getBackoffPeriod(0, $request)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php new file mode 100644 index 0000000..5590dfb --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Backoff/TruncatedBackoffStrategyTest.php @@ -0,0 +1,30 @@ +assertTrue($strategy->makesDecision()); + $request = $this->getMock('Guzzle\Http\Message\Request', array(), array(), '', false); + $this->assertFalse($strategy->getBackoffPeriod(0, $request)); + $this->assertFalse($strategy->getBackoffPeriod(1, $request)); + $this->assertFalse($strategy->getBackoffPeriod(2, $request)); + + $response = new Response(500); + $strategy->setNext(new HttpBackoffStrategy(null, new ConstantBackoffStrategy(10))); + $this->assertEquals(10, $strategy->getBackoffPeriod(0, $request, $response)); + $this->assertEquals(10, $strategy->getBackoffPeriod(1, $request, $response)); + $this->assertFalse($strategy->getBackoffPeriod(2, $request, $response)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php new file mode 100644 index 0000000..69da60a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CachePluginTest.php @@ -0,0 +1,441 @@ +assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage')); + } + + public function testAddsDefaultCollaborators() + { + $this->assertNotEmpty(CachePlugin::getSubscribedEvents()); + $plugin = new CachePlugin(array( + 'storage' => $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass() + )); + $this->assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage')); + $this->assertInstanceOf( + 'Guzzle\Plugin\Cache\CanCacheStrategyInterface', + $this->readAttribute($plugin, 'canCache') + ); + $this->assertInstanceOf( + 'Guzzle\Plugin\Cache\RevalidationInterface', + $this->readAttribute($plugin, 'revalidation') + ); + } + + public function testAddsCallbackCollaborators() + { + $this->assertNotEmpty(CachePlugin::getSubscribedEvents()); + $plugin = new CachePlugin(array('can_cache' => function () {})); + $this->assertInstanceOf( + 'Guzzle\Plugin\Cache\CallbackCanCacheStrategy', + $this->readAttribute($plugin, 'canCache') + ); + } + + public function testCanPassCacheAsOnlyArgumentToConstructor() + { + $p = new CachePlugin(new DoctrineCacheAdapter(new ArrayCache())); + $p = new CachePlugin(new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache()))); + } + + public function testUsesCreatedCacheStorage() + { + $plugin = new CachePlugin(array( + 'adapter' => $this->getMockBuilder('Guzzle\Cache\CacheAdapterInterface')->getMockForAbstractClass() + )); + $this->assertInstanceOf('Guzzle\Plugin\Cache\CacheStorageInterface', $this->readAttribute($plugin, 'storage')); + } + + public function testUsesProvidedOptions() + { + $can = $this->getMockBuilder('Guzzle\Plugin\Cache\CanCacheStrategyInterface')->getMockForAbstractClass(); + $revalidate = $this->getMockBuilder('Guzzle\Plugin\Cache\RevalidationInterface')->getMockForAbstractClass(); + $plugin = new CachePlugin(array( + 'storage' => $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass(), + 'can_cache' => $can, + 'revalidation' => $revalidate + )); + $this->assertSame($can, $this->readAttribute($plugin, 'canCache')); + $this->assertSame($revalidate, $this->readAttribute($plugin, 'revalidation')); + } + + public function satisfyProvider() + { + $req1 = new Request('GET', 'http://foo.com', array('Cache-Control' => 'no-cache')); + + return array( + // The response is too old to satisfy the request + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-age=20')), new Response(200, array('Age' => 100)), false, false), + // The response cannot satisfy the request because it is stale + array(new Request('GET', 'http://foo.com'), new Response(200, array('Cache-Control' => 'max-age=10', 'Age' => 100)), false, false), + // Allows the expired response to satisfy the request because of the max-stale + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale=15')), new Response(200, array('Cache-Control' => 'max-age=90', 'Age' => 100)), true, false), + // Max stale is > than the allowed staleness + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale=5')), new Response(200, array('Cache-Control' => 'max-age=90', 'Age' => 100)), false, false), + // Performs cache revalidation + array($req1, new Response(200), true, true), + // Performs revalidation due to ETag on the response and no cache-control on the request + array(new Request('GET', 'http://foo.com'), new Response(200, array( + 'ETag' => 'ABC', + 'Expires' => date('c', strtotime('+1 year')) + )), true, true), + ); + } + + /** + * @dataProvider satisfyProvider + */ + public function testChecksIfResponseCanSatisfyRequest($request, $response, $can, $revalidates) + { + $didRevalidate = false; + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface')->getMockForAbstractClass(); + $revalidate = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultRevalidation') + ->setMethods(array('revalidate')) + ->setConstructorArgs(array($storage)) + ->getMockForAbstractClass(); + + $revalidate->expects($this->any()) + ->method('revalidate') + ->will($this->returnCallback(function () use (&$didRevalidate) { + $didRevalidate = true; + return true; + })); + + $plugin = new CachePlugin(array( + 'storage' => $storage, + 'revalidation' => $revalidate + )); + + $this->assertEquals($can, $plugin->canResponseSatisfyRequest($request, $response)); + $this->assertEquals($didRevalidate, $revalidates); + } + + public function satisfyFailedProvider() + { + return array( + // Neither has stale-if-error + array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100)), false), + // Request has stale-if-error + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), true), + // Request has valid stale-if-error + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=50')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), true), + // Request has expired stale-if-error + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=20')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50')), false), + // Response has permanent stale-if-error + array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error', )), true), + // Response has valid stale-if-error + array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=50')), true), + // Response has expired stale-if-error + array(new Request('GET', 'http://foo.com', array()), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=20')), false), + // Request has valid stale-if-error but response does not + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=50')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=20')), false), + // Response has valid stale-if-error but request does not + array(new Request('GET', 'http://foo.com', array('Cache-Control' => 'stale-if-error=20')), new Response(200, array('Age' => 100, 'Cache-Control' => 'max-age=50, stale-if-error=50')), false), + ); + } + + /** + * @dataProvider satisfyFailedProvider + */ + public function testChecksIfResponseCanSatisfyFailedRequest($request, $response, $can) + { + $plugin = new CachePlugin(); + + $this->assertEquals($can, $plugin->canResponseSatisfyFailedRequest($request, $response)); + } + + public function testDoesNothingWhenRequestIsNotCacheable() + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->never())->method('fetch'); + + $plugin = new CachePlugin(array( + 'storage' => $storage, + 'can_cache' => new CallbackCanCacheStrategy(function () { return false; }) + )); + + $plugin->onRequestBeforeSend(new Event(array( + 'request' => new Request('GET', 'http://foo.com') + ))); + } + + public function satisfiableProvider() + { + $date = new \DateTime('-10 seconds'); + + return array( + // Fresh response + array(new Response(200, array(), 'foo')), + // Stale response + array(new Response(200, array('Date' => $date->format('c'), 'Cache-Control' => 'max-age=5'), 'foo')) + ); + } + + /** + * @dataProvider satisfiableProvider + */ + public function testInjectsSatisfiableResponses($response) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + + $storage->expects($this->once())->method('fetch')->will($this->returnValue($response)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale')); + $plugin->onRequestBeforeSend(new Event(array('request' => $request))); + $plugin->onRequestSent(new Event(array('request' => $request, 'response' => $request->getResponse()))); + $this->assertEquals($response->getStatusCode(), $request->getResponse()->getStatusCode()); + $this->assertEquals((string) $response->getBody(), (string) $request->getResponse()->getBody()); + $this->assertTrue($request->getResponse()->hasHeader('Age')); + if ($request->getResponse()->isFresh() === false) { + $this->assertContains('110', (string) $request->getResponse()->getHeader('Warning')); + } + $this->assertSame( + sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), + (string) $request->getHeader('Via') + ); + $this->assertSame( + sprintf('%s GuzzleCache/%s',$request->getProtocolVersion(), Version::VERSION), + (string) $request->getResponse()->getHeader('Via') + ); + $this->assertTrue($request->getParams()->get('cache.lookup')); + $this->assertTrue($request->getParams()->get('cache.hit')); + $this->assertTrue($request->getResponse()->hasHeader('X-Cache-Lookup')); + $this->assertTrue($request->getResponse()->hasHeader('X-Cache')); + $this->assertEquals('HIT from GuzzleCache', (string) $request->getResponse()->getHeader('X-Cache')); + $this->assertEquals('HIT from GuzzleCache', (string) $request->getResponse()->getHeader('X-Cache-Lookup')); + } + + public function satisfiableOnErrorProvider() + { + $date = new \DateTime('-10 seconds'); + return array( + array( + new Response(200, array( + 'Date' => $date->format('c'), + 'Cache-Control' => 'max-age=5, stale-if-error' + ), 'foo'), + ) + ); + } + + /** + * @dataProvider satisfiableOnErrorProvider + */ + public function testInjectsSatisfiableResponsesOnError($cacheResponse) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->exactly(2))->method('fetch')->will($this->returnValue($cacheResponse)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale')); + $plugin->onRequestBeforeSend(new Event(array('request' => $request))); + $plugin->onRequestError( + $event = new Event(array( + 'request' => $request, + 'response' => $request->getResponse(), + )) + ); + $response = $event['response']; + $this->assertEquals($cacheResponse->getStatusCode(), $response->getStatusCode()); + $this->assertEquals((string) $cacheResponse->getBody(), (string) $response->getBody()); + $this->assertTrue($response->hasHeader('Age')); + if ($response->isFresh() === false) { + $this->assertContains('110', (string) $response->getHeader('Warning')); + } + $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $request->getHeader('Via')); + $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $response->getHeader('Via')); + $this->assertTrue($request->getParams()->get('cache.lookup')); + $this->assertSame('error', $request->getParams()->get('cache.hit')); + $this->assertTrue($response->hasHeader('X-Cache-Lookup')); + $this->assertTrue($response->hasHeader('X-Cache')); + $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup')); + $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache')); + } + + /** + * @dataProvider satisfiableOnErrorProvider + */ + public function testInjectsSatisfiableResponsesOnException($cacheResponse) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->exactly(2))->method('fetch')->will($this->returnValue($cacheResponse)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', array('Cache-Control' => 'max-stale')); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + $plugin->onRequestException( + new Event(array( + 'request' => $request, + 'response' => $request->getResponse(), + 'exception' => $this->getMock('Guzzle\Http\Exception\CurlException'), + )) + ); + $plugin->onRequestSent( + new Event(array( + 'request' => $request, + 'response' => $response = $request->getResponse(), + )) + ); + $this->assertEquals($cacheResponse->getStatusCode(), $response->getStatusCode()); + $this->assertEquals((string) $cacheResponse->getBody(), (string) $response->getBody()); + $this->assertTrue($response->hasHeader('Age')); + if ($response->isFresh() === false) { + $this->assertContains('110', (string) $response->getHeader('Warning')); + } + $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $request->getHeader('Via')); + $this->assertSame(sprintf('%s GuzzleCache/%s', $request->getProtocolVersion(), Version::VERSION), (string) $response->getHeader('Via')); + $this->assertTrue($request->getParams()->get('cache.lookup')); + $this->assertSame('error', $request->getParams()->get('cache.hit')); + $this->assertTrue($response->hasHeader('X-Cache-Lookup')); + $this->assertTrue($response->hasHeader('X-Cache')); + $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup')); + $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache')); + } + + public function unsatisfiableOnErrorProvider() + { + $date = new \DateTime('-10 seconds'); + + return array( + // no-store on request + array( + false, + array('Cache-Control' => 'no-store'), + new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error'), 'foo'), + ), + // request expired + array( + true, + array('Cache-Control' => 'stale-if-error=4'), + new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error'), 'foo'), + ), + // response expired + array( + true, + array('Cache-Control' => 'stale-if-error'), + new Response(200, array('Date' => $date->format('D, d M Y H:i:s T'), 'Cache-Control' => 'max-age=5, stale-if-error=4'), 'foo'), + ), + ); + } + + /** + * @dataProvider unsatisfiableOnErrorProvider + */ + public function testDoesNotInjectUnsatisfiableResponsesOnError($requestCanCache, $requestHeaders, $cacheResponse) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->exactly($requestCanCache ? 2 : 0))->method('fetch')->will($this->returnValue($cacheResponse)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', $requestHeaders); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + $plugin->onRequestError( + $event = new Event(array( + 'request' => $request, + 'response' => $response = $request->getResponse(), + )) + ); + + $this->assertSame($response, $event['response']); + } + + /** + * @dataProvider unsatisfiableOnErrorProvider + */ + public function testDoesNotInjectUnsatisfiableResponsesOnException($requestCanCache, $requestHeaders, $responseParts) + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + $storage->expects($this->exactly($requestCanCache ? 2 : 0))->method('fetch')->will($this->returnValue($responseParts)); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', $requestHeaders); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + $plugin->onRequestException( + $event = new Event(array( + 'request' => $request, + 'response' => $response = $request->getResponse(), + 'exception' => $this->getMock('Guzzle\Http\Exception\CurlException'), + )) + ); + + $this->assertSame($response, $request->getResponse()); + } + + public function testCachesResponsesWhenCacheable() + { + $cache = new ArrayCache(); + $plugin = new CachePlugin($cache); + + $request = new Request('GET', 'http://foo.com'); + $response = new Response(200, array(), 'Foo'); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + $plugin->onRequestSent(new Event(array( + 'request' => $request, + 'response' => $response + ))); + $data = $this->readAttribute($cache, 'data'); + $this->assertNotEmpty($data); + } + + public function testPurgesRequests() + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('purge')) + ->getMockForAbstractClass(); + $storage->expects($this->atLeastOnce())->method('purge'); + $plugin = new CachePlugin(array('storage' => $storage)); + $request = new Request('GET', 'http://foo.com', array('X-Foo' => 'Bar')); + $plugin->purge($request); + } + + public function testAutoPurgesRequests() + { + $storage = $this->getMockBuilder('Guzzle\Plugin\Cache\CacheStorageInterface') + ->setMethods(array('purge')) + ->getMockForAbstractClass(); + $storage->expects($this->atLeastOnce())->method('purge'); + $plugin = new CachePlugin(array('storage' => $storage, 'auto_purge' => true)); + $client = new Client(); + $request = $client->put('http://foo.com', array('X-Foo' => 'Bar')); + $request->addSubscriber($plugin); + $request->setResponse(new Response(200), true); + $request->send(); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php new file mode 100644 index 0000000..f3d9baf --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/CallbackCanCacheStrategyTest.php @@ -0,0 +1,72 @@ +assertTrue($c->canCacheRequest(new Request('DELETE', 'http://www.foo.com'))); + } + + /** + * The following is a bit of an integration test to ensure that the CachePlugin honors a + * custom can cache strategy. + */ + public function testIntegrationWithCachePlugin() + { + $c = new CallbackCanCacheStrategy( + function ($request) { return true; }, + function ($response) { return true; } + ); + + // Make a request and response that have no business being cached + $request = new Request('DELETE', 'http://www.foo.com'); + $response = Response::fromMessage( + "HTTP/1.1 200 OK\r\n" + . "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + . "Last-Modified: Wed, 09 Jan 2013 08:48:53 GMT\r\n" + . "Content-Length: 2\r\n" + . "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\n\r\n" + . "hi" + ); + + $this->assertTrue($c->canCacheRequest($request)); + $this->assertTrue($c->canCacheResponse($response)); + + $s = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultCacheStorage') + ->setConstructorArgs(array(new DoctrineCacheAdapter(new ArrayCache()))) + ->setMethods(array('fetch')) + ->getMockForAbstractClass(); + + $s->expects($this->once()) + ->method('fetch') + ->will($this->returnValue($response)); + + $plugin = new CachePlugin(array('can_cache' => $c, 'storage' => $s)); + $plugin->onRequestBeforeSend(new Event(array('request' => $request))); + + $this->assertEquals(200, $request->getResponse()->getStatusCode()); + $this->assertEquals('hi', $request->getResponse()->getBody(true)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php new file mode 100644 index 0000000..701a015 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCacheStorageTest.php @@ -0,0 +1,193 @@ + 'application/json')); + $response = new Response(200, array( + 'Content-Type' => 'application/json', + 'Connection' => 'close', + 'X-Foo' => 'Bar', + 'Vary' => 'Accept' + ), 'test'); + $s->cache($request, $response); + $data = $this->readAttribute($a, 'data'); + + return array( + 'cache' => $a, + 'adapter' => $c, + 'storage' => $s, + 'request' => $request, + 'response' => $response, + 'serialized' => end($data) + ); + } + + public function testReturnsNullForCacheMiss() + { + $cache = $this->getCache(); + $this->assertNull($cache['storage']->fetch(new Request('GET', 'http://test.com'))); + } + + public function testCachesRequests() + { + $cache = $this->getCache(); + $foundRequest = $foundBody = $bodyKey = false; + foreach ($this->readAttribute($cache['cache'], 'data') as $key => $v) { + if (strpos($v, 'foo.com')) { + $foundRequest = true; + $data = unserialize($v); + $bodyKey = $data[0][3]; + $this->assertInternalType('integer', $data[0][4]); + $this->assertFalse(isset($data[0][0]['connection'])); + $this->assertEquals('foo.com', $data[0][0]['host']); + } elseif ($v == 'test') { + $foundBody = $key; + } + } + $this->assertContains($bodyKey, $foundBody); + $this->assertTrue($foundRequest); + } + + public function testFetchesResponse() + { + $cache = $this->getCache(); + $response = $cache['storage']->fetch($cache['request']); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertFalse($response->hasHeader('Connection')); + $this->assertEquals('Bar', (string) $response->getHeader('X-Foo')); + $this->assertEquals('test', (string) $response->getBody()); + $this->assertTrue(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data'))); + } + + public function testDeletesRequestItemsAndBody() + { + $cache = $this->getCache(); + $cache['storage']->delete($cache['request']); + $this->assertFalse(in_array('test', $this->readAttribute($cache['cache'], 'data'))); + $this->assertFalse(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data'))); + } + + public function testCachesMultipleRequestsWithVary() + { + $cache = $this->getCache(); + $cache['request']->setHeader('Accept', 'application/xml'); + $response = $cache['response']->setHeader('Content-Type', 'application/xml'); + $response->setBody('123'); + $cache['storage']->cache($cache['request'], $response); + $data = $this->readAttribute($cache['cache'], 'data'); + foreach ($data as $v) { + if (strpos($v, 'foo.com')) { + $u = unserialize($v); + $this->assertEquals(2, count($u)); + $this->assertEquals($u[0][0]['accept'], 'application/xml'); + $this->assertEquals($u[0][1]['content-type'], 'application/xml'); + $this->assertEquals($u[1][0]['accept'], 'application/json'); + $this->assertEquals($u[1][1]['content-type'], 'application/json'); + $this->assertNotSame($u[0][3], $u[1][3]); + break; + } + } + } + + public function testPurgeRemovesAllMethodCaches() + { + $cache = $this->getCache(); + foreach (array('HEAD', 'POST', 'PUT', 'DELETE') as $method) { + $request = RequestFactory::getInstance()->cloneRequestWithMethod($cache['request'], $method); + $cache['storage']->cache($request, $cache['response']); + } + $cache['storage']->purge('http://foo.com'); + $this->assertFalse(in_array('test', $this->readAttribute($cache['cache'], 'data'))); + $this->assertFalse(in_array($cache['serialized'], $this->readAttribute($cache['cache'], 'data'))); + $this->assertEquals( + array('DoctrineNamespaceCacheKey[]'), + array_keys($this->readAttribute($cache['cache'], 'data')) + ); + } + + public function testRemovesExpiredResponses() + { + $cache = $this->getCache(); + $request = new Request('GET', 'http://xyz.com'); + $response = new Response(200, array('Age' => 1000, 'Cache-Control' => 'max-age=-10000')); + $cache['storage']->cache($request, $response); + $this->assertNull($cache['storage']->fetch($request)); + $data = $this->readAttribute($cache['cache'], 'data'); + $this->assertFalse(in_array('xyz.com', $data)); + $this->assertTrue(in_array($cache['serialized'], $data)); + } + + public function testUsesVaryToDetermineResult() + { + $cache = $this->getCache(); + $this->assertInstanceOf('Guzzle\Http\Message\Response', $cache['storage']->fetch($cache['request'])); + $request = new Request('GET', 'http://foo.com', array('Accept' => 'application/xml')); + $this->assertNull($cache['storage']->fetch($request)); + } + + public function testEnsuresResponseIsStillPresent() + { + $cache = $this->getCache(); + $data = $this->readAttribute($cache['cache'], 'data'); + $key = array_search('test', $data); + $cache['cache']->delete(substr($key, 1, -4)); + $this->assertNull($cache['storage']->fetch($cache['request'])); + } + + public function staleProvider() + { + return array( + array( + new Request('GET', 'http://foo.com', array('Accept' => 'foo')), + new Response(200, array('Cache-Control' => 'stale-if-error=100', 'Vary' => 'Accept')) + ), + array( + new Request('GET', 'http://foo.com', array('Accept' => 'foo')), + new Response(200, array('Cache-Control' => 'stale-if-error', 'Vary' => 'Accept')) + ) + ); + } + + /** + * @dataProvider staleProvider + */ + public function testUsesStaleTimeDirectiveForTtd($request, $response) + { + $cache = $this->getCache(); + $cache['storage']->cache($request, $response); + $data = $this->readAttribute($cache['cache'], 'data'); + foreach ($data as $v) { + if (strpos($v, 'foo.com')) { + $u = unserialize($v); + $this->assertGreaterThan($u[1][4], $u[0][4]); + break; + } + } + } + + public function testCanFilterCacheKeys() + { + $cache = $this->getCache(); + $cache['request']->getQuery()->set('auth', 'foo'); + $this->assertNull($cache['storage']->fetch($cache['request'])); + $cache['request']->getParams()->set('cache.key_filter', 'auth'); + $this->assertNotNull($cache['storage']->fetch($cache['request'])); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php new file mode 100644 index 0000000..de4d182 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultCanCacheStrategyTest.php @@ -0,0 +1,40 @@ +assertTrue($strategy->canCacheRequest($request)); + } + + public function testDoesNotCacheNoStore() + { + $strategy = new DefaultCanCacheStrategy(); + $request = new Request('GET', 'http://foo.com', array('cache-control' => 'no-store')); + $this->assertFalse($strategy->canCacheRequest($request)); + } + + public function testCanCacheResponse() + { + $response = $this->getMockBuilder('Guzzle\Http\Message\Response') + ->setMethods(array('canCache')) + ->setConstructorArgs(array(200)) + ->getMock(); + $response->expects($this->once()) + ->method('canCache') + ->will($this->returnValue(true)); + $strategy = new DefaultCanCacheStrategy(); + $this->assertTrue($strategy->canCacheResponse($response)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php new file mode 100644 index 0000000..0699cb2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DefaultRevalidationTest.php @@ -0,0 +1,248 @@ +getHttpDate('-100 hours') . "\r\nContent-Length: 4\r\n\r\nData", + "HTTP/1.1 304 NOT MODIFIED\r\nCache-Control: max-age=2000000\r\nContent-Length: 0\r\n\r\n", + ), + // Forces revalidation that overwrites what is in cache + array( + false, + "\r\n", + "HTTP/1.1 200 OK\r\nCache-Control: must-revalidate, no-cache\r\nDate: " . $this->getHttpDate('-10 hours') . "\r\nContent-Length: 4\r\n\r\nData", + "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nDatas", + "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nDate: " . $this->getHttpDate('now') . "\r\n\r\nDatas" + ), + // Throws an exception during revalidation + array( + false, + "\r\n", + "HTTP/1.1 200 OK\r\nCache-Control: no-cache\r\nDate: " . $this->getHttpDate('-3 hours') . "\r\n\r\nData", + "HTTP/1.1 500 INTERNAL SERVER ERROR\r\nContent-Length: 0\r\n\r\n" + ), + // ETag mismatch + array( + false, + "\r\n", + "HTTP/1.1 200 OK\r\nCache-Control: no-cache\r\nETag: \"123\"\r\nDate: " . $this->getHttpDate('-10 hours') . "\r\n\r\nData", + "HTTP/1.1 304 NOT MODIFIED\r\nETag: \"123456\"\r\n\r\n", + ), + ); + } + + /** + * @dataProvider cacheRevalidationDataProvider + */ + public function testRevalidatesResponsesAgainstOriginServer($can, $request, $response, $validate = null, $result = null) + { + // Send some responses to the test server for cache validation + $server = $this->getServer(); + $server->flush(); + + if ($validate) { + $server->enqueue($validate); + } + + $request = RequestFactory::getInstance()->fromMessage("GET / HTTP/1.1\r\nHost: 127.0.0.1:" . $server->getPort() . "\r\n" . $request); + $response = Response::fromMessage($response); + $request->setClient(new Client()); + + $plugin = new CachePlugin(new DoctrineCacheAdapter(new ArrayCache())); + $this->assertEquals( + $can, + $plugin->canResponseSatisfyRequest($request, $response), + '-> ' . $request . "\n" . $response + ); + + if ($result) { + $result = Response::fromMessage($result); + $result->removeHeader('Date'); + $request->getResponse()->removeHeader('Date'); + $request->getResponse()->removeHeader('Connection'); + // Get rid of dates + $this->assertEquals((string) $result, (string) $request->getResponse()); + } + + if ($validate) { + $this->assertEquals(1, count($server->getReceivedRequests())); + } + } + + public function testHandles404RevalidationResponses() + { + $request = new Request('GET', 'http://foo.com'); + $request->setClient(new Client()); + $badResponse = new Response(404, array(), 'Oh no!'); + $badRequest = clone $request; + $badRequest->setResponse($badResponse, true); + $response = new Response(200, array(), 'foo'); + + // Seed the cache + $s = new DefaultCacheStorage(new DoctrineCacheAdapter(new ArrayCache())); + $s->cache($request, $response); + $this->assertNotNull($s->fetch($request)); + + $rev = $this->getMockBuilder('Guzzle\Plugin\Cache\DefaultRevalidation') + ->setConstructorArgs(array($s)) + ->setMethods(array('createRevalidationRequest')) + ->getMock(); + + $rev->expects($this->once()) + ->method('createRevalidationRequest') + ->will($this->returnValue($badRequest)); + + try { + $rev->revalidate($request, $response); + $this->fail('Should have thrown an exception'); + } catch (BadResponseException $e) { + $this->assertSame($badResponse, $e->getResponse()); + $this->assertNull($s->fetch($request)); + } + } + + public function testCanRevalidateWithPlugin() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n" . + "Date: Mon, 12 Nov 2012 03:06:37 GMT\r\n" . + "Cache-Control: private, s-maxage=0, max-age=0, must-revalidate\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Content-Length: 2\r\n\r\nhi", + "HTTP/1.0 304 Not Modified\r\n" . + "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Age: 6302\r\n\r\n", + "HTTP/1.0 304 Not Modified\r\n" . + "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Age: 6302\r\n\r\n", + )); + $client = new Client($this->getServer()->getUrl()); + $client->addSubscriber(new CachePlugin()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(3, count($this->getServer()->getReceivedRequests())); + } + + public function testCanHandleRevalidationFailures() + { + $client = new Client($this->getServer()->getUrl()); + $lm = gmdate('c', time() - 60); + $mock = new MockPlugin(array( + new Response(200, array( + 'Date' => $lm, + 'Cache-Control' => 'max-age=100, must-revalidate, stale-if-error=9999', + 'Last-Modified' => $lm, + 'Content-Length' => 2 + ), 'hi'), + new CurlException('Bleh'), + new CurlException('Bleh') + )); + $client->addSubscriber(new CachePlugin()); + $client->addSubscriber($mock); + $client->get()->send(); + $response = $client->get()->send(); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('hi', $response->getBody(true)); + $this->assertEquals(3, count($mock->getReceivedRequests())); + $this->assertEquals(0, count($mock->getQueue())); + } + + public function testCanHandleStaleIfErrorWhenRevalidating() + { + $lm = gmdate('c', time() - 60); + $mock = new MockPlugin(array( + new Response(200, array( + 'Date' => $lm, + 'Cache-Control' => 'must-revalidate, max-age=0, stale-if-error=1200', + 'Last-Modified' => $lm, + 'Content-Length' => 2 + ), 'hi'), + new CurlException('Oh no!'), + new CurlException('Oh no!') + )); + $cache = new CachePlugin(); + $client = new Client('http://www.example.com'); + $client->addSubscriber($cache); + $client->addSubscriber($mock); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $response = $client->get()->send(); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertCount(0, $mock); + $this->assertEquals('HIT from GuzzleCache', (string) $response->getHeader('X-Cache-Lookup')); + $this->assertEquals('HIT_ERROR from GuzzleCache', (string) $response->getHeader('X-Cache')); + } + + /** + * @group issue-437 + */ + public function testDoesNotTouchClosureListeners() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n" . + "Date: Mon, 12 Nov 2012 03:06:37 GMT\r\n" . + "Cache-Control: private, s-maxage=0, max-age=0, must-revalidate\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Content-Length: 2\r\n\r\nhi", + "HTTP/1.0 304 Not Modified\r\n" . + "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Age: 6302\r\n\r\n", + "HTTP/1.0 304 Not Modified\r\n" . + "Date: Mon, 12 Nov 2012 03:06:38 GMT\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Last-Modified: Mon, 12 Nov 2012 02:53:38 GMT\r\n" . + "Age: 6302\r\n\r\n", + )); + $client = new Client($this->getServer()->getUrl()); + $client->addSubscriber(new CachePlugin()); + $client->getEventDispatcher()->addListener('command.after_send', function(){}); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + $this->assertEquals(200, $client->get()->send()->getStatusCode()); + } + +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php new file mode 100644 index 0000000..9af80f2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/DenyRevalidationTest.php @@ -0,0 +1,19 @@ +assertFalse($deny->revalidate(new Request('GET', 'http://foo.com'), new Response(200))); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php new file mode 100644 index 0000000..4bcc04b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cache/SkipRevalidationTest.php @@ -0,0 +1,19 @@ +assertTrue($skip->revalidate(new Request('GET', 'http://foo.com'), new Response(200))); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php new file mode 100644 index 0000000..5d0f668 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/ArrayCookieJarTest.php @@ -0,0 +1,385 @@ +jar = new ArrayCookieJar(); + } + + protected function getTestCookies() + { + return array( + new Cookie(array('name' => 'foo', 'value' => 'bar', 'domain' => 'foo.com', 'path' => '/', 'discard' => true)), + new Cookie(array('name' => 'test', 'value' => '123', 'domain' => 'baz.com', 'path' => '/foo', 'expires' => 2)), + new Cookie(array('name' => 'you', 'value' => '123', 'domain' => 'bar.com', 'path' => '/boo', 'expires' => time() + 1000)) + ); + } + + /** + * Provides test data for cookie cookieJar retrieval + */ + public function getCookiesDataProvider() + { + return array( + array(array('foo', 'baz', 'test', 'muppet', 'googoo'), '', '', '', false), + array(array('foo', 'baz', 'muppet', 'googoo'), '', '', '', true), + array(array('googoo'), 'www.example.com', '', '', false), + array(array('muppet', 'googoo'), 'test.y.example.com', '', '', false), + array(array('foo', 'baz'), 'example.com', '', '', false), + array(array('muppet'), 'x.y.example.com', '/acme/', '', false), + array(array('muppet'), 'x.y.example.com', '/acme/test/', '', false), + array(array('googoo'), 'x.y.example.com', '/test/acme/test/', '', false), + array(array('foo', 'baz'), 'example.com', '', '', false), + array(array('baz'), 'example.com', '', 'baz', false), + ); + } + + public function testStoresAndRetrievesCookies() + { + $cookies = $this->getTestCookies(); + foreach ($cookies as $cookie) { + $this->assertTrue($this->jar->add($cookie)); + } + + $this->assertEquals(3, count($this->jar)); + $this->assertEquals(3, count($this->jar->getIterator())); + $this->assertEquals($cookies, $this->jar->all(null, null, null, false, false)); + } + + public function testRemovesExpiredCookies() + { + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->add($cookie); + } + $this->jar->removeExpired(); + $this->assertEquals(array($cookies[0], $cookies[2]), $this->jar->all()); + } + + public function testRemovesTemporaryCookies() + { + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->add($cookie); + } + $this->jar->removeTemporary(); + $this->assertEquals(array($cookies[2]), $this->jar->all()); + } + + public function testIsSerializable() + { + $this->assertEquals('[]', $this->jar->serialize()); + $this->jar->unserialize('[]'); + $this->assertEquals(array(), $this->jar->all()); + + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->add($cookie); + } + + // Remove discard and expired cookies + $serialized = $this->jar->serialize(); + $data = json_decode($serialized, true); + $this->assertEquals(1, count($data)); + + $a = new ArrayCookieJar(); + $a->unserialize($serialized); + $this->assertEquals(1, count($a)); + } + + public function testRemovesSelectively() + { + $cookies = $this->getTestCookies(); + foreach ($this->getTestCookies() as $cookie) { + $this->jar->add($cookie); + } + + // Remove foo.com cookies + $this->jar->remove('foo.com'); + $this->assertEquals(2, count($this->jar)); + // Try again, removing no further cookies + $this->jar->remove('foo.com'); + $this->assertEquals(2, count($this->jar)); + + // Remove bar.com cookies with path of /boo + $this->jar->remove('bar.com', '/boo'); + $this->assertEquals(1, count($this->jar)); + + // Remove cookie by name + $this->jar->remove(null, null, 'test'); + $this->assertEquals(0, count($this->jar)); + } + + public function testDoesNotAddIncompleteCookies() + { + $this->assertEquals(false, $this->jar->add(new Cookie())); + $this->assertFalse($this->jar->add(new Cookie(array( + 'name' => 'foo' + )))); + $this->assertFalse($this->jar->add(new Cookie(array( + 'name' => false + )))); + $this->assertFalse($this->jar->add(new Cookie(array( + 'name' => true + )))); + $this->assertFalse($this->jar->add(new Cookie(array( + 'name' => 'foo', + 'domain' => 'foo.com' + )))); + } + + public function testDoesAddValidCookies() + { + $this->assertTrue($this->jar->add(new Cookie(array( + 'name' => 'foo', + 'domain' => 'foo.com', + 'value' => 0 + )))); + $this->assertTrue($this->jar->add(new Cookie(array( + 'name' => 'foo', + 'domain' => 'foo.com', + 'value' => 0.0 + )))); + $this->assertTrue($this->jar->add(new Cookie(array( + 'name' => 'foo', + 'domain' => 'foo.com', + 'value' => '0' + )))); + } + + public function testOverwritesCookiesThatAreOlderOrDiscardable() + { + $t = time() + 1000; + $data = array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'max_age' => '86400', + 'port' => array(80, 8080), + 'version' => '1', + 'secure' => true, + 'discard' => true, + 'expires' => $t + ); + + // Make sure that the discard cookie is overridden with the non-discard + $this->assertTrue($this->jar->add(new Cookie($data))); + + unset($data['discard']); + $this->assertTrue($this->jar->add(new Cookie($data))); + $this->assertEquals(1, count($this->jar)); + + $c = $this->jar->all(); + $this->assertEquals(false, $c[0]->getDiscard()); + + // Make sure it doesn't duplicate the cookie + $this->jar->add(new Cookie($data)); + $this->assertEquals(1, count($this->jar)); + + // Make sure the more future-ful expiration date supersede the other + $data['expires'] = time() + 2000; + $this->assertTrue($this->jar->add(new Cookie($data))); + $this->assertEquals(1, count($this->jar)); + $c = $this->jar->all(); + $this->assertNotEquals($t, $c[0]->getExpires()); + } + + public function testOverwritesCookiesThatHaveChanged() + { + $t = time() + 1000; + $data = array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => '.example.com', + 'path' => '/', + 'max_age' => '86400', + 'port' => array(80, 8080), + 'version' => '1', + 'secure' => true, + 'discard' => true, + 'expires' => $t + ); + + // Make sure that the discard cookie is overridden with the non-discard + $this->assertTrue($this->jar->add(new Cookie($data))); + + $data['value'] = 'boo'; + $this->assertTrue($this->jar->add(new Cookie($data))); + $this->assertEquals(1, count($this->jar)); + + // Changing the value plus a parameter also must overwrite the existing one + $data['value'] = 'zoo'; + $data['secure'] = false; + $this->assertTrue($this->jar->add(new Cookie($data))); + $this->assertEquals(1, count($this->jar)); + + $c = $this->jar->all(); + $this->assertEquals('zoo', $c[0]->getValue()); + } + + public function testAddsCookiesFromResponseWithNoRequest() + { + $response = new Response(200, array( + 'Set-Cookie' => array( + "fpc=d=.Hm.yh4.1XmJWjJfs4orLQzKzPImxklQoxXSHOZATHUSEFciRueW_7704iYUtsXNEXq0M92Px2glMdWypmJ7HIQl6XIUvrZimWjQ3vIdeuRbI.FNQMAfcxu_XN1zSx7l.AcPdKL6guHc2V7hIQFhnjRW0rxm2oHY1P4bGQxFNz7f.tHm12ZD3DbdMDiDy7TBXsuP4DM-&v=2; expires=Fri, 02-Mar-2019 02:17:40 GMT; path=/; domain=127.0.0.1", + "FPCK3=AgBNbvoQAGpGEABZLRAAbFsQAF1tEABkDhAAeO0=; expires=Sat, 02-Apr-2019 02:17:40 GMT; path=/; domain=127.0.0.1", + "CH=deleted; expires=Wed, 03-Mar-2010 02:17:39 GMT; path=/; domain=127.0.0.1", + "CH=AgBNbvoQAAEcEAApuhAAMJcQADQvEAAvGxAALe0QAD6uEAATwhAAC1AQAC8t; expires=Sat, 02-Apr-2019 02:17:40 GMT; path=/; domain=127.0.0.1" + ) + )); + + $this->jar->addCookiesFromResponse($response); + $this->assertEquals(3, count($this->jar)); + $this->assertEquals(1, count($this->jar->all(null, null, 'fpc'))); + $this->assertEquals(1, count($this->jar->all(null, null, 'FPCK3'))); + $this->assertEquals(1, count($this->jar->all(null, null, 'CH'))); + } + + public function testAddsCookiesFromResponseWithRequest() + { + $response = new Response(200, array( + 'Set-Cookie' => "fpc=d=.Hm.yh4.1XmJWjJfs4orLQzKzPImxklQoxXSHOZATHUSEFciRueW_7704iYUtsXNEXq0M92Px2glMdWypmJ7HIQl6XIUvrZimWjQ3vIdeuRbI.FNQMAfcxu_XN1zSx7l.AcPdKL6guHc2V7hIQFhnjRW0rxm2oHY1P4bGQxFNz7f.tHm12ZD3DbdMDiDy7TBXsuP4DM-&v=2; expires=Fri, 02-Mar-2019 02:17:40 GMT;" + )); + $request = new Request('GET', 'http://www.example.com'); + $this->jar->addCookiesFromResponse($response, $request); + $this->assertEquals(1, count($this->jar)); + } + + public function getMatchingCookiesDataProvider() + { + return array( + array('https://example.com', array(0)), + array('http://example.com', array()), + array('https://example.com:8912', array()), + array('https://foo.example.com', array(0)), + array('http://foo.example.com/test/acme/', array(4)) + ); + } + + /** + * @dataProvider getMatchingCookiesDataProvider + */ + public function testReturnsCookiesMatchingRequests($url, $cookies) + { + $bag = array( + new Cookie(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => 'example.com', + 'path' => '/', + 'max_age' => '86400', + 'port' => array(443, 8080), + 'version' => '1', + 'secure' => true + )), + new Cookie(array( + 'name' => 'baz', + 'value' => 'foobar', + 'domain' => 'example.com', + 'path' => '/', + 'max_age' => '86400', + 'port' => array(80, 8080), + 'version' => '1', + 'secure' => true + )), + new Cookie(array( + 'name' => 'test', + 'value' => '123', + 'domain' => 'www.foobar.com', + 'path' => '/path/', + 'discard' => true + )), + new Cookie(array( + 'name' => 'muppet', + 'value' => 'cookie_monster', + 'domain' => '.y.example.com', + 'path' => '/acme/', + 'comment' => 'Comment goes here...', + 'expires' => time() + 86400 + )), + new Cookie(array( + 'name' => 'googoo', + 'value' => 'gaga', + 'domain' => '.example.com', + 'path' => '/test/acme/', + 'max_age' => 1500, + 'version' => 2 + )) + ); + + foreach ($bag as $cookie) { + $this->jar->add($cookie); + } + + $request = new Request('GET', $url); + $results = $this->jar->getMatchingCookies($request); + $this->assertEquals(count($cookies), count($results)); + foreach ($cookies as $i) { + $this->assertContains($bag[$i], $results); + } + } + + /** + * @expectedException \Guzzle\Plugin\Cookie\Exception\InvalidCookieException + * @expectedExceptionMessage The cookie name must not contain invalid characters: abc:@123 + */ + public function testThrowsExceptionWithStrictMode() + { + $a = new ArrayCookieJar(); + $a->setStrictMode(true); + $a->add(new Cookie(array( + 'name' => 'abc:@123', + 'value' => 'foo', + 'domain' => 'bar' + ))); + } + + public function testRemoveExistingCookieIfEmpty() + { + // Add a cookie that should not be affected + $a = new Cookie(array( + 'name' => 'foo', + 'value' => 'nope', + 'domain' => 'foo.com', + 'path' => '/abc' + )); + $this->jar->add($a); + + $data = array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => 'foo.com', + 'path' => '/' + ); + + $b = new Cookie($data); + $this->assertTrue($this->jar->add($b)); + $this->assertEquals(2, count($this->jar)); + + // Try to re-set the same cookie with no value: assert that cookie is not added + $data['value'] = null; + $this->assertFalse($this->jar->add(new Cookie($data))); + // assert that original cookie has been deleted + $cookies = $this->jar->all('foo.com'); + $this->assertTrue(in_array($a, $cookies, true)); + $this->assertFalse(in_array($b, $cookies, true)); + $this->assertEquals(1, count($this->jar)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php new file mode 100644 index 0000000..ac9471f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieJar/FileCookieJarTest.php @@ -0,0 +1,63 @@ +file = tempnam('/tmp', 'file-cookies'); + } + + public function testLoadsFromFileFile() + { + $jar = new FileCookieJar($this->file); + $this->assertEquals(array(), $jar->all()); + unlink($this->file); + } + + public function testPersistsToFileFile() + { + $jar = new FileCookieJar($this->file); + $jar->add(new Cookie(array( + 'name' => 'foo', + 'value' => 'bar', + 'domain' => 'foo.com', + 'expires' => time() + 1000 + ))); + $jar->add(new Cookie(array( + 'name' => 'baz', + 'value' => 'bar', + 'domain' => 'foo.com', + 'expires' => time() + 1000 + ))); + $jar->add(new Cookie(array( + 'name' => 'boo', + 'value' => 'bar', + 'domain' => 'foo.com', + ))); + + $this->assertEquals(3, count($jar)); + unset($jar); + + // Make sure it wrote to the file + $contents = file_get_contents($this->file); + $this->assertNotEmpty($contents); + + // Load the cookieJar from the file + $jar = new FileCookieJar($this->file); + + // Weeds out temporary and session cookies + $this->assertEquals(2, count($jar)); + unset($jar); + unlink($this->file); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php new file mode 100644 index 0000000..f8c175c --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookiePluginTest.php @@ -0,0 +1,134 @@ +getMockBuilder('Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar') + ->setMethods(array('addCookiesFromResponse')) + ->getMock(); + + $mock->expects($this->exactly(1)) + ->method('addCookiesFromResponse') + ->with($response); + + $plugin = new CookiePlugin($mock); + $plugin->onRequestSent(new Event(array( + 'response' => $response + ))); + } + + public function testAddsCookiesToRequests() + { + $cookie = new Cookie(array( + 'name' => 'foo', + 'value' => 'bar' + )); + + $mock = $this->getMockBuilder('Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar') + ->setMethods(array('getMatchingCookies')) + ->getMock(); + + $mock->expects($this->once()) + ->method('getMatchingCookies') + ->will($this->returnValue(array($cookie))); + + $plugin = new CookiePlugin($mock); + + $client = new Client(); + $client->getEventDispatcher()->addSubscriber($plugin); + + $request = $client->get('http://www.example.com'); + $plugin->onRequestBeforeSend(new Event(array( + 'request' => $request + ))); + + $this->assertEquals('bar', $request->getCookie('foo')); + } + + public function testCookiesAreExtractedFromRedirectResponses() + { + $plugin = new CookiePlugin(new ArrayCookieJar()); + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 302 Moved Temporarily\r\n" . + "Set-Cookie: test=583551; expires=Wednesday, 23-Mar-2050 19:49:45 GMT; path=/\r\n" . + "Location: /redirect\r\n\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Length: 0\r\n\r\n", + "HTTP/1.1 200 OK\r\n" . + "Content-Length: 0\r\n\r\n" + )); + + $client = new Client($this->getServer()->getUrl()); + $client->getEventDispatcher()->addSubscriber($plugin); + + $client->get()->send(); + $request = $client->get(); + $request->send(); + $this->assertEquals('test=583551', $request->getHeader('Cookie')); + + $requests = $this->getServer()->getReceivedRequests(true); + // Confirm subsequent requests have the cookie. + $this->assertEquals('test=583551', $requests[2]->getHeader('Cookie')); + // Confirm the redirected request has the cookie. + $this->assertEquals('test=583551', $requests[1]->getHeader('Cookie')); + } + + public function testCookiesAreNotAddedWhenParamIsSet() + { + $jar = new ArrayCookieJar(); + $plugin = new CookiePlugin($jar); + + $jar->add(new Cookie(array( + 'domain' => 'example.com', + 'path' => '/', + 'name' => 'test', + 'value' => 'hi', + 'expires' => time() + 3600 + ))); + + $client = new Client('http://example.com'); + $client->getEventDispatcher()->addSubscriber($plugin); + + // Ensure that it is normally added + $request = $client->get(); + $request->setResponse(new Response(200), true); + $request->send(); + $this->assertEquals('hi', $request->getCookie('test')); + + // Now ensure that it is not added + $request = $client->get(); + $request->getParams()->set('cookies.disable', true); + $request->setResponse(new Response(200), true); + $request->send(); + $this->assertNull($request->getCookie('test')); + } + + public function testProvidesCookieJar() + { + $jar = new ArrayCookieJar(); + $plugin = new CookiePlugin($jar); + $this->assertSame($jar, $plugin->getCookieJar()); + } + + public function testEscapesCookieDomains() + { + $cookie = new Cookie(array('domain' => '/foo/^$[A-Z]+/')); + $this->assertFalse($cookie->matchesDomain('foo')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php new file mode 100644 index 0000000..9fb0b43 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Cookie/CookieTest.php @@ -0,0 +1,223 @@ +assertEquals('/', $cookie->getPath()); + $this->assertEquals(array(), $cookie->getPorts()); + } + + public function testConvertsDateTimeMaxAgeToUnixTimestamp() + { + $cookie = new Cookie(array( + 'expires' => 'November 20, 1984' + )); + $this->assertTrue(is_numeric($cookie->getExpires())); + } + + public function testAddsExpiresBasedOnMaxAge() + { + $t = time(); + $cookie = new Cookie(array( + 'max_age' => 100 + )); + $this->assertEquals($t + 100, $cookie->getExpires()); + } + + public function testHoldsValues() + { + $t = time(); + $data = array( + 'name' => 'foo', + 'value' => 'baz', + 'path' => '/bar', + 'domain' => 'baz.com', + 'expires' => $t, + 'max_age' => 100, + 'comment' => 'Hi', + 'comment_url' => 'foo.com', + 'port' => array(1, 2), + 'version' => 2, + 'secure' => true, + 'discard' => true, + 'http_only' => true, + 'data' => array( + 'foo' => 'baz', + 'bar' => 'bam' + ) + ); + + $cookie = new Cookie($data); + $this->assertEquals($data, $cookie->toArray()); + + $this->assertEquals('foo', $cookie->getName()); + $this->assertEquals('baz', $cookie->getValue()); + $this->assertEquals('baz.com', $cookie->getDomain()); + $this->assertEquals('/bar', $cookie->getPath()); + $this->assertEquals($t, $cookie->getExpires()); + $this->assertEquals(100, $cookie->getMaxAge()); + $this->assertEquals('Hi', $cookie->getComment()); + $this->assertEquals('foo.com', $cookie->getCommentUrl()); + $this->assertEquals(array(1, 2), $cookie->getPorts()); + $this->assertEquals(2, $cookie->getVersion()); + $this->assertTrue($cookie->getSecure()); + $this->assertTrue($cookie->getDiscard()); + $this->assertTrue($cookie->getHttpOnly()); + $this->assertEquals('baz', $cookie->getAttribute('foo')); + $this->assertEquals('bam', $cookie->getAttribute('bar')); + $this->assertEquals(array( + 'foo' => 'baz', + 'bar' => 'bam' + ), $cookie->getAttributes()); + + $cookie->setName('a') + ->setValue('b') + ->setPath('c') + ->setDomain('bar.com') + ->setExpires(10) + ->setMaxAge(200) + ->setComment('e') + ->setCommentUrl('f') + ->setPorts(array(80)) + ->setVersion(3) + ->setSecure(false) + ->setHttpOnly(false) + ->setDiscard(false) + ->setAttribute('snoop', 'dog'); + + $this->assertEquals('a', $cookie->getName()); + $this->assertEquals('b', $cookie->getValue()); + $this->assertEquals('c', $cookie->getPath()); + $this->assertEquals('bar.com', $cookie->getDomain()); + $this->assertEquals(10, $cookie->getExpires()); + $this->assertEquals(200, $cookie->getMaxAge()); + $this->assertEquals('e', $cookie->getComment()); + $this->assertEquals('f', $cookie->getCommentUrl()); + $this->assertEquals(array(80), $cookie->getPorts()); + $this->assertEquals(3, $cookie->getVersion()); + $this->assertFalse($cookie->getSecure()); + $this->assertFalse($cookie->getDiscard()); + $this->assertFalse($cookie->getHttpOnly()); + $this->assertEquals('dog', $cookie->getAttribute('snoop')); + } + + public function testDeterminesIfExpired() + { + $c = new Cookie(); + $c->setExpires(10); + $this->assertTrue($c->isExpired()); + $c->setExpires(time() + 10000); + $this->assertFalse($c->isExpired()); + } + + public function testMatchesPorts() + { + $cookie = new Cookie(); + // Always matches when nothing is set + $this->assertTrue($cookie->matchesPort(2)); + + $cookie->setPorts(array(1, 2)); + $this->assertTrue($cookie->matchesPort(2)); + $this->assertFalse($cookie->matchesPort(100)); + } + + public function testMatchesDomain() + { + $cookie = new Cookie(); + $this->assertTrue($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('baz.com'); + $this->assertTrue($cookie->matchesDomain('baz.com')); + $this->assertFalse($cookie->matchesDomain('bar.com')); + + $cookie->setDomain('.baz.com'); + $this->assertTrue($cookie->matchesDomain('.baz.com')); + $this->assertTrue($cookie->matchesDomain('foo.baz.com')); + $this->assertFalse($cookie->matchesDomain('baz.bar.com')); + $this->assertTrue($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('.127.0.0.1'); + $this->assertTrue($cookie->matchesDomain('127.0.0.1')); + + $cookie->setDomain('127.0.0.1'); + $this->assertTrue($cookie->matchesDomain('127.0.0.1')); + + $cookie->setDomain('.com.'); + $this->assertFalse($cookie->matchesDomain('baz.com')); + + $cookie->setDomain('.local'); + $this->assertTrue($cookie->matchesDomain('example.local')); + } + + public function testMatchesPath() + { + $cookie = new Cookie(); + $this->assertTrue($cookie->matchesPath('/foo')); + + $cookie->setPath('/foo'); + + // o The cookie-path and the request-path are identical. + $this->assertTrue($cookie->matchesPath('/foo')); + $this->assertFalse($cookie->matchesPath('/bar')); + + // o 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. + $this->assertTrue($cookie->matchesPath('/foo/bar')); + $this->assertFalse($cookie->matchesPath('/fooBar')); + + // o The cookie-path is a prefix of the request-path, and the last + // character of the cookie-path is %x2F ("/"). + $cookie->setPath('/foo/'); + $this->assertTrue($cookie->matchesPath('/foo/bar')); + $this->assertFalse($cookie->matchesPath('/fooBaz')); + $this->assertFalse($cookie->matchesPath('/foo')); + + } + + public function cookieValidateProvider() + { + return array( + array('foo', 'baz', 'bar', true), + array('0', '0', '0', true), + array('', 'baz', 'bar', 'The cookie name must not be empty'), + array('foo', '', 'bar', 'The cookie value must not be empty'), + array('foo', 'baz', '', 'The cookie domain must not be empty'), + array('foo\\', 'baz', '0', 'The cookie name must not contain invalid characters: foo\\'), + ); + } + + /** + * @dataProvider cookieValidateProvider + */ + public function testValidatesCookies($name, $value, $domain, $result) + { + $cookie = new Cookie(array( + 'name' => $name, + 'value' => $value, + 'domain' => $domain + )); + $this->assertSame($result, $cookie->validate()); + } + + public function testCreatesInvalidCharacterString() + { + $m = new \ReflectionMethod('Guzzle\Plugin\Cookie\Cookie', 'getInvalidCharacters'); + $m->setAccessible(true); + $p = new \ReflectionProperty('Guzzle\Plugin\Cookie\Cookie', 'invalidCharString'); + $p->setAccessible(true); + $p->setValue(''); + // Expects a string containing 51 invalid characters + $this->assertEquals(51, strlen($m->invoke($m))); + $this->assertContains('@', $m->invoke($m)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php new file mode 100644 index 0000000..2a4b49e --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/CurlAuth/CurlAuthPluginTest.php @@ -0,0 +1,39 @@ +getEventDispatcher()->addSubscriber($plugin); + $request = $client->get('/'); + $this->assertEquals('michael', $request->getUsername()); + $this->assertEquals('test', $request->getPassword()); + Version::$emitWarnings = true; + } + + public function testAddsDigestAuthentication() + { + Version::$emitWarnings = false; + $plugin = new CurlAuthPlugin('julian', 'test', CURLAUTH_DIGEST); + $client = new Client('http://www.test.com/'); + $client->getEventDispatcher()->addSubscriber($plugin); + $request = $client->get('/'); + $this->assertEquals('julian', $request->getUsername()); + $this->assertEquals('test', $request->getPassword()); + $this->assertEquals('julian:test', $request->getCurlOptions()->get(CURLOPT_USERPWD)); + $this->assertEquals(CURLAUTH_DIGEST, $request->getCurlOptions()->get(CURLOPT_HTTPAUTH)); + Version::$emitWarnings = true; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php new file mode 100644 index 0000000..6f94186 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/ErrorResponse/ErrorResponsePluginTest.php @@ -0,0 +1,137 @@ +flush(); + } + + public function setUp() + { + $mockError = 'Guzzle\Tests\Mock\ErrorResponseMock'; + $description = ServiceDescription::factory(array( + 'operations' => array( + 'works' => array( + 'httpMethod' => 'GET', + 'errorResponses' => array( + array('code' => 500, 'class' => $mockError), + array('code' => 503, 'reason' => 'foo', 'class' => $mockError), + array('code' => 200, 'reason' => 'Error!', 'class' => $mockError) + ) + ), + 'bad_class' => array( + 'httpMethod' => 'GET', + 'errorResponses' => array( + array('code' => 500, 'class' => 'Does\\Not\\Exist') + ) + ), + 'does_not_implement' => array( + 'httpMethod' => 'GET', + 'errorResponses' => array( + array('code' => 500, 'class' => __CLASS__) + ) + ), + 'no_errors' => array('httpMethod' => 'GET'), + 'no_class' => array( + 'httpMethod' => 'GET', + 'errorResponses' => array( + array('code' => 500) + ) + ), + ) + )); + $this->client = new Client($this->getServer()->getUrl()); + $this->client->setDescription($description); + } + + /** + * @expectedException \Guzzle\Http\Exception\ServerErrorResponseException + */ + public function testSkipsWhenErrorResponsesIsNotSet() + { + $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('no_errors')->execute(); + } + + public function testSkipsWhenErrorResponsesIsNotSetAndAllowsSuccess() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('no_errors')->execute(); + } + + /** + * @expectedException \Guzzle\Plugin\ErrorResponse\Exception\ErrorResponseException + * @expectedExceptionMessage Does\Not\Exist does not exist + */ + public function testEnsuresErrorResponseExists() + { + $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('bad_class')->execute(); + } + + /** + * @expectedException \Guzzle\Plugin\ErrorResponse\Exception\ErrorResponseException + * @expectedExceptionMessage must implement Guzzle\Plugin\ErrorResponse\ErrorResponseExceptionInterface + */ + public function testEnsuresErrorResponseImplementsInterface() + { + $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('does_not_implement')->execute(); + } + + public function testThrowsSpecificErrorResponseOnMatch() + { + try { + $this->getServer()->enqueue("HTTP/1.1 500 Foo\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $command = $this->client->getCommand('works'); + $command->execute(); + $this->fail('Exception not thrown'); + } catch (ErrorResponseMock $e) { + $this->assertSame($command, $e->command); + $this->assertEquals(500, $e->response->getStatusCode()); + } + } + + /** + * @expectedException \Guzzle\Tests\Mock\ErrorResponseMock + */ + public function testThrowsWhenCodeAndPhraseMatch() + { + $this->getServer()->enqueue("HTTP/1.1 200 Error!\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('works')->execute(); + } + + public function testSkipsWhenReasonDoesNotMatch() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('works')->execute(); + } + + public function testSkipsWhenNoClassIsSet() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $this->client->addSubscriber(new ErrorResponsePlugin()); + $this->client->getCommand('no_class')->execute(); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php new file mode 100644 index 0000000..41aa673 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/History/HistoryPluginTest.php @@ -0,0 +1,140 @@ +get(); + $requests[$i]->setResponse(new Response(200), true); + $requests[$i]->send(); + $h->add($requests[$i]); + } + + return $requests; + } + + public function testDescribesSubscribedEvents() + { + $this->assertInternalType('array', HistoryPlugin::getSubscribedEvents()); + } + + public function testMaintainsLimitValue() + { + $h = new HistoryPlugin(); + $this->assertSame($h, $h->setLimit(10)); + $this->assertEquals(10, $h->getLimit()); + } + + public function testAddsRequests() + { + $h = new HistoryPlugin(); + $requests = $this->addRequests($h, 1); + $this->assertEquals(1, count($h)); + $i = $h->getIterator(); + $this->assertEquals(1, count($i)); + $this->assertEquals($requests[0], $i[0]); + } + + /** + * @depends testAddsRequests + */ + public function testMaintainsLimit() + { + $h = new HistoryPlugin(); + $h->setLimit(2); + $requests = $this->addRequests($h, 3); + $this->assertEquals(2, count($h)); + $i = 0; + foreach ($h as $request) { + if ($i > 0) { + $this->assertSame($requests[$i], $request); + } + } + } + + public function testReturnsLastRequest() + { + $h = new HistoryPlugin(); + $requests = $this->addRequests($h, 5); + $this->assertSame(end($requests), $h->getLastRequest()); + } + + public function testReturnsLastResponse() + { + $h = new HistoryPlugin(); + $requests = $this->addRequests($h, 5); + $this->assertSame(end($requests)->getResponse(), $h->getLastResponse()); + } + + public function testClearsHistory() + { + $h = new HistoryPlugin(); + $requests = $this->addRequests($h, 5); + $this->assertEquals(5, count($h)); + $h->clear(); + $this->assertEquals(0, count($h)); + } + + /** + * @depends testAddsRequests + */ + public function testUpdatesAddRequests() + { + $h = new HistoryPlugin(); + $client = new Client('http://127.0.0.1/'); + $client->getEventDispatcher()->addSubscriber($h); + + $request = $client->get(); + $request->setResponse(new Response(200), true); + $request->send(); + + $this->assertSame($request, $h->getLastRequest()); + } + + public function testCanCastToString() + { + $client = new Client('http://127.0.0.1/'); + $h = new HistoryPlugin(); + $client->getEventDispatcher()->addSubscriber($h); + + $mock = new MockPlugin(array( + new Response(301, array('Location' => '/redirect1', 'Content-Length' => 0)), + new Response(307, array('Location' => '/redirect2', 'Content-Length' => 0)), + new Response(200, array('Content-Length' => '2'), 'HI') + )); + + $client->getEventDispatcher()->addSubscriber($mock); + $request = $client->get(); + $request->send(); + $this->assertEquals(3, count($h)); + $this->assertEquals(3, count($mock->getReceivedRequests())); + + $h = str_replace("\r", '', $h); + $this->assertContains("> GET / HTTP/1.1\nHost: 127.0.0.1\nUser-Agent:", $h); + $this->assertContains("< HTTP/1.1 301 Moved Permanently\nLocation: /redirect1", $h); + $this->assertContains("< HTTP/1.1 307 Temporary Redirect\nLocation: /redirect2", $h); + $this->assertContains("< HTTP/1.1 200 OK\nContent-Length: 2\n\nHI", $h); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php new file mode 100644 index 0000000..ad663a5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Log/LogPluginTest.php @@ -0,0 +1,95 @@ +adapter = new ClosureLogAdapter(function ($message) { + echo $message; + }); + } + + public function testIgnoresCurlEventsWhenNotWiringBodies() + { + $p = new LogPlugin($this->adapter); + $this->assertNotEmpty($p->getSubscribedEvents()); + $event = new Event(array('request' => new Request('GET', 'http://foo.com'))); + $p->onCurlRead($event); + $p->onCurlWrite($event); + $p->onRequestBeforeSend($event); + } + + public function testLogsWhenComplete() + { + $output = ''; + $p = new LogPlugin(new ClosureLogAdapter(function ($message) use (&$output) { + $output = $message; + }), '{method} {resource} | {code} {res_body}'); + + $p->onRequestSent(new Event(array( + 'request' => new Request('GET', 'http://foo.com'), + 'response' => new Response(200, array(), 'Foo') + ))); + + $this->assertEquals('GET / | 200 Foo', $output); + } + + public function testWiresBodiesWhenNeeded() + { + $client = new Client($this->getServer()->getUrl()); + $plugin = new LogPlugin($this->adapter, '{req_body} | {res_body}', true); + $client->getEventDispatcher()->addSubscriber($plugin); + $request = $client->put(); + + // Send the response from the dummy server as the request body + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nsend"); + $stream = fopen($this->getServer()->getUrl(), 'r'); + $request->setBody(EntityBody::factory($stream, 4)); + + $tmpFile = tempnam(sys_get_temp_dir(), 'non_repeatable'); + $request->setResponseBody(EntityBody::factory(fopen($tmpFile, 'w'))); + + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nresponse"); + + ob_start(); + $request->send(); + $message = ob_get_clean(); + + unlink($tmpFile); + $this->assertContains("send", $message); + $this->assertContains("response", $message); + } + + public function testHasHelpfulStaticFactoryMethod() + { + $s = fopen('php://temp', 'r+'); + $client = new Client(); + $client->addSubscriber(LogPlugin::getDebugPlugin(true, $s)); + $request = $client->put('http://foo.com', array('Content-Type' => 'Foo'), 'Bar'); + $request->setresponse(new Response(200), true); + $request->send(); + rewind($s); + $contents = stream_get_contents($s); + $this->assertContains('# Request:', $contents); + $this->assertContainsIns('PUT / HTTP/1.1', $contents); + $this->assertContains('# Response:', $contents); + $this->assertContainsIns('HTTP/1.1 200 OK', $contents); + $this->assertContains('# Errors:', $contents); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php new file mode 100644 index 0000000..4bd4111 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/CommandContentMd5PluginTest.php @@ -0,0 +1,97 @@ + array( + 'test' => array( + 'httpMethod' => 'PUT', + 'parameters' => array( + 'ContentMD5' => array(), + 'Body' => array( + 'location' => 'body' + ) + ) + ) + ) + )); + + $client = new Client(); + $client->setDescription($description); + + return $client; + } + + public function testHasEvents() + { + $this->assertNotEmpty(CommandContentMd5Plugin::getSubscribedEvents()); + } + + public function testValidatesMd5WhenParamExists() + { + $client = $this->getClient(); + $command = $client->getCommand('test', array( + 'Body' => 'Foo', + 'ContentMD5' => true + )); + $event = new Event(array('command' => $command)); + $request = $command->prepare(); + $plugin = new CommandContentMd5Plugin(); + $plugin->onCommandBeforeSend($event); + $this->assertEquals('E1bGfXrRY42Ba/uCLdLCXQ==', (string) $request->getHeader('Content-MD5')); + } + + public function testDoesNothingWhenNoPayloadExists() + { + $client = $this->getClient(); + $client->getDescription()->getOperation('test')->setHttpMethod('GET'); + $command = $client->getCommand('test'); + $event = new Event(array('command' => $command)); + $request = $command->prepare(); + $plugin = new CommandContentMd5Plugin(); + $plugin->onCommandBeforeSend($event); + $this->assertNull($request->getHeader('Content-MD5')); + } + + public function testAddsValidationToResponsesOfContentMd5() + { + $client = $this->getClient(); + $client->getDescription()->getOperation('test')->setHttpMethod('GET'); + $command = $client->getCommand('test', array( + 'ValidateMD5' => true + )); + $event = new Event(array('command' => $command)); + $request = $command->prepare(); + $plugin = new CommandContentMd5Plugin(); + $plugin->onCommandBeforeSend($event); + $listeners = $request->getEventDispatcher()->getListeners('request.complete'); + $this->assertNotEmpty($listeners); + } + + public function testIgnoresValidationWhenDisabled() + { + $client = $this->getClient(); + $client->getDescription()->getOperation('test')->setHttpMethod('GET'); + $command = $client->getCommand('test', array( + 'ValidateMD5' => false + )); + $event = new Event(array('command' => $command)); + $request = $command->prepare(); + $plugin = new CommandContentMd5Plugin(); + $plugin->onCommandBeforeSend($event); + $listeners = $request->getEventDispatcher()->getListeners('request.complete'); + $this->assertEmpty($listeners); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php new file mode 100644 index 0000000..482e92b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Md5/Md5ValidatorPluginTest.php @@ -0,0 +1,120 @@ +create('GET', 'http://www.test.com/'); + $request->getEventDispatcher()->addSubscriber($plugin); + + $body = 'abc'; + $hash = md5($body); + $response = new Response(200, array( + 'Content-MD5' => $hash, + 'Content-Length' => 3 + ), 'abc'); + + $request->dispatch('request.complete', array( + 'response' => $response + )); + + // Try again with no Content-MD5 + $response->removeHeader('Content-MD5'); + $request->dispatch('request.complete', array( + 'response' => $response + )); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testThrowsExceptionOnInvalidMd5() + { + $plugin = new Md5ValidatorPlugin(); + $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/'); + $request->getEventDispatcher()->addSubscriber($plugin); + + $request->dispatch('request.complete', array( + 'response' => new Response(200, array( + 'Content-MD5' => 'foobar', + 'Content-Length' => 3 + ), 'abc') + )); + } + + public function testSkipsWhenContentLengthIsTooLarge() + { + $plugin = new Md5ValidatorPlugin(false, 1); + $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/'); + $request->getEventDispatcher()->addSubscriber($plugin); + + $request->dispatch('request.complete', array( + 'response' => new Response(200, array( + 'Content-MD5' => 'foobar', + 'Content-Length' => 3 + ), 'abc') + )); + } + + public function testProperlyValidatesWhenUsingContentEncoding() + { + $plugin = new Md5ValidatorPlugin(true); + $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/'); + $request->getEventDispatcher()->addSubscriber($plugin); + + // Content-MD5 is the MD5 hash of the canonical content after all + // content-encoding has been applied. Because cURL will automatically + // decompress entity bodies, we need to re-compress it to calculate. + $body = EntityBody::factory('abc'); + $body->compress(); + $hash = $body->getContentMd5(); + $body->uncompress(); + + $response = new Response(200, array( + 'Content-MD5' => $hash, + 'Content-Encoding' => 'gzip' + ), 'abc'); + $request->dispatch('request.complete', array( + 'response' => $response + )); + $this->assertEquals('abc', $response->getBody(true)); + + // Try again with an unknown encoding + $response = new Response(200, array( + 'Content-MD5' => $hash, + 'Content-Encoding' => 'foobar' + ), 'abc'); + $request->dispatch('request.complete', array( + 'response' => $response + )); + + // Try again with compress + $body->compress('bzip2.compress'); + $response = new Response(200, array( + 'Content-MD5' => $body->getContentMd5(), + 'Content-Encoding' => 'compress' + ), 'abc'); + $request->dispatch('request.complete', array( + 'response' => $response + )); + + // Try again with encoding and disabled content-encoding checks + $request->getEventDispatcher()->removeSubscriber($plugin); + $plugin = new Md5ValidatorPlugin(false); + $request->getEventDispatcher()->addSubscriber($plugin); + $request->dispatch('request.complete', array( + 'response' => $response + )); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php new file mode 100644 index 0000000..3af8fef --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Mock/MockPluginTest.php @@ -0,0 +1,199 @@ +assertInternalType('array', MockPlugin::getSubscribedEvents()); + } + + public function testDescribesEvents() + { + $this->assertInternalType('array', MockPlugin::getAllEvents()); + } + + public function testCanBeTemporary() + { + $plugin = new MockPlugin(); + $this->assertFalse($plugin->isTemporary()); + $plugin = new MockPlugin(null, true); + $this->assertTrue($plugin->isTemporary()); + } + + public function testIsCountable() + { + $plugin = new MockPlugin(); + $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $this->assertEquals(1, count($plugin)); + } + + /** + * @depends testIsCountable + */ + public function testCanClearQueue() + { + $plugin = new MockPlugin(); + $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $plugin->clearQueue(); + $this->assertEquals(0, count($plugin)); + } + + public function testCanInspectQueue() + { + $plugin = new MockPlugin(); + $this->assertInternalType('array', $plugin->getQueue()); + $plugin->addResponse(Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); + $queue = $plugin->getQueue(); + $this->assertInternalType('array', $queue); + $this->assertEquals(1, count($queue)); + } + + public function testRetrievesResponsesFromFiles() + { + $response = MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response'); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $response); + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testThrowsExceptionWhenResponseFileIsNotFound() + { + MockPlugin::getMockFile('missing/filename'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidResponsesThrowAnException() + { + $p = new MockPlugin(); + $p->addResponse($this); + } + + public function testAddsResponseObjectsToQueue() + { + $p = new MockPlugin(); + $response = Response::fromMessage("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $p->addResponse($response); + $this->assertEquals(array($response), $p->getQueue()); + } + + public function testAddsResponseFilesToQueue() + { + $p = new MockPlugin(); + $p->addResponse(__DIR__ . '/../../TestData/mock_response'); + $this->assertEquals(1, count($p)); + } + + /** + * @depends testAddsResponseFilesToQueue + */ + public function testAddsMockResponseToRequestFromClient() + { + $p = new MockPlugin(); + $response = MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response'); + $p->addResponse($response); + + $client = new Client('http://127.0.0.1:123/'); + $client->getEventDispatcher()->addSubscriber($p, 9999); + $request = $client->get(); + $request->send(); + + $this->assertSame($response, $request->getResponse()); + $this->assertEquals(0, count($p)); + } + + /** + * @depends testAddsResponseFilesToQueue + * @expectedException \OutOfBoundsException + */ + public function testUpdateThrowsExceptionWhenEmpty() + { + $p = new MockPlugin(); + $p->onRequestBeforeSend(new Event()); + } + + /** + * @depends testAddsMockResponseToRequestFromClient + */ + public function testDetachesTemporaryWhenEmpty() + { + $p = new MockPlugin(null, true); + $p->addResponse(MockPlugin::getMockFile(__DIR__ . '/../../TestData/mock_response')); + $client = new Client('http://127.0.0.1:123/'); + $client->getEventDispatcher()->addSubscriber($p, 9999); + $request = $client->get(); + $request->send(); + + $this->assertFalse($this->hasSubscriber($client, $p)); + } + + public function testLoadsResponsesFromConstructor() + { + $p = new MockPlugin(array(new Response(200))); + $this->assertEquals(1, $p->count()); + } + + public function testStoresMockedRequests() + { + $p = new MockPlugin(array(new Response(200), new Response(200))); + $client = new Client('http://127.0.0.1:123/'); + $client->getEventDispatcher()->addSubscriber($p, 9999); + + $request1 = $client->get(); + $request1->send(); + $this->assertEquals(array($request1), $p->getReceivedRequests()); + + $request2 = $client->get(); + $request2->send(); + $this->assertEquals(array($request1, $request2), $p->getReceivedRequests()); + + $p->flush(); + $this->assertEquals(array(), $p->getReceivedRequests()); + } + + public function testReadsBodiesFromMockedRequests() + { + $p = new MockPlugin(array(new Response(200))); + $p->readBodies(true); + $client = new Client('http://127.0.0.1:123/'); + $client->getEventDispatcher()->addSubscriber($p, 9999); + + $body = EntityBody::factory('foo'); + $request = $client->put(); + $request->setBody($body); + $request->send(); + $this->assertEquals(3, $body->ftell()); + } + + public function testCanMockBadRequestExceptions() + { + $client = new Client('http://127.0.0.1:123/'); + $ex = new CurlException('Foo'); + $mock = new MockPlugin(array($ex)); + $client->addSubscriber($mock); + $request = $client->get('foo'); + + try { + $request->send(); + $this->fail('Did not dequeue an exception'); + } catch (CurlException $e) { + $this->assertSame($e, $ex); + $this->assertSame($request, $ex->getRequest()); + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php new file mode 100644 index 0000000..3892fb6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Plugin/Oauth/OauthPluginTest.php @@ -0,0 +1,345 @@ + 'foo', + 'consumer_secret' => 'bar', + 'token' => 'count', + 'token_secret' => 'dracula' + ); + + protected function getRequest() + { + return RequestFactory::getInstance()->create('POST', 'http://www.test.com/path?a=b&c=d', null, array( + 'e' => 'f' + )); + } + + public function testSubscribesToEvents() + { + $events = OauthPlugin::getSubscribedEvents(); + $this->assertArrayHasKey('request.before_send', $events); + } + + public function testAcceptsConfigurationData() + { + $p = new OauthPlugin($this->config); + + // Access the config object + $class = new \ReflectionClass($p); + $property = $class->getProperty('config'); + $property->setAccessible(true); + $config = $property->getValue($p); + + $this->assertEquals('foo', $config['consumer_key']); + $this->assertEquals('bar', $config['consumer_secret']); + $this->assertEquals('count', $config['token']); + $this->assertEquals('dracula', $config['token_secret']); + $this->assertEquals('1.0', $config['version']); + $this->assertEquals('HMAC-SHA1', $config['signature_method']); + $this->assertEquals('header', $config['request_method']); + } + + public function testCreatesStringToSignFromPostRequest() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $signString = $p->getStringToSign($request, self::TIMESTAMP, self::NONCE); + + $this->assertContains('&e=f', rawurldecode($signString)); + + $expectedSignString = + // Method and URL + 'POST&http%3A%2F%2Fwww.test.com%2Fpath' . + // Sorted parameters from query string and body + '&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' . + '%26oauth_nonce%3De7aa11195ca58349bec8b5ebe351d3497eb9e603%26' . + 'oauth_signature_method%3DHMAC-SHA1' . + '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0'; + + $this->assertEquals($expectedSignString, $signString); + } + + public function testCreatesStringToSignIgnoringPostFields() + { + $config = $this->config; + $config['disable_post_params'] = true; + $p = new OauthPlugin($config); + $request = $this->getRequest(); + $sts = rawurldecode($p->getStringToSign($request, self::TIMESTAMP, self::NONCE)); + $this->assertNotContains('&e=f', $sts); + } + + public function testCreatesStringToSignFromPostRequestWithCustomContentType() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $request->setHeader('Content-Type', 'Foo'); + $this->assertEquals( + // Method and URL + 'POST&http%3A%2F%2Fwww.test.com%2Fpath' . + // Sorted parameters from query string and body + '&a%3Db%26c%3Dd%26oauth_consumer_key%3Dfoo' . + '%26oauth_nonce%3D'. self::NONCE .'%26' . + 'oauth_signature_method%3DHMAC-SHA1' . + '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0', + $p->getStringToSign($request, self::TIMESTAMP, self::NONCE) + ); + } + + /** + * @depends testCreatesStringToSignFromPostRequest + */ + public function testConvertsBooleansToStrings() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $request->getQuery()->set('a', true); + $request->getQuery()->set('c', false); + $this->assertContains('&a%3Dtrue%26c%3Dfalse', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE)); + } + + public function testCreatesStringToSignFromPostRequestWithNullValues() + { + $config = array( + 'consumer_key' => 'foo', + 'consumer_secret' => 'bar', + 'token' => null, + 'token_secret' => 'dracula' + ); + + $p = new OauthPlugin($config); + $request = $this->getRequest(); + $signString = $p->getStringToSign($request, self::TIMESTAMP, self::NONCE); + + $this->assertContains('&e=f', rawurldecode($signString)); + + $expectedSignString = // Method and URL + 'POST&http%3A%2F%2Fwww.test.com%2Fpath' . + // Sorted parameters from query string and body + '&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' . + '%26oauth_nonce%3De7aa11195ca58349bec8b5ebe351d3497eb9e603%26' . + 'oauth_signature_method%3DHMAC-SHA1' . + '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_version%3D1.0'; + + $this->assertEquals($expectedSignString, $signString); + } + + /** + * @depends testCreatesStringToSignFromPostRequest + */ + public function testMultiDimensionalArray() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $request->getQuery()->set('a', array('b' => array('e' => 'f', 'c' => 'd'))); + $this->assertContains('a%255Bb%255D%255Bc%255D%3Dd%26a%255Bb%255D%255Be%255D%3Df%26c%3Dd%26e%3Df%26', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE)); + } + + /** + * @depends testMultiDimensionalArray + */ + public function testMultiDimensionalArrayWithNonDefaultQueryAggregator() + { + $p = new OauthPlugin($this->config); + $request = $this->getRequest(); + $aggregator = new CommaAggregator(); + $query = $request->getQuery()->setAggregator($aggregator) + ->set('g', array('h', 'i', 'j')) + ->set('k', array('l')) + ->set('m', array('n', 'o')); + $this->assertContains('a%3Db%26c%3Dd%26e%3Df%26g%3Dh%2Ci%2Cj%26k%3Dl%26m%3Dn%2Co', $p->getStringToSign($request, self::TIMESTAMP, self::NONCE)); + } + + /** + * @depends testCreatesStringToSignFromPostRequest + */ + public function testSignsStrings() + { + $p = new OauthPlugin(array_merge($this->config, array( + 'signature_callback' => function($string, $key) { + return "_{$string}|{$key}_"; + } + ))); + $request = $this->getRequest(); + $sig = $p->getSignature($request, self::TIMESTAMP, self::NONCE); + $this->assertEquals( + '_POST&http%3A%2F%2Fwww.test.com%2Fpath&a%3Db%26c%3Dd%26e%3Df%26oauth_consumer_key%3Dfoo' . + '%26oauth_nonce%3D'. self::NONCE .'%26oauth_signature_method%3DHMAC-SHA1' . + '%26oauth_timestamp%3D' . self::TIMESTAMP . '%26oauth_token%3Dcount%26oauth_version%3D1.0|' . + 'bar&dracula_', + base64_decode($sig) + ); + } + + /** + * Test that the Oauth is signed correctly and that extra strings haven't been added + * to the authorization header. + */ + public function testSignsOauthRequests() + { + $p = new OauthPlugin($this->config); + $event = new Event(array( + 'request' => $this->getRequest(), + 'timestamp' => self::TIMESTAMP + )); + $params = $p->onRequestBeforeSend($event); + + $this->assertTrue($event['request']->hasHeader('Authorization')); + + $authorizationHeader = (string)$event['request']->getHeader('Authorization'); + + $this->assertStringStartsWith('OAuth ', $authorizationHeader); + + $stringsToCheck = array( + 'oauth_consumer_key="foo"', + 'oauth_nonce="'.urlencode($params['oauth_nonce']).'"', + 'oauth_signature="'.urlencode($params['oauth_signature']).'"', + 'oauth_signature_method="HMAC-SHA1"', + 'oauth_timestamp="' . self::TIMESTAMP . '"', + 'oauth_token="count"', + 'oauth_version="1.0"', + ); + + $totalLength = strlen('OAuth '); + + //Separator is not used before first parameter. + $separator = ''; + + foreach ($stringsToCheck as $stringToCheck) { + $this->assertContains($stringToCheck, $authorizationHeader); + $totalLength += strlen($separator); + $totalLength += strlen($stringToCheck); + $separator = ', '; + } + + // Technically this test is not universally valid. It would be allowable to have extra \n characters + // in the Authorization header. However Guzzle does not do this, so we just perform a simple check + // on length to validate the Authorization header is composed of only the strings above. + $this->assertEquals($totalLength, strlen($authorizationHeader), 'Authorization has extra characters i.e. contains extra elements compared to stringsToCheck.'); + } + + public function testSignsOauthQueryStringRequest() + { + $config = array_merge( + $this->config, + array('request_method' => OauthPlugin::REQUEST_METHOD_QUERY) + ); + + $p = new OauthPlugin($config); + $event = new Event(array( + 'request' => $this->getRequest(), + 'timestamp' => self::TIMESTAMP + )); + $params = $p->onRequestBeforeSend($event); + + $this->assertFalse($event['request']->hasHeader('Authorization')); + + $stringsToCheck = array( + 'a=b', + 'c=d', + 'oauth_consumer_key=foo', + 'oauth_nonce='.urlencode($params['oauth_nonce']), + 'oauth_signature='.urlencode($params['oauth_signature']), + 'oauth_signature_method=HMAC-SHA1', + 'oauth_timestamp='.self::TIMESTAMP, + 'oauth_token=count', + 'oauth_version=1.0', + ); + + $queryString = (string) $event['request']->getQuery(); + + $totalLength = strlen('?'); + + //Separator is not used before first parameter. + $separator = ''; + + foreach ($stringsToCheck as $stringToCheck) { + $this->assertContains($stringToCheck, $queryString); + $totalLength += strlen($separator); + $totalLength += strlen($stringToCheck); + $separator = '&'; + } + + // Removes the last query string separator '&' + $totalLength -= 1; + + $this->assertEquals($totalLength, strlen($queryString), 'Query string has extra characters i.e. contains extra elements compared to stringsToCheck.'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidArgumentExceptionOnMethodError() + { + $config = array_merge( + $this->config, + array('request_method' => 'FakeMethod') + ); + + $p = new OauthPlugin($config); + $event = new Event(array( + 'request' => $this->getRequest(), + 'timestamp' => self::TIMESTAMP + )); + + $p->onRequestBeforeSend($event); + } + + public function testDoesNotAddFalseyValuesToAuthorization() + { + unset($this->config['token']); + $p = new OauthPlugin($this->config); + $event = new Event(array('request' => $this->getRequest(), 'timestamp' => self::TIMESTAMP)); + $p->onRequestBeforeSend($event); + $this->assertTrue($event['request']->hasHeader('Authorization')); + $this->assertNotContains('oauth_token=', (string) $event['request']->getHeader('Authorization')); + } + + public function testOptionalOauthParametersAreNotAutomaticallyAdded() + { + // The only required Oauth parameters are the consumer key and secret. That is enough credentials + // for signing oauth requests. + $config = array( + 'consumer_key' => 'foo', + 'consumer_secret' => 'bar', + ); + + $plugin = new OauthPlugin($config); + $event = new Event(array( + 'request' => $this->getRequest(), + 'timestamp' => self::TIMESTAMP + )); + + $timestamp = $plugin->getTimestamp($event); + $request = $event['request']; + $nonce = $plugin->generateNonce($request); + + $paramsToSign = $plugin->getParamsToSign($request, $timestamp, $nonce); + + $optionalParams = array( + 'callback' => 'oauth_callback', + 'token' => 'oauth_token', + 'verifier' => 'oauth_verifier', + 'token_secret' => 'token_secret' + ); + + foreach ($optionalParams as $optionName => $oauthName) { + $this->assertArrayNotHasKey($oauthName, $paramsToSign, "Optional Oauth param '$oauthName' was not set via config variable '$optionName', but it is listed in getParamsToSign()."); + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php new file mode 100644 index 0000000..8b42fb8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/AbstractConfigLoaderTest.php @@ -0,0 +1,149 @@ +loader = $this->getMockBuilder('Guzzle\Service\AbstractConfigLoader') + ->setMethods(array('build')) + ->getMockForAbstractClass(); + } + + public function tearDown() + { + foreach ($this->cleanup as $file) { + unlink($file); + } + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testOnlyLoadsSupportedTypes() + { + $this->loader->load(new \stdClass()); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + * @expectedExceptionMessage Unable to open fooooooo.json + */ + public function testFileMustBeReadable() + { + $this->loader->load('fooooooo.json'); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + * @expectedExceptionMessage Unknown file extension + */ + public function testMustBeSupportedExtension() + { + $this->loader->load(dirname(__DIR__) . '/TestData/FileBody.txt'); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + * @expectedExceptionMessage Error loading JSON data from + */ + public function testJsonMustBeValue() + { + $filename = tempnam(sys_get_temp_dir(), 'json') . '.json'; + file_put_contents($filename, '{/{./{}foo'); + $this->cleanup[] = $filename; + $this->loader->load($filename); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + * @expectedExceptionMessage PHP files must return an array + */ + public function testPhpFilesMustReturnAnArray() + { + $filename = tempnam(sys_get_temp_dir(), 'php') . '.php'; + file_put_contents($filename, 'cleanup[] = $filename; + $this->loader->load($filename); + } + + public function testLoadsPhpFileIncludes() + { + $filename = tempnam(sys_get_temp_dir(), 'php') . '.php'; + file_put_contents($filename, ' "bar");'); + $this->cleanup[] = $filename; + $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0)); + $config = $this->loader->load($filename); + $this->assertEquals(array('foo' => 'bar'), $config); + } + + public function testCanCreateFromJson() + { + $file = dirname(__DIR__) . '/TestData/services/json1.json'; + // The build method will just return the config data + $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0)); + $data = $this->loader->load($file); + // Ensure that the config files were merged using the includes directives + $this->assertArrayHasKey('includes', $data); + $this->assertArrayHasKey('services', $data); + $this->assertInternalType('array', $data['services']['foo']); + $this->assertInternalType('array', $data['services']['abstract']); + $this->assertInternalType('array', $data['services']['mock']); + $this->assertEquals('bar', $data['services']['foo']['params']['baz']); + } + + public function testUsesAliases() + { + $file = dirname(__DIR__) . '/TestData/services/json1.json'; + $this->loader->addAlias('foo', $file); + // The build method will just return the config data + $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0)); + $data = $this->loader->load('foo'); + $this->assertEquals('bar', $data['services']['foo']['params']['baz']); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + * @expectedExceptionMessage Unable to open foo.json + */ + public function testCanRemoveAliases() + { + $file = dirname(__DIR__) . '/TestData/services/json1.json'; + $this->loader->addAlias('foo.json', $file); + $this->loader->removeAlias('foo.json'); + $this->loader->load('foo.json'); + } + + public function testCanLoadArraysWithIncludes() + { + $file = dirname(__DIR__) . '/TestData/services/json1.json'; + $config = array('includes' => array($file)); + // The build method will just return the config data + $this->loader->expects($this->exactly(1))->method('build')->will($this->returnArgument(0)); + $data = $this->loader->load($config); + $this->assertEquals('bar', $data['services']['foo']['params']['baz']); + } + + public function testDoesNotEnterInfiniteLoop() + { + $prefix = $file = dirname(__DIR__) . '/TestData/description'; + $this->loader->load("{$prefix}/baz.json"); + $this->assertCount(4, $this->readAttribute($this->loader, 'loadedFiles')); + // Ensure that the internal list of loaded files is reset + $this->loader->load("{$prefix}/../test_service2.json"); + $this->assertCount(1, $this->readAttribute($this->loader, 'loadedFiles')); + // Ensure that previously loaded files will be reloaded when starting fresh + $this->loader->load("{$prefix}/baz.json"); + $this->assertCount(4, $this->readAttribute($this->loader, 'loadedFiles')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php new file mode 100644 index 0000000..f63070e --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderLoaderTest.php @@ -0,0 +1,177 @@ + array( + 'abstract' => array( + 'params' => array( + 'access_key' => 'xyz', + 'secret' => 'abc', + ), + ), + 'foo' => array( + 'extends' => 'abstract', + 'params' => array( + 'baz' => 'bar', + ), + ), + 'mock' => array( + 'extends' => 'abstract', + 'params' => array( + 'username' => 'foo', + 'password' => 'baz', + 'subdomain' => 'bar', + ) + ) + ) + ); + + $builder = $arrayFactory->load($data); + + // Ensure that services were parsed + $this->assertTrue(isset($builder['mock'])); + $this->assertTrue(isset($builder['abstract'])); + $this->assertTrue(isset($builder['foo'])); + $this->assertFalse(isset($builder['jimmy'])); + } + + /** + * @expectedException Guzzle\Service\Exception\ServiceNotFoundException + * @expectedExceptionMessage foo is trying to extend a non-existent service: abstract + */ + public function testThrowsExceptionWhenExtendingNonExistentService() + { + $arrayFactory = new ServiceBuilderLoader(); + + $data = array( + 'services' => array( + 'foo' => array( + 'extends' => 'abstract' + ) + ) + ); + + $builder = $arrayFactory->load($data); + } + + public function testAllowsGlobalParameterOverrides() + { + $arrayFactory = new ServiceBuilderLoader(); + + $data = array( + 'services' => array( + 'foo' => array( + 'params' => array( + 'foo' => 'baz', + 'bar' => 'boo' + ) + ) + ) + ); + + $builder = $arrayFactory->load($data, array( + 'bar' => 'jar', + 'far' => 'car' + )); + + $compiled = json_decode($builder->serialize(), true); + $this->assertEquals(array( + 'foo' => 'baz', + 'bar' => 'jar', + 'far' => 'car' + ), $compiled['foo']['params']); + } + + public function tstDoesNotErrorOnCircularReferences() + { + $arrayFactory = new ServiceBuilderLoader(); + $arrayFactory->load(array( + 'services' => array( + 'too' => array('extends' => 'ball'), + 'ball' => array('extends' => 'too'), + ) + )); + } + + public function configProvider() + { + $foo = array( + 'extends' => 'bar', + 'class' => 'stdClass', + 'params' => array('a' => 'test', 'b' => '456') + ); + + return array( + array( + // Does not extend the existing `foo` service but overwrites it + array( + 'services' => array( + 'foo' => $foo, + 'bar' => array('params' => array('baz' => '123')) + ) + ), + array( + 'services' => array( + 'foo' => array('class' => 'Baz') + ) + ), + array( + 'services' => array( + 'foo' => array('class' => 'Baz'), + 'bar' => array('params' => array('baz' => '123')) + ) + ) + ), + array( + // Extends the existing `foo` service + array( + 'services' => array( + 'foo' => $foo, + 'bar' => array('params' => array('baz' => '123')) + ) + ), + array( + 'services' => array( + 'foo' => array( + 'extends' => 'foo', + 'params' => array('b' => '123', 'c' => 'def') + ) + ) + ), + array( + 'services' => array( + 'foo' => array( + 'extends' => 'bar', + 'class' => 'stdClass', + 'params' => array('a' => 'test', 'b' => '123', 'c' => 'def') + ), + 'bar' => array('params' => array('baz' => '123')) + ) + ) + ) + ); + } + + /** + * @dataProvider configProvider + */ + public function testCombinesConfigs($a, $b, $c) + { + $l = new ServiceBuilderLoader(); + $m = new \ReflectionMethod($l, 'mergeData'); + $m->setAccessible(true); + $this->assertEquals($c, $m->invoke($l, $a, $b)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php new file mode 100644 index 0000000..e1b3a1d --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Builder/ServiceBuilderTest.php @@ -0,0 +1,317 @@ + array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => 'michael', + 'password' => 'testing123', + 'subdomain' => 'michael', + ), + ), + 'billy.mock' => array( + 'alias' => 'Hello!', + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => 'billy', + 'password' => 'passw0rd', + 'subdomain' => 'billy', + ), + ), + 'billy.testing' => array( + 'extends' => 'billy.mock', + 'params' => array( + 'subdomain' => 'test.billy', + ), + ), + 'missing_params' => array( + 'extends' => 'billy.mock' + ) + ); + + public function testAllowsSerialization() + { + $builder = ServiceBuilder::factory($this->arrayData); + $cached = unserialize(serialize($builder)); + $this->assertEquals($cached, $builder); + } + + public function testDelegatesFactoryMethodToAbstractFactory() + { + $builder = ServiceBuilder::factory($this->arrayData); + $c = $builder->get('michael.mock'); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $c); + } + + /** + * @expectedException Guzzle\Service\Exception\ServiceNotFoundException + * @expectedExceptionMessage No service is registered as foobar + */ + public function testThrowsExceptionWhenGettingInvalidClient() + { + ServiceBuilder::factory($this->arrayData)->get('foobar'); + } + + public function testStoresClientCopy() + { + $builder = ServiceBuilder::factory($this->arrayData); + $client = $builder->get('michael.mock'); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client); + $this->assertEquals('http://127.0.0.1:8124/v1/michael', $client->getBaseUrl()); + $this->assertEquals($client, $builder->get('michael.mock')); + + // Get another client but throw this one away + $client2 = $builder->get('billy.mock', true); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client2); + $this->assertEquals('http://127.0.0.1:8124/v1/billy', $client2->getBaseUrl()); + + // Make sure the original client is still there and set + $this->assertTrue($client === $builder->get('michael.mock')); + + // Create a new billy.mock client that is stored + $client3 = $builder->get('billy.mock'); + + // Make sure that the stored billy.mock client is equal to the other stored client + $this->assertTrue($client3 === $builder->get('billy.mock')); + + // Make sure that this client is not equal to the previous throwaway client + $this->assertFalse($client2 === $builder->get('billy.mock')); + } + + public function testBuildersPassOptionsThroughToClients() + { + $s = new ServiceBuilder(array( + 'michael.mock' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'base_url' => 'http://www.test.com/', + 'subdomain' => 'michael', + 'password' => 'test', + 'username' => 'michael', + 'curl.curlopt_proxyport' => 8080 + ) + ) + )); + + $c = $s->get('michael.mock'); + $this->assertEquals(8080, $c->getConfig('curl.curlopt_proxyport')); + } + + public function testUsesTheDefaultBuilderWhenNoBuilderIsSpecified() + { + $s = new ServiceBuilder(array( + 'michael.mock' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'base_url' => 'http://www.test.com/', + 'subdomain' => 'michael', + 'password' => 'test', + 'username' => 'michael', + 'curl.curlopt_proxyport' => 8080 + ) + ) + )); + + $c = $s->get('michael.mock'); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $c); + } + + public function testUsedAsArray() + { + $b = ServiceBuilder::factory($this->arrayData); + $this->assertTrue($b->offsetExists('michael.mock')); + $this->assertFalse($b->offsetExists('not_there')); + $this->assertInstanceOf('Guzzle\Service\Client', $b['michael.mock']); + + unset($b['michael.mock']); + $this->assertFalse($b->offsetExists('michael.mock')); + + $b['michael.mock'] = new Client('http://www.test.com/'); + $this->assertInstanceOf('Guzzle\Service\Client', $b['michael.mock']); + } + + public function testFactoryCanCreateFromJson() + { + $tmp = sys_get_temp_dir() . '/test.js'; + file_put_contents($tmp, json_encode($this->arrayData)); + $b = ServiceBuilder::factory($tmp); + unlink($tmp); + $s = $b->get('billy.testing'); + $this->assertEquals('test.billy', $s->getConfig('subdomain')); + $this->assertEquals('billy', $s->getConfig('username')); + } + + public function testFactoryCanCreateFromArray() + { + $b = ServiceBuilder::factory($this->arrayData); + $s = $b->get('billy.testing'); + $this->assertEquals('test.billy', $s->getConfig('subdomain')); + $this->assertEquals('billy', $s->getConfig('username')); + } + + public function testFactoryDoesNotRequireParams() + { + $b = ServiceBuilder::factory($this->arrayData); + $s = $b->get('missing_params'); + $this->assertEquals('billy', $s->getConfig('username')); + } + + public function testBuilderAllowsReferencesBetweenClients() + { + $builder = ServiceBuilder::factory(array( + 'a' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'other_client' => '{b}', + 'username' => 'x', + 'password' => 'y', + 'subdomain' => 'z' + ) + ), + 'b' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => '1', + 'password' => '2', + 'subdomain' => '3' + ) + ) + )); + + $client = $builder['a']; + $this->assertEquals('x', $client->getConfig('username')); + $this->assertSame($builder['b'], $client->getConfig('other_client')); + $this->assertEquals('1', $builder['b']->getConfig('username')); + } + + public function testEmitsEventsWhenClientsAreCreated() + { + // Ensure that the client signals that it emits an event + $this->assertEquals(array('service_builder.create_client'), ServiceBuilder::getAllEvents()); + + // Create a test service builder + $builder = ServiceBuilder::factory(array( + 'a' => array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => 'test', + 'password' => '123', + 'subdomain' => 'z' + ) + ) + )); + + // Add an event listener to pick up client creation events + $emits = 0; + $builder->getEventDispatcher()->addListener('service_builder.create_client', function($event) use (&$emits) { + $emits++; + }); + + // Get the 'a' client by name + $client = $builder->get('a'); + + // Ensure that the event was emitted once, and that the client was present + $this->assertEquals(1, $emits); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $client); + } + + public function testCanAddGlobalParametersToServicesOnLoad() + { + $builder = ServiceBuilder::factory($this->arrayData, array( + 'username' => 'fred', + 'new_value' => 'test' + )); + + $data = json_decode($builder->serialize(), true); + + foreach ($data as $service) { + $this->assertEquals('fred', $service['params']['username']); + $this->assertEquals('test', $service['params']['new_value']); + } + } + + public function testAddsGlobalPlugins() + { + $b = new ServiceBuilder($this->arrayData); + $b->addGlobalPlugin(new HistoryPlugin()); + $s = $b->get('michael.mock'); + $this->assertTrue($s->getEventDispatcher()->hasListeners('request.sent')); + } + + public function testCanGetData() + { + $b = new ServiceBuilder($this->arrayData); + $this->assertEquals($this->arrayData['michael.mock'], $b->getData('michael.mock')); + $this->assertNull($b->getData('ewofweoweofe')); + } + + public function testCanGetByAlias() + { + $b = new ServiceBuilder($this->arrayData); + $this->assertSame($b->get('billy.mock'), $b->get('Hello!')); + } + + public function testCanOverwriteParametersForThrowawayClients() + { + $b = new ServiceBuilder($this->arrayData); + + $c1 = $b->get('michael.mock'); + $this->assertEquals('michael', $c1->getConfig('username')); + + $c2 = $b->get('michael.mock', array('username' => 'jeremy')); + $this->assertEquals('jeremy', $c2->getConfig('username')); + } + + public function testGettingAThrowawayClientWithParametersDoesNotAffectGettingOtherClients() + { + $b = new ServiceBuilder($this->arrayData); + + $c1 = $b->get('michael.mock', array('username' => 'jeremy')); + $this->assertEquals('jeremy', $c1->getConfig('username')); + + $c2 = $b->get('michael.mock'); + $this->assertEquals('michael', $c2->getConfig('username')); + } + + public function testCanUseArbitraryData() + { + $b = new ServiceBuilder(); + $b['a'] = 'foo'; + $this->assertTrue(isset($b['a'])); + $this->assertEquals('foo', $b['a']); + unset($b['a']); + $this->assertFalse(isset($b['a'])); + } + + public function testCanRegisterServiceData() + { + $b = new ServiceBuilder(); + $b['a'] = array( + 'class' => 'Guzzle\Tests\Service\Mock\MockClient', + 'params' => array( + 'username' => 'billy', + 'password' => 'passw0rd', + 'subdomain' => 'billy', + ) + ); + $this->assertTrue(isset($b['a'])); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\MockClient', $b['a']); + $client = $b['a']; + unset($b['a']); + $this->assertFalse(isset($b['a'])); + // Ensure that instantiated clients can be registered + $b['mock'] = $client; + $this->assertSame($client, $b['mock']); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php new file mode 100644 index 0000000..b8245ad --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/CachingConfigLoaderTest.php @@ -0,0 +1,43 @@ +getMockBuilder('Guzzle\Service\ConfigLoaderInterface') + ->setMethods(array('load')) + ->getMockForAbstractClass(); + $data = array('foo' => 'bar'); + $loader->expects($this->once()) + ->method('load') + ->will($this->returnValue($data)); + $cache = new CachingConfigLoader($loader, $cache); + $this->assertEquals($data, $cache->load('foo')); + $this->assertEquals($data, $cache->load('foo')); + } + + public function testDoesNotCacheArrays() + { + $cache = new DoctrineCacheAdapter(new ArrayCache()); + $loader = $this->getMockBuilder('Guzzle\Service\ConfigLoaderInterface') + ->setMethods(array('load')) + ->getMockForAbstractClass(); + $data = array('foo' => 'bar'); + $loader->expects($this->exactly(2)) + ->method('load') + ->will($this->returnValue($data)); + $cache = new CachingConfigLoader($loader, $cache); + $this->assertEquals($data, $cache->load(array())); + $this->assertEquals($data, $cache->load(array())); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php new file mode 100644 index 0000000..aee29ed --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/ClientTest.php @@ -0,0 +1,320 @@ +serviceTest = new ServiceDescription(array( + 'test_command' => new Operation(array( + 'doc' => 'documentationForCommand', + 'method' => 'DELETE', + 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand', + 'args' => array( + 'bucket' => array( + 'required' => true + ), + 'key' => array( + 'required' => true + ) + ) + )) + )); + + $this->service = ServiceDescription::factory(__DIR__ . '/../TestData/test_service.json'); + } + + public function testAllowsCustomClientParameters() + { + $client = new Mock\MockClient(null, array( + Client::COMMAND_PARAMS => array(AbstractCommand::RESPONSE_PROCESSING => 'foo') + )); + $command = $client->getCommand('mock_command'); + $this->assertEquals('foo', $command->get(AbstractCommand::RESPONSE_PROCESSING)); + } + + public function testFactoryCreatesClient() + { + $client = Client::factory(array( + 'base_url' => 'http://www.test.com/', + 'test' => '123' + )); + + $this->assertEquals('http://www.test.com/', $client->getBaseUrl()); + $this->assertEquals('123', $client->getConfig('test')); + } + + public function testFactoryDoesNotRequireBaseUrl() + { + $client = Client::factory(); + } + + public function testDescribesEvents() + { + $this->assertInternalType('array', Client::getAllEvents()); + } + + public function testExecutesCommands() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + + $client = new Client($this->getServer()->getUrl()); + $cmd = new MockCommand(); + $client->execute($cmd); + + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $cmd->getResponse()); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Response', $cmd->getResult()); + $this->assertEquals(1, count($this->getServer()->getReceivedRequests(false))); + } + + public function testExecutesCommandsWithArray() + { + $client = new Client('http://www.test.com/'); + $client->getEventDispatcher()->addSubscriber(new MockPlugin(array( + new Response(200), + new Response(200) + ))); + + // Create a command set and a command + $set = array(new MockCommand(), new MockCommand()); + $client->execute($set); + + // Make sure it sent + $this->assertTrue($set[0]->isExecuted()); + $this->assertTrue($set[1]->isExecuted()); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testThrowsExceptionWhenInvalidCommandIsExecuted() + { + $client = new Client(); + $client->execute(new \stdClass()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testThrowsExceptionWhenMissingCommand() + { + $client = new Client(); + + $mock = $this->getMock('Guzzle\\Service\\Command\\Factory\\FactoryInterface'); + $mock->expects($this->any()) + ->method('factory') + ->with($this->equalTo('test')) + ->will($this->returnValue(null)); + + $client->setCommandFactory($mock); + $client->getCommand('test'); + } + + public function testCreatesCommandsUsingCommandFactory() + { + $mockCommand = new MockCommand(); + + $client = new Mock\MockClient(); + $mock = $this->getMock('Guzzle\\Service\\Command\\Factory\\FactoryInterface'); + $mock->expects($this->any()) + ->method('factory') + ->with($this->equalTo('foo')) + ->will($this->returnValue($mockCommand)); + + $client->setCommandFactory($mock); + + $command = $client->getCommand('foo', array('acl' => '123')); + $this->assertSame($mockCommand, $command); + $command = $client->getCommand('foo', array('acl' => '123')); + $this->assertSame($mockCommand, $command); + $this->assertSame($client, $command->getClient()); + } + + public function testOwnsServiceDescription() + { + $client = new Mock\MockClient(); + $this->assertNull($client->getDescription()); + + $description = $this->getMock('Guzzle\\Service\\Description\\ServiceDescription'); + $this->assertSame($client, $client->setDescription($description)); + $this->assertSame($description, $client->getDescription()); + } + + public function testOwnsResourceIteratorFactory() + { + $client = new Mock\MockClient(); + + $method = new \ReflectionMethod($client, 'getResourceIteratorFactory'); + $method->setAccessible(TRUE); + $rf1 = $method->invoke($client); + + $rf = $this->readAttribute($client, 'resourceIteratorFactory'); + $this->assertInstanceOf('Guzzle\\Service\\Resource\\ResourceIteratorClassFactory', $rf); + $this->assertSame($rf1, $rf); + + $rf = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock'); + $client->setResourceIteratorFactory($rf); + $this->assertNotSame($rf1, $rf); + } + + public function testClientResetsRequestsBeforeExecutingCommands() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHi", + "HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\nI" + )); + + $client = new Mock\MockClient($this->getServer()->getUrl()); + + $command = $client->getCommand('mock_command'); + $client->execute($command); + $client->execute($command); + $this->assertEquals('I', $command->getResponse()->getBody(true)); + } + + public function testClientCreatesIterators() + { + $client = new Mock\MockClient(); + + $iterator = $client->getIterator('mock_command', array( + 'foo' => 'bar' + ), array( + 'limit' => 10 + )); + + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + $this->assertEquals(10, $this->readAttribute($iterator, 'limit')); + + $command = $this->readAttribute($iterator, 'originalCommand'); + $this->assertEquals('bar', $command->get('foo')); + } + + public function testClientCreatesIteratorsWithNoOptions() + { + $client = new Mock\MockClient(); + $iterator = $client->getIterator('mock_command'); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } + + public function testClientCreatesIteratorsWithCommands() + { + $client = new Mock\MockClient(); + $command = new MockCommand(); + $iterator = $client->getIterator($command); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + $iteratorCommand = $this->readAttribute($iterator, 'originalCommand'); + $this->assertSame($command, $iteratorCommand); + } + + public function testClientHoldsInflector() + { + $client = new Mock\MockClient(); + $this->assertInstanceOf('Guzzle\Inflection\MemoizingInflector', $client->getInflector()); + + $inflector = new Inflector(); + $client->setInflector($inflector); + $this->assertSame($inflector, $client->getInflector()); + } + + public function testClientAddsGlobalCommandOptions() + { + $client = new Mock\MockClient('http://www.foo.com', array( + Client::COMMAND_PARAMS => array( + 'mesa' => 'bar' + ) + )); + $command = $client->getCommand('mock_command'); + $this->assertEquals('bar', $command->get('mesa')); + } + + public function testSupportsServiceDescriptionBaseUrls() + { + $description = new ServiceDescription(array('baseUrl' => 'http://foo.com')); + $client = new Client(); + $client->setDescription($description); + $this->assertEquals('http://foo.com', $client->getBaseUrl()); + } + + public function testMergesDefaultCommandParamsCorrectly() + { + $client = new Mock\MockClient('http://www.foo.com', array( + Client::COMMAND_PARAMS => array( + 'mesa' => 'bar', + 'jar' => 'jar' + ) + )); + $command = $client->getCommand('mock_command', array('jar' => 'test')); + $this->assertEquals('bar', $command->get('mesa')); + $this->assertEquals('test', $command->get('jar')); + } + + /** + * @expectedException \Guzzle\Http\Exception\BadResponseException + */ + public function testWrapsSingleCommandExceptions() + { + $client = new Mock\MockClient('http://foobaz.com'); + $mock = new MockPlugin(array(new Response(401))); + $client->addSubscriber($mock); + $client->execute(new MockCommand()); + } + + public function testWrapsMultipleCommandExceptions() + { + $client = new Mock\MockClient('http://foobaz.com'); + $mock = new MockPlugin(array(new Response(200), new Response(200), new Response(404), new Response(500))); + $client->addSubscriber($mock); + + $cmds = array(new MockCommand(), new MockCommand(), new MockCommand(), new MockCommand()); + try { + $client->execute($cmds); + } catch (CommandTransferException $e) { + $this->assertEquals(2, count($e->getFailedRequests())); + $this->assertEquals(2, count($e->getSuccessfulRequests())); + $this->assertEquals(2, count($e->getFailedCommands())); + $this->assertEquals(2, count($e->getSuccessfulCommands())); + + foreach ($e->getSuccessfulCommands() as $c) { + $this->assertTrue($c->getResponse()->isSuccessful()); + } + + foreach ($e->getFailedCommands() as $c) { + $this->assertFalse($c->getRequest()->getResponse()->isSuccessful()); + } + } + } + + public function testGetCommandAfterTwoSetDescriptions() + { + $service1 = ServiceDescription::factory(__DIR__ . '/../TestData/test_service.json'); + $service2 = ServiceDescription::factory(__DIR__ . '/../TestData/test_service_3.json'); + + $client = new Mock\MockClient(); + + $client->setDescription($service1); + $client->getCommand('foo_bar'); + $client->setDescription($service2); + $client->getCommand('baz_qux'); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php new file mode 100644 index 0000000..1004fae --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/AbstractCommandTest.php @@ -0,0 +1,16 @@ +setDescription(ServiceDescription::factory(__DIR__ . '/../../TestData/test_service.json')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php new file mode 100644 index 0000000..d762246 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/ClosureCommandTest.php @@ -0,0 +1,54 @@ + function($command, $api) { + $command->set('testing', '123'); + $request = RequestFactory::getInstance()->create('GET', 'http://www.test.com/'); + return $request; + } + )); + + $client = $this->getServiceBuilder()->get('mock'); + $c->setClient($client)->prepare(); + $this->assertEquals('123', $c->get('testing')); + $this->assertEquals('http://www.test.com/', $c->getRequest()->getUrl()); + } + + /** + * @expectedException UnexpectedValueException + * @expectedExceptionMessage Closure command did not return a RequestInterface object + */ + public function testMustReturnRequest() + { + $c = new ClosureCommand(array( + 'closure' => function($command, $api) { + return false; + } + )); + + $client = $this->getServiceBuilder()->get('mock'); + $c->setClient($client)->prepare(); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php new file mode 100644 index 0000000..b7173d4 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/CommandTest.php @@ -0,0 +1,445 @@ +assertEquals('123', $command->get('test')); + $this->assertFalse($command->isPrepared()); + $this->assertFalse($command->isExecuted()); + } + + public function testDeterminesShortName() + { + $api = new Operation(array('name' => 'foobar')); + $command = new MockCommand(array(), $api); + $this->assertEquals('foobar', $command->getName()); + + $command = new MockCommand(); + $this->assertEquals('mock_command', $command->getName()); + + $command = new Sub(); + $this->assertEquals('sub.sub', $command->getName()); + } + + /** + * @expectedException RuntimeException + */ + public function testGetRequestThrowsExceptionBeforePreparation() + { + $command = new MockCommand(); + $command->getRequest(); + } + + public function testGetResponseExecutesCommandsWhenNeeded() + { + $response = new Response(200); + $client = $this->getClient(); + $this->setMockResponse($client, array($response)); + $command = new MockCommand(); + $command->setClient($client); + $this->assertSame($response, $command->getResponse()); + $this->assertSame($response, $command->getResponse()); + } + + public function testGetResultExecutesCommandsWhenNeeded() + { + $response = new Response(200); + $client = $this->getClient(); + $this->setMockResponse($client, array($response)); + $command = new MockCommand(); + $command->setClient($client); + $this->assertSame($response, $command->getResult()); + $this->assertSame($response, $command->getResult()); + } + + public function testSetClient() + { + $command = new MockCommand(); + $client = $this->getClient(); + + $command->setClient($client); + $this->assertEquals($client, $command->getClient()); + + unset($client); + unset($command); + + $command = new MockCommand(); + $client = $this->getClient(); + + $command->setClient($client)->prepare(); + $this->assertEquals($client, $command->getClient()); + $this->assertTrue($command->isPrepared()); + } + + public function testExecute() + { + $client = $this->getClient(); + $response = new Response(200, array( + 'Content-Type' => 'application/xml' + ), '123'); + $this->setMockResponse($client, array($response)); + $command = new MockCommand(); + $this->assertSame($command, $command->setClient($client)); + + // Returns the result of the command + $this->assertInstanceOf('SimpleXMLElement', $command->execute()); + + $this->assertTrue($command->isPrepared()); + $this->assertTrue($command->isExecuted()); + $this->assertSame($response, $command->getResponse()); + $this->assertInstanceOf('Guzzle\\Http\\Message\\Request', $command->getRequest()); + // Make sure that the result was automatically set to a SimpleXMLElement + $this->assertInstanceOf('SimpleXMLElement', $command->getResult()); + $this->assertEquals('123', (string) $command->getResult()->data); + } + + public function testConvertsJsonResponsesToArray() + { + $client = $this->getClient(); + $this->setMockResponse($client, array( + new \Guzzle\Http\Message\Response(200, array( + 'Content-Type' => 'application/json' + ), '{ "key": "Hi!" }' + ) + )); + $command = new MockCommand(); + $command->setClient($client); + $command->execute(); + $this->assertEquals(array( + 'key' => 'Hi!' + ), $command->getResult()); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testConvertsInvalidJsonResponsesToArray() + { + $json = '{ "key": "Hi!" }invalid'; + // Some implementations of php-json extension are not strict enough + // and allow to parse invalid json ignoring invalid parts + // See https://github.com/remicollet/pecl-json-c/issues/5 + if (json_decode($json) && JSON_ERROR_NONE === json_last_error()) { + $this->markTestSkipped('php-pecl-json library regression issues'); + } + + $client = $this->getClient(); + $this->setMockResponse($client, array( + new \Guzzle\Http\Message\Response(200, array( + 'Content-Type' => 'application/json' + ), $json + ) + )); + $command = new MockCommand(); + $command->setClient($client); + $command->execute(); + } + + public function testProcessResponseIsNotXml() + { + $client = $this->getClient(); + $this->setMockResponse($client, array( + new Response(200, array( + 'Content-Type' => 'application/octet-stream' + ), 'abc,def,ghi') + )); + $command = new MockCommand(); + $client->execute($command); + + // Make sure that the result was not converted to XML + $this->assertFalse($command->getResult() instanceof \SimpleXMLElement); + } + + /** + * @expectedException RuntimeException + */ + public function testExecuteThrowsExceptionWhenNoClientIsSet() + { + $command = new MockCommand(); + $command->execute(); + } + + /** + * @expectedException RuntimeException + */ + public function testPrepareThrowsExceptionWhenNoClientIsSet() + { + $command = new MockCommand(); + $command->prepare(); + } + + public function testCommandsAllowsCustomRequestHeaders() + { + $command = new MockCommand(); + $command->getRequestHeaders()->set('test', '123'); + $this->assertInstanceOf('Guzzle\Common\Collection', $command->getRequestHeaders()); + $this->assertEquals('123', $command->getRequestHeaders()->get('test')); + + $command->setClient($this->getClient())->prepare(); + $this->assertEquals('123', (string) $command->getRequest()->getHeader('test')); + } + + public function testCommandsAllowsCustomRequestHeadersAsArray() + { + $command = new MockCommand(array(AbstractCommand::HEADERS_OPTION => array('Foo' => 'Bar'))); + $this->assertInstanceOf('Guzzle\Common\Collection', $command->getRequestHeaders()); + $this->assertEquals('Bar', $command->getRequestHeaders()->get('Foo')); + } + + private function getOperation() + { + return new Operation(array( + 'name' => 'foobar', + 'httpMethod' => 'POST', + 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand', + 'parameters' => array( + 'test' => array( + 'default' => '123', + 'type' => 'string' + ) + ))); + } + + public function testCommandsUsesOperation() + { + $api = $this->getOperation(); + $command = new MockCommand(array(), $api); + $this->assertSame($api, $command->getOperation()); + $command->setClient($this->getClient())->prepare(); + $this->assertEquals('123', $command->get('test')); + $this->assertSame($api, $command->getOperation($api)); + } + + public function testCloneMakesNewRequest() + { + $client = $this->getClient(); + $command = new MockCommand(array(), $this->getOperation()); + $command->setClient($client); + + $command->prepare(); + $this->assertTrue($command->isPrepared()); + + $command2 = clone $command; + $this->assertFalse($command2->isPrepared()); + } + + public function testHasOnCompleteMethod() + { + $that = $this; + $called = 0; + + $testFunction = function($command) use (&$called, $that) { + $called++; + $that->assertInstanceOf('Guzzle\Service\Command\CommandInterface', $command); + }; + + $client = $this->getClient(); + $command = new MockCommand(array( + 'command.on_complete' => $testFunction + ), $this->getOperation()); + $command->setClient($client); + + $command->prepare()->setResponse(new Response(200), true); + $command->execute(); + $this->assertEquals(1, $called); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testOnCompleteMustBeCallable() + { + $client = $this->getClient(); + $command = new MockCommand(); + $command->setOnComplete('foo'); + } + + public function testCanSetResultManually() + { + $client = $this->getClient(); + $client->getEventDispatcher()->addSubscriber(new MockPlugin(array( + new Response(200) + ))); + $command = new MockCommand(); + $client->execute($command); + $command->setResult('foo!'); + $this->assertEquals('foo!', $command->getResult()); + } + + public function testCanInitConfig() + { + $command = $this->getMockBuilder('Guzzle\\Service\\Command\\AbstractCommand') + ->setConstructorArgs(array(array( + 'foo' => 'bar' + ), new Operation(array( + 'parameters' => array( + 'baz' => new Parameter(array( + 'default' => 'baaar' + )) + ) + )))) + ->getMockForAbstractClass(); + + $this->assertEquals('bar', $command['foo']); + $this->assertEquals('baaar', $command['baz']); + } + + public function testAddsCurlOptionsToRequestsWhenPreparing() + { + $command = new MockCommand(array( + 'foo' => 'bar', + 'curl.options' => array('CURLOPT_PROXYPORT' => 8080) + )); + $client = new Client(); + $command->setClient($client); + $request = $command->prepare(); + $this->assertEquals(8080, $request->getCurlOptions()->get(CURLOPT_PROXYPORT)); + } + + public function testIsInvokable() + { + $client = $this->getClient(); + $response = new Response(200); + $this->setMockResponse($client, array($response)); + $command = new MockCommand(); + $command->setClient($client); + // Returns the result of the command + $this->assertSame($response, $command()); + } + + public function testCreatesDefaultOperation() + { + $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')->getMockForAbstractClass(); + $this->assertInstanceOf('Guzzle\Service\Description\Operation', $command->getOperation()); + } + + public function testAllowsValidatorToBeInjected() + { + $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand')->getMockForAbstractClass(); + $v = new SchemaValidator(); + $command->setValidator($v); + $this->assertSame($v, $this->readAttribute($command, 'validator')); + } + + public function testCanDisableValidation() + { + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator') + ->setMethods(array('validate')) + ->getMock(); + $v->expects($this->never())->method('validate'); + $command->setValidator($v); + $command->set(AbstractCommand::DISABLE_VALIDATION, true); + $command->prepare(); + } + + public function testValidatorDoesNotUpdateNonDefaultValues() + { + $command = new MockCommand(array('test' => 123, 'foo' => 'bar')); + $command->setClient(new \Guzzle\Service\Client()); + $command->prepare(); + $this->assertEquals(123, $command->get('test')); + $this->assertEquals('bar', $command->get('foo')); + } + + public function testValidatorUpdatesDefaultValues() + { + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + $command->prepare(); + $this->assertEquals(123, $command->get('test')); + $this->assertEquals('abc', $command->get('_internal')); + } + + /** + * @expectedException \Guzzle\Service\Exception\ValidationException + * @expectedExceptionMessage [Foo] Baz + */ + public function testValidatesCommandBeforeSending() + { + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator') + ->setMethods(array('validate', 'getErrors')) + ->getMock(); + $v->expects($this->any())->method('validate')->will($this->returnValue(false)); + $v->expects($this->any())->method('getErrors')->will($this->returnValue(array('[Foo] Baz', '[Bar] Boo'))); + $command->setValidator($v); + $command->prepare(); + } + + /** + * @expectedException \Guzzle\Service\Exception\ValidationException + * @expectedExceptionMessage Validation errors: [abc] must be of type string + */ + public function testValidatesAdditionalParameters() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'foo' => array( + 'parameters' => array( + 'baz' => array('type' => 'integer') + ), + 'additionalParameters' => array( + 'type' => 'string' + ) + ) + ) + )); + + $client = new Client(); + $client->setDescription($description); + $command = $client->getCommand('foo', array( + 'abc' => false, + 'command.headers' => array('foo' => 'bar') + )); + $command->prepare(); + } + + public function testCanAccessValidationErrorsFromCommand() + { + $validationErrors = array('[Foo] Baz', '[Bar] Boo'); + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + + $this->assertFalse($command->getValidationErrors()); + + $v = $this->getMockBuilder('Guzzle\Service\Description\SchemaValidator') + ->setMethods(array('validate', 'getErrors')) + ->getMock(); + $v->expects($this->any())->method('getErrors')->will($this->returnValue($validationErrors)); + $command->setValidator($v); + + $this->assertEquals($validationErrors, $command->getValidationErrors()); + } + + public function testCanChangeResponseBody() + { + $body = EntityBody::factory(); + $command = new MockCommand(); + $command->setClient(new \Guzzle\Service\Client()); + $command->set(AbstractCommand::RESPONSE_BODY, $body); + $request = $command->prepare(); + $this->assertSame($body, $this->readAttribute($request, 'responseBody')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php new file mode 100644 index 0000000..b7a4682 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultRequestSerializerTest.php @@ -0,0 +1,122 @@ +serializer = DefaultRequestSerializer::getInstance(); + $this->client = new Client('http://foo.com/baz'); + $this->operation = new Operation(array('httpMethod' => 'POST')); + $this->command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setConstructorArgs(array(array(), $this->operation)) + ->getMockForAbstractClass(); + $this->command->setClient($this->client); + } + + public function testAllowsCustomVisitor() + { + $this->serializer->addVisitor('custom', new HeaderVisitor()); + $this->command['test'] = '123'; + $this->operation->addParam(new Parameter(array('name' => 'test', 'location' => 'custom'))); + $request = $this->serializer->prepare($this->command); + $this->assertEquals('123', (string) $request->getHeader('test')); + } + + public function testUsesRelativePath() + { + $this->operation->setUri('bar'); + $request = $this->serializer->prepare($this->command); + $this->assertEquals('http://foo.com/baz/bar', (string) $request->getUrl()); + } + + public function testUsesRelativePathWithUriLocations() + { + $this->command['test'] = '123'; + $this->operation->setUri('bar/{test}'); + $this->operation->addParam(new Parameter(array('name' => 'test', 'location' => 'uri'))); + $request = $this->serializer->prepare($this->command); + $this->assertEquals('http://foo.com/baz/bar/123', (string) $request->getUrl()); + } + + public function testAllowsCustomFactory() + { + $f = new VisitorFlyweight(); + $serializer = new DefaultRequestSerializer($f); + $this->assertSame($f, $this->readAttribute($serializer, 'factory')); + } + + public function testMixedParams() + { + $this->operation->setUri('bar{?limit,fields}'); + $this->operation->addParam(new Parameter(array( + 'name' => 'limit', + 'location' => 'uri', + 'required' => false, + ))); + $this->operation->addParam(new Parameter(array( + 'name' => 'fields', + 'location' => 'uri', + 'required' => true, + ))); + + $this->command['fields'] = array('id', 'name'); + + $request = $this->serializer->prepare($this->command); + $this->assertEquals('http://foo.com/baz/bar?fields='.urlencode('id,name'), (string) $request->getUrl()); + } + + public function testValidatesAdditionalParameters() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'foo' => array( + 'httpMethod' => 'PUT', + 'parameters' => array( + 'bar' => array('location' => 'header') + ), + 'additionalParameters' => array( + 'location' => 'json' + ) + ) + ) + )); + + $client = new Client(); + $client->setDescription($description); + $command = $client->getCommand('foo'); + $command['bar'] = 'test'; + $command['hello'] = 'abc'; + $request = $command->prepare(); + $this->assertEquals('test', (string) $request->getHeader('bar')); + $this->assertEquals('{"hello":"abc"}', (string) $request->getBody()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php new file mode 100644 index 0000000..a6a02f9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/DefaultResponseParserTest.php @@ -0,0 +1,59 @@ +setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, array( + 'Content-Type' => 'application/xml' + ), 'Bar'), true); + $this->assertInstanceOf('SimpleXMLElement', $op->execute()); + } + + public function testParsesJsonResponses() + { + $op = new OperationCommand(array(), new Operation()); + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, array( + 'Content-Type' => 'application/json' + ), '{"Baz":"Bar"}'), true); + $this->assertEquals(array('Baz' => 'Bar'), $op->execute()); + } + + /** + * @expectedException \Guzzle\Common\Exception\RuntimeException + */ + public function testThrowsExceptionWhenParsingJsonFails() + { + $op = new OperationCommand(array(), new Operation()); + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, array('Content-Type' => 'application/json'), '{"Baz":ddw}'), true); + $op->execute(); + } + + public function testAddsContentTypeWhenExpectsIsSetOnCommand() + { + $op = new OperationCommand(array(), new Operation()); + $op['command.expects'] = 'application/json'; + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, null, '{"Baz":"Bar"}'), true); + $this->assertEquals(array('Baz' => 'Bar'), $op->execute()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php new file mode 100644 index 0000000..ab1041a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/AliasFactoryTest.php @@ -0,0 +1,76 @@ +client = new Client(); + + $map = new MapFactory(array( + 'test' => 'Guzzle\Tests\Service\Mock\Command\MockCommand', + 'test1' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand' + )); + + $this->factory = new AliasFactory($this->client, array( + 'foo' => 'test', + 'bar' => 'sub', + 'sub' => 'test1', + 'krull' => 'test3', + 'krull_2' => 'krull', + 'sub_2' => 'bar', + 'bad_link' => 'jarjar' + )); + + $map2 = new MapFactory(array( + 'test3' => 'Guzzle\Tests\Service\Mock\Command\Sub\Sub' + )); + + $this->client->setCommandFactory(new CompositeFactory(array($map, $this->factory, $map2))); + } + + public function aliasProvider() + { + return array( + array('foo', 'Guzzle\Tests\Service\Mock\Command\MockCommand', false), + array('bar', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false), + array('sub', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false), + array('sub_2', 'Guzzle\Tests\Service\Mock\Command\OtherCommand', false), + array('krull', 'Guzzle\Tests\Service\Mock\Command\Sub\Sub', false), + array('krull_2', 'Guzzle\Tests\Service\Mock\Command\Sub\Sub', false), + array('missing', null, true), + array('bad_link', null, true) + ); + } + + /** + * @dataProvider aliasProvider + */ + public function testAliasesCommands($key, $result, $exception) + { + try { + $command = $this->client->getCommand($key); + if (is_null($result)) { + $this->assertNull($command); + } else { + $this->assertInstanceof($result, $command); + } + } catch (\Exception $e) { + if (!$exception) { + $this->fail('Got exception when it was not expected'); + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php new file mode 100644 index 0000000..b896dcf --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/CompositeFactoryTest.php @@ -0,0 +1,124 @@ +getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testIsIterable() + { + $factory = new CompositeFactory(array($this->getFactory(), $this->getFactory())); + $this->assertEquals(2, count($factory)); + $this->assertEquals(2, count(iterator_to_array($factory->getIterator()))); + } + + public function testFindsFactories() + { + $f1 = $this->getFactory(); + $f2 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $factory = new CompositeFactory(array($f1, $f2)); + $this->assertNull($factory->find('foo')); + $this->assertNull($factory->find($this->getFactory())); + $this->assertSame($f1, $factory->find('Guzzle\\Service\\Command\\Factory\\MapFactory')); + $this->assertSame($f2, $factory->find('Guzzle\\Service\\Command\\Factory\\CompositeFactory')); + $this->assertSame($f1, $factory->find($f1)); + $this->assertSame($f2, $factory->find($f2)); + + $this->assertFalse($factory->has('foo')); + $this->assertTrue($factory->has('Guzzle\\Service\\Command\\Factory\\MapFactory')); + $this->assertTrue($factory->has('Guzzle\\Service\\Command\\Factory\\CompositeFactory')); + } + + public function testCreatesCommands() + { + $factory = new CompositeFactory(); + $this->assertNull($factory->factory('foo')); + + $f1 = $this->getFactory(); + $mockCommand1 = $this->getMockForAbstractClass('Guzzle\\Service\\Command\\AbstractCommand'); + + $f1->expects($this->once()) + ->method('factory') + ->with($this->equalTo('foo')) + ->will($this->returnValue($mockCommand1)); + + $factory = new CompositeFactory(array($f1)); + $this->assertSame($mockCommand1, $factory->factory('foo')); + } + + public function testAllowsRemovalOfFactories() + { + $f1 = $this->getFactory(); + $f2 = $this->getFactory(); + $f3 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $factories = array($f1, $f2, $f3); + $factory = new CompositeFactory($factories); + + $factory->remove('foo'); + $this->assertEquals($factories, $factory->getIterator()->getArrayCopy()); + + $factory->remove($f1); + $this->assertEquals(array($f2, $f3), $factory->getIterator()->getArrayCopy()); + + $factory->remove('Guzzle\\Service\\Command\\Factory\\MapFactory'); + $this->assertEquals(array($f3), $factory->getIterator()->getArrayCopy()); + + $factory->remove('Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $this->assertEquals(array(), $factory->getIterator()->getArrayCopy()); + + $factory->remove('foo'); + $this->assertEquals(array(), $factory->getIterator()->getArrayCopy()); + } + + public function testAddsFactoriesBeforeAndAtEnd() + { + $f1 = $this->getFactory(); + $f2 = $this->getFactory(); + $f3 = $this->getFactory('Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $f4 = $this->getFactory(); + + $factory = new CompositeFactory(); + + $factory->add($f1); + $this->assertEquals(array($f1), $factory->getIterator()->getArrayCopy()); + + $factory->add($f2); + $this->assertEquals(array($f1, $f2), $factory->getIterator()->getArrayCopy()); + + $factory->add($f3, $f2); + $this->assertEquals(array($f1, $f3, $f2), $factory->getIterator()->getArrayCopy()); + + $factory->add($f4, 'Guzzle\\Service\\Command\\Factory\\CompositeFactory'); + $this->assertEquals(array($f1, $f4, $f3, $f2), $factory->getIterator()->getArrayCopy()); + } + + public function testProvidesDefaultChainForClients() + { + $client = $this->getMock('Guzzle\\Service\\Client'); + $chain = CompositeFactory::getDefaultChain($client); + $a = $chain->getIterator()->getArrayCopy(); + $this->assertEquals(1, count($a)); + $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ConcreteClassFactory', $a[0]); + + $description = $this->getMock('Guzzle\\Service\\Description\\ServiceDescription'); + $client->expects($this->once()) + ->method('getDescription') + ->will($this->returnValue($description)); + $chain = CompositeFactory::getDefaultChain($client); + $a = $chain->getIterator()->getArrayCopy(); + $this->assertEquals(2, count($a)); + $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ServiceDescriptionFactory', $a[0]); + $this->assertInstanceOf('Guzzle\\Service\\Command\\Factory\\ConcreteClassFactory', $a[1]); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php new file mode 100644 index 0000000..7664718 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ConcreteClassFactoryTest.php @@ -0,0 +1,49 @@ + $prefix + )); + } + + $factory = new ConcreteClassFactory($client); + + if (is_null($result)) { + $this->assertNull($factory->factory($key)); + } else { + $this->assertInstanceof($result, $factory->factory($key)); + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php new file mode 100644 index 0000000..ee720d1 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/MapFactoryTest.php @@ -0,0 +1,37 @@ + 'Guzzle\Tests\Service\Mock\Command\MockCommand', + 'test1' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand' + )); + + if (is_null($result)) { + $this->assertNull($factory->factory($key)); + } else { + $this->assertInstanceof($result, $factory->factory($key)); + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php new file mode 100644 index 0000000..3372634 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/Factory/ServiceDescriptionFactoryTest.php @@ -0,0 +1,68 @@ +getDescription(); + + $factory = new ServiceDescriptionFactory($d); + $this->assertSame($d, $factory->getServiceDescription()); + + if (is_null($result)) { + $this->assertNull($factory->factory($key)); + } else { + $this->assertInstanceof($result, $factory->factory($key)); + } + } + + public function testUsesUcFirstIfNoExactMatch() + { + $d = $this->getDescription(); + $factory = new ServiceDescriptionFactory($d, new Inflector()); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('Test')); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('test')); + } + + public function testUsesInflectionIfNoExactMatch() + { + $d = $this->getDescription(); + $factory = new ServiceDescriptionFactory($d, new Inflector()); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('Binks')); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\OtherCommand', $factory->factory('binks')); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\MockCommand', $factory->factory('JarJar')); + $this->assertInstanceof('Guzzle\Tests\Service\Mock\Command\MockCommand', $factory->factory('jar_jar')); + } + + protected function getDescription() + { + return ServiceDescription::factory(array( + 'operations' => array( + 'jar_jar' => array('class' => 'Guzzle\Tests\Service\Mock\Command\MockCommand'), + 'binks' => array('class' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand'), + 'Test' => array('class' => 'Guzzle\Tests\Service\Mock\Command\OtherCommand') + ) + )); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php new file mode 100644 index 0000000..46b472e --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/AbstractVisitorTestCase.php @@ -0,0 +1,110 @@ +command = new MockCommand(); + $this->request = new EntityEnclosingRequest('POST', 'http://www.test.com/some/path.php'); + $this->validator = new SchemaValidator(); + } + + protected function getCommand($location) + { + $command = new OperationCommand(array(), $this->getNestedCommand($location)); + $command->setClient(new MockClient()); + + return $command; + } + + protected function getNestedCommand($location) + { + return new Operation(array( + 'httpMethod' => 'POST', + 'parameters' => array( + 'foo' => new Parameter(array( + 'type' => 'object', + 'location' => $location, + 'sentAs' => 'Foo', + 'required' => true, + 'properties' => array( + 'test' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'baz' => array( + 'type' => 'boolean', + 'default' => true + ), + 'jenga' => array( + 'type' => 'string', + 'default' => 'hello', + 'sentAs' => 'Jenga_Yall!', + 'filters' => array('strtoupper') + ) + ) + ), + 'bar' => array('default' => 123) + ), + 'additionalProperties' => array( + 'type' => 'string', + 'filters' => array('strtoupper'), + 'location' => $location + ) + )), + 'arr' => new Parameter(array( + 'type' => 'array', + 'location' => $location, + 'items' => array( + 'type' => 'string', + 'filters' => array('strtoupper') + ) + )), + ) + )); + } + + protected function getCommandWithArrayParamAndFilters() + { + $operation = new Operation(array( + 'httpMethod' => 'POST', + 'parameters' => array( + 'foo' => new Parameter(array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'Foo', + 'required' => true, + 'default' => 'bar', + 'filters' => array('strtoupper') + )), + 'arr' => new Parameter(array( + 'type' => 'array', + 'location' => 'query', + 'sentAs' => 'Arr', + 'required' => true, + 'default' => array(123, 456, 789), + 'filters' => array(array('method' => 'implode', 'args' => array(',', '@value'))) + )) + ) + )); + $command = new OperationCommand(array(), $operation); + $command->setClient(new MockClient()); + + return $command; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php new file mode 100644 index 0000000..2a95c45 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/BodyVisitorTest.php @@ -0,0 +1,63 @@ +getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('123', (string) $this->request->getBody()); + $this->assertNull($this->request->getHeader('Expect')); + } + + public function testAddsExpectHeaderWhenSetToTrue() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + $param->setData('expect_header', true); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('123', (string) $this->request->getBody()); + } + + public function testCanDisableExpectHeader() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + $param->setData('expect_header', false); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertNull($this->request->getHeader('Expect')); + } + + public function testCanSetExpectHeaderBasedOnSize() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + // The body is less than the cutoff + $param->setData('expect_header', 5); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertNull($this->request->getHeader('Expect')); + // Now check when the body is greater than the cutoff + $param->setData('expect_header', 2); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('100-Continue', (string) $this->request->getHeader('Expect')); + } + + public function testAddsContentEncodingWhenSetOnBody() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('body')->getParam('foo')->setSentAs('Foo'); + $body = EntityBody::factory('foo'); + $body->compress(); + $visitor->visit($this->command, $this->request, $param, $body); + $this->assertEquals('gzip', (string) $this->request->getHeader('Content-Encoding')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php new file mode 100644 index 0000000..7ea1ae9 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/HeaderVisitorTest.php @@ -0,0 +1,48 @@ +getNestedCommand('header')->getParam('foo')->setSentAs('test'); + $param->setAdditionalProperties(new Parameter(array())); + $visitor->visit($this->command, $this->request, $param, 'test'); + } + + public function testVisitsLocation() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('header')->getParam('foo')->setSentAs('test'); + $param->setAdditionalProperties(false); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('123', (string) $this->request->getHeader('test')); + } + + public function testVisitsMappedPrefixHeaders() + { + $visitor = new Visitor(); + $param = $this->getNestedCommand('header')->getParam('foo')->setSentAs('test'); + $param->setSentAs('x-foo-'); + $param->setAdditionalProperties(new Parameter(array( + 'type' => 'string' + ))); + $visitor->visit($this->command, $this->request, $param, array( + 'bar' => 'test', + 'baz' => '123' + )); + $this->assertEquals('test', (string) $this->request->getHeader('x-foo-bar')); + $this->assertEquals('123', (string) $this->request->getHeader('x-foo-baz')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php new file mode 100644 index 0000000..ea6782f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/JsonVisitorTest.php @@ -0,0 +1,60 @@ +after($this->command, $this->request); + + $param = $this->getNestedCommand('json')->getParam('foo'); + $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123'); + $visitor->visit($this->command, $this->request, $param->setSentAs('test2'), 'abc'); + $visitor->after($this->command, $this->request); + $this->assertEquals('{"test":"123","test2":"abc"}', (string) $this->request->getBody()); + } + + public function testAddsJsonHeader() + { + $visitor = new Visitor(); + $visitor->setContentTypeHeader('application/json-foo'); + $param = $this->getNestedCommand('json')->getParam('foo'); + $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123'); + $visitor->after($this->command, $this->request); + $this->assertEquals('application/json-foo', (string) $this->request->getHeader('Content-Type')); + } + + public function testRecursivelyBuildsJsonBodies() + { + $command = $this->getCommand('json'); + $request = $command->prepare(); + $this->assertEquals('{"Foo":{"test":{"baz":true,"Jenga_Yall!":"HELLO"},"bar":123}}', (string) $request->getBody()); + } + + public function testAppliesFiltersToAdditionalProperties() + { + $command = $this->getCommand('json'); + $command->set('foo', array('not_set' => 'abc')); + $request = $command->prepare(); + $result = json_decode($request->getBody(), true); + $this->assertEquals('ABC', $result['Foo']['not_set']); + } + + public function testAppliesFiltersToArrayItemValues() + { + $command = $this->getCommand('json'); + $command->set('arr', array('a', 'b')); + $request = $command->prepare(); + $result = json_decode($request->getBody(), true); + $this->assertEquals(array('A', 'B'), $result['arr']); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php new file mode 100644 index 0000000..540b410 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFieldVisitorTest.php @@ -0,0 +1,33 @@ +getNestedCommand('postField')->getParam('foo'); + $visitor->visit($this->command, $this->request, $param->setSentAs('test'), '123'); + $this->assertEquals('123', (string) $this->request->getPostField('test')); + } + + public function testRecursivelyBuildsPostFields() + { + $command = $this->getCommand('postField'); + $request = $command->prepare(); + $visitor = new Visitor(); + $param = $command->getOperation()->getParam('foo'); + $visitor->visit($command, $request, $param, $command['foo']); + $visitor->after($command, $request); + $this->assertEquals( + 'Foo[test][baz]=1&Foo[test][Jenga_Yall!]=HELLO&Foo[bar]=123', + rawurldecode((string) $request->getPostFields()) + ); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php new file mode 100644 index 0000000..21e3cec --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/PostFileVisitorTest.php @@ -0,0 +1,54 @@ +getNestedCommand('postFile')->getParam('foo'); + + // Test using a path to a file + $visitor->visit($this->command, $this->request, $param->setSentAs('test_3'), __FILE__); + $this->assertInternalType('array', $this->request->getPostFile('test_3')); + + // Test with a PostFile + $visitor->visit($this->command, $this->request, $param->setSentAs(null), new PostFile('baz', __FILE__)); + $this->assertInternalType('array', $this->request->getPostFile('baz')); + } + + public function testVisitsLocationWithMultipleFiles() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'DoPost' => array( + 'httpMethod' => 'POST', + 'parameters' => array( + 'foo' => array( + 'location' => 'postFile', + 'type' => array('string', 'array') + ) + ) + ) + ) + )); + $this->getServer()->flush(); + $this->getServer()->enqueue(array("HTTP/1.1 200 OK\r\nContent-Length:0\r\n\r\n")); + $client = new Client($this->getServer()->getUrl()); + $client->setDescription($description); + $command = $client->getCommand('DoPost', array('foo' => array(__FILE__, __FILE__))); + $command->execute(); + $received = $this->getServer()->getReceivedRequests(); + $this->assertContains('name="foo[0]";', $received[0]); + $this->assertContains('name="foo[1]";', $received[0]); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php new file mode 100644 index 0000000..607af76 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/QueryVisitorTest.php @@ -0,0 +1,48 @@ +getNestedCommand('query')->getParam('foo')->setSentAs('test'); + $visitor->visit($this->command, $this->request, $param, '123'); + $this->assertEquals('123', $this->request->getQuery()->get('test')); + } + + /** + * @covers Guzzle\Service\Command\LocationVisitor\Request\QueryVisitor + * @covers Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor::resolveRecursively + */ + public function testRecursivelyBuildsQueryStrings() + { + $command = $this->getCommand('query'); + $command->getOperation()->getParam('foo')->setSentAs('Foo'); + $request = $command->prepare(); + $this->assertEquals( + 'Foo[test][baz]=1&Foo[test][Jenga_Yall!]=HELLO&Foo[bar]=123', + rawurldecode($request->getQuery()) + ); + } + + /** + * @covers Guzzle\Service\Command\LocationVisitor\Request\AbstractRequestVisitor::resolveRecursively + */ + public function testFiltersAreAppliedToArrayParamType() + { + $command = $this->getCommandWithArrayParamAndFilters(); + $request = $command->prepare(); + $query = $request->getQuery(); + // param type 'string' + $this->assertEquals('BAR', $query->get('Foo')); + // param type 'array' + $this->assertEquals('123,456,789', $query->get('Arr')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php new file mode 100644 index 0000000..ff8cec5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/ResponseBodyVisitorTest.php @@ -0,0 +1,20 @@ +getNestedCommand('response_body')->getParam('foo'); + $visitor->visit($this->command, $this->request, $param, sys_get_temp_dir() . '/foo.txt'); + $body = $this->readAttribute($this->request, 'responseBody'); + $this->assertContains('/foo.txt', $body->getUri()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php new file mode 100644 index 0000000..beb58b0 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Request/XmlVisitorTest.php @@ -0,0 +1,558 @@ + array( + 'xmlRoot' => array( + 'name' => 'test', + 'namespaces' => 'http://foo.com' + ) + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array('location' => 'xml', 'type' => 'string') + ) + ), + array('Foo' => 'test', 'Baz' => 'bar'), + 'testbar' + ), + // Ensure that the content-type is not added + array(array('parameters' => array('Foo' => array('location' => 'xml', 'type' => 'string'))), array(), ''), + // Test with adding attributes and no namespace + array( + array( + 'data' => array( + 'xmlRoot' => array( + 'name' => 'test' + ) + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string', 'data' => array('xmlAttribute' => true)) + ) + ), + array('Foo' => 'test', 'Baz' => 'bar'), + '' + ), + // Test adding with an array + array( + array( + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array( + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'type' => 'numeric', + 'sentAs' => 'Bar' + ) + ) + ) + ), + array('Foo' => 'test', 'Baz' => array(1, 2)), + 'test12' + ), + // Test adding an object + array( + array( + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Bar' => array('type' => 'string'), + 'Bam' => array() + ) + ) + ) + ), + array('Foo' => 'test', 'Baz' => array('Bar' => 'abc', 'Bam' => 'foo')), + 'testabcfoo' + ), + // Add an array that contains an object + array( + array( + 'parameters' => array( + 'Baz' => array( + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'type' => 'object', + 'sentAs' => 'Bar', + 'properties' => array('A' => array(), 'B' => array()) + ) + ) + ) + ), + array('Baz' => array( + array('A' => '1', 'B' => '2'), + array('A' => '3', 'B' => '4') + )), + '1234' + ), + // Add an object of attributes + array( + array( + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Bar' => array('type' => 'string', 'data' => array('xmlAttribute' => true)), + 'Bam' => array() + ) + ) + ) + ), + array('Foo' => 'test', 'Baz' => array('Bar' => 'abc', 'Bam' => 'foo')), + 'testfoo' + ), + // Check order doesn't matter + array( + array( + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Bar' => array('type' => 'string', 'data' => array('xmlAttribute' => true)), + 'Bam' => array() + ) + ) + ) + ), + array('Foo' => 'test', 'Baz' => array('Bam' => 'foo', 'Bar' => 'abc')), + 'testfoo' + ), + // Add values with custom namespaces + array( + array( + 'parameters' => array( + 'Foo' => array( + 'location' => 'xml', + 'type' => 'string', + 'data' => array( + 'xmlNamespace' => 'http://foo.com' + ) + ) + ) + ), + array('Foo' => 'test'), + 'test' + ), + // Add attributes with custom namespace prefix + array( + array( + 'parameters' => array( + 'Wrap' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Foo' => array( + 'type' => 'string', + 'sentAs' => 'xsi:baz', + 'data' => array( + 'xmlNamespace' => 'http://foo.com', + 'xmlAttribute' => true + ) + ) + ) + ), + ) + ), + array('Wrap' => array( + 'Foo' => 'test' + )), + '' + ), + // Add nodes with custom namespace prefix + array( + array( + 'parameters' => array( + 'Wrap' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Foo' => array( + 'type' => 'string', + 'sentAs' => 'xsi:Foo', + 'data' => array( + 'xmlNamespace' => 'http://foobar.com' + ) + ) + ) + ), + ) + ), + array('Wrap' => array( + 'Foo' => 'test' + )), + 'test' + ), + array( + array( + 'parameters' => array( + 'Foo' => array( + 'location' => 'xml', + 'type' => 'string', + 'data' => array( + 'xmlNamespace' => 'http://foo.com' + ) + ) + ) + ), + array('Foo' => '

    This is a title

    '), + 'This is a title]]>' + ), + // Flat array at top level + array( + array( + 'parameters' => array( + 'Bars' => array( + 'type' => 'array', + 'data' => array('xmlFlattened' => true), + 'location' => 'xml', + 'items' => array( + 'type' => 'object', + 'sentAs' => 'Bar', + 'properties' => array( + 'A' => array(), + 'B' => array() + ) + ) + ), + 'Boos' => array( + 'type' => 'array', + 'data' => array('xmlFlattened' => true), + 'location' => 'xml', + 'items' => array( + 'sentAs' => 'Boo', + 'type' => 'string' + ) + ) + ) + ), + array( + 'Bars' => array( + array('A' => '1', 'B' => '2'), + array('A' => '3', 'B' => '4') + ), + 'Boos' => array('test', '123') + ), + '1234test123' + ), + // Nested flat arrays + array( + array( + 'parameters' => array( + 'Delete' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Items' => array( + 'type' => 'array', + 'data' => array('xmlFlattened' => true), + 'items' => array( + 'type' => 'object', + 'sentAs' => 'Item', + 'properties' => array( + 'A' => array(), + 'B' => array() + ) + ) + ) + ) + ) + ) + ), + array( + 'Delete' => array( + 'Items' => array( + array('A' => '1', 'B' => '2'), + array('A' => '3', 'B' => '4') + ) + ) + ), + '1234' + ) + ); + } + + /** + * @dataProvider xmlProvider + */ + public function testSerializesXml(array $operation, array $input, $xml) + { + $operation = new Operation($operation); + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array($input, $operation)) + ->getMockForAbstractClass(); + $command->setClient(new Client('http://www.test.com/some/path.php')); + $request = $command->prepare(); + if (!empty($input)) { + $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type')); + } else { + $this->assertNull($request->getHeader('Content-Type')); + } + $body = str_replace(array("\n", ""), '', (string) $request->getBody()); + $this->assertEquals($xml, $body); + } + + public function testAddsContentTypeAndTopLevelValues() + { + $operation = new Operation(array( + 'data' => array( + 'xmlRoot' => array( + 'name' => 'test', + 'namespaces' => array( + 'xsi' => 'http://foo.com' + ) + ) + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string'), + 'Baz' => array('location' => 'xml', 'type' => 'string') + ) + )); + + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array( + 'Foo' => 'test', + 'Baz' => 'bar' + ), $operation)) + ->getMockForAbstractClass(); + + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type')); + $this->assertEquals( + '' . "\n" + . 'testbar' . "\n", + (string) $request->getBody() + ); + } + + public function testCanChangeContentType() + { + $visitor = new XmlVisitor(); + $visitor->setContentTypeHeader('application/foo'); + $this->assertEquals('application/foo', $this->readAttribute($visitor, 'contentType')); + } + + public function testCanAddArrayOfSimpleTypes() + { + $request = new EntityEnclosingRequest('POST', 'http://foo.com'); + $visitor = new XmlVisitor(); + $param = new Parameter(array( + 'type' => 'object', + 'location' => 'xml', + 'name' => 'Out', + 'properties' => array( + 'Nodes' => array( + 'required' => true, + 'type' => 'array', + 'min' => 1, + 'items' => array('type' => 'string', 'sentAs' => 'Node') + ) + ) + )); + + $param->setParent(new Operation(array( + 'data' => array( + 'xmlRoot' => array( + 'name' => 'Test', + 'namespaces' => array( + 'https://foo/' + ) + ) + ) + ))); + + $value = array('Nodes' => array('foo', 'baz')); + $this->assertTrue($this->validator->validate($param, $value)); + $visitor->visit($this->command, $request, $param, $value); + $visitor->after($this->command, $request); + + $this->assertEquals( + "\n" + . "foobaz\n", + (string) $request->getBody() + ); + } + + public function testCanAddMultipleNamespacesToRoot() + { + $operation = new Operation(array( + 'data' => array( + 'xmlRoot' => array( + 'name' => 'Hi', + 'namespaces' => array( + 'xsi' => 'http://foo.com', + 'foo' => 'http://foobar.com' + ) + ) + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml', 'type' => 'string') + ) + )); + + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array( + 'Foo' => 'test' + ), $operation)) + ->getMockForAbstractClass(); + + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals('application/xml', (string) $request->getHeader('Content-Type')); + $this->assertEquals( + '' . "\n" + . 'test' . "\n", + (string) $request->getBody() + ); + } + + public function testValuesAreFiltered() + { + $operation = new Operation(array( + 'parameters' => array( + 'Foo' => array( + 'location' => 'xml', + 'type' => 'string', + 'filters' => array('strtoupper') + ), + 'Bar' => array( + 'location' => 'xml', + 'type' => 'object', + 'properties' => array( + 'Baz' => array( + 'filters' => array('strtoupper') + ) + ) + ) + ) + )); + + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array( + 'Foo' => 'test', + 'Bar' => array( + 'Baz' => 'abc' + ) + ), $operation)) + ->getMockForAbstractClass(); + + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals( + '' . "\n" + . 'TESTABC' . "\n", + (string) $request->getBody() + ); + } + + public function testSkipsNullValues() + { + $operation = new Operation(array( + 'parameters' => array( + 'Foo' => array( + 'location' => 'xml', + 'type' => 'string' + ), + 'Bar' => array( + 'location' => 'xml', + 'type' => 'object', + 'properties' => array( + 'Baz' => array(), + 'Bam' => array(), + ) + ), + 'Arr' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'string' + ) + ) + ) + )); + + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array( + 'Foo' => null, + 'Bar' => array( + 'Bar' => null, + 'Bam' => 'test' + ), + 'Arr' => array(null) + ), $operation)) + ->getMockForAbstractClass(); + + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals( + '' . "\n" + . 'test' . "\n", + (string) $request->getBody() + ); + } + + public function testAllowsXmlEncoding() + { + $operation = new Operation(array( + 'data' => array( + 'xmlEncoding' => 'UTF-8' + ), + 'parameters' => array( + 'Foo' => array('location' => 'xml') + ) + )); + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array('Foo' => 'test'), $operation)) + ->getMockForAbstractClass(); + $command->setClient(new Client()); + $request = $command->prepare(); + $this->assertEquals( + '' . "\n" + . 'test' . "\n", + (string) $request->getBody() + ); + } + + public function testAllowsSendingXmlPayloadIfNoXmlParamsWereSet() + { + $operation = new Operation(array( + 'httpMethod' => 'POST', + 'data' => array('xmlAllowEmpty' => true), + 'parameters' => array('Foo' => array('location' => 'xml')) + )); + $command = $this->getMockBuilder('Guzzle\Service\Command\OperationCommand') + ->setConstructorArgs(array(array(), $operation)) + ->getMockForAbstractClass(); + $command->setClient(new Client('http://foo.com')); + $request = $command->prepare(); + $this->assertEquals( + '' . "\n" + . '' . "\n", + (string) $request->getBody() + ); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php new file mode 100644 index 0000000..7b86003 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/AbstractResponseVisitorTest.php @@ -0,0 +1,29 @@ +value = array(); + $this->command = new MockCommand(); + $this->response = new Response(200, array( + 'X-Foo' => 'bar', + 'Content-Length' => 3, + 'Content-Type' => 'text/plain' + ), 'Foo'); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php new file mode 100644 index 0000000..932e39b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/BodyVisitorTest.php @@ -0,0 +1,21 @@ + 'body', 'name' => 'foo')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('Foo', (string) $this->value['foo']); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php new file mode 100644 index 0000000..db54b1a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/HeaderVisitorTest.php @@ -0,0 +1,98 @@ + 'header', + 'name' => 'ContentType', + 'sentAs' => 'Content-Type' + )); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('text/plain', $this->value['ContentType']); + } + + public function testVisitsLocationWithFilters() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'header', + 'name' => 'Content-Type', + 'filters' => array('strtoupper') + )); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('TEXT/PLAIN', $this->value['Content-Type']); + } + + public function testVisitsMappedPrefixHeaders() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'header', + 'name' => 'Metadata', + 'sentAs' => 'X-Baz-', + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'string' + ) + )); + $response = new Response(200, array( + 'X-Baz-Test' => 'ABC', + 'X-Baz-Bar' => array('123', '456'), + 'Content-Length' => 3 + ), 'Foo'); + $visitor->visit($this->command, $response, $param, $this->value); + $this->assertEquals(array( + 'Metadata' => array( + 'Test' => 'ABC', + 'Bar' => array('123', '456') + ) + ), $this->value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownHeaders() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'header', + 'name' => 'Content-Type', + 'additionalParameters' => false + )); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('text/plain', $this->value['Content-Type']); + $this->assertArrayNotHasKey('X-Foo', $this->value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownPropertiesWithAliasing() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'header', + 'name' => 'ContentType', + 'sentAs' => 'Content-Type', + 'additionalParameters' => false + )); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('text/plain', $this->value['ContentType']); + $this->assertArrayNotHasKey('X-Foo', $this->value); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php new file mode 100644 index 0000000..4f8d30b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/JsonVisitorTest.php @@ -0,0 +1,157 @@ +getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setMethods(array('getResponse')) + ->getMockForAbstractClass(); + $command->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue(new Response(200, null, '{"foo":"bar"}'))); + $result = array(); + $visitor->before($command, $result); + $this->assertEquals(array('foo' => 'bar'), $result); + } + + public function testVisitsLocation() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'array', + 'items' => array( + 'filters' => 'strtoupper', + 'type' => 'string' + ) + )); + $this->value = array('foo' => array('a', 'b', 'c')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('A', 'B', 'C'), $this->value['foo']); + } + + public function testRenamesTopLevelValues() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'sentAs' => 'Baz', + 'type' => 'string', + )); + $this->value = array('Baz' => 'test'); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => 'test'), $this->value); + } + + public function testRenamesDoesNotFailForNonExistentKey() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array( + 'bar' => array( + 'name' => 'bar', + 'sentAs' => 'baz', + ), + ), + )); + $this->value = array('foo' => array('unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('unknown' => 'Unknown')), $this->value); + } + + public function testTraversesObjectsAndAppliesFilters() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array( + 'foo' => array('filters' => 'strtoupper'), + 'bar' => array('filters' => 'strtolower') + ) + )); + $this->value = array('foo' => array('foo' => 'hello', 'bar' => 'THERE')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => 'HELLO', 'bar' => 'there'), $this->value['foo']); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownProperties() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'bar' => array( + 'type' => 'string', + 'name' => 'bar', + ), + ), + )); + $this->value = array('foo' => array('bar' => 15, 'unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('bar' => 15)), $this->value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownPropertiesWithAliasing() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'bar' => array( + 'name' => 'bar', + 'sentAs' => 'baz', + ), + ), + )); + $this->value = array('foo' => array('baz' => 15, 'unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('bar' => 15)), $this->value); + } + + public function testWalksAdditionalProperties() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'object', + 'properties' => array( + 'bar' => array( + 'type' => 'string', + 'filters' => array('base64_decode') + ) + ), + ), + )); + $this->value = array('foo' => array('baz' => array('bar' => 'Zm9v'))); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('foo', $this->value['foo']['baz']['bar']); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php new file mode 100644 index 0000000..23cd40f --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/ReasonPhraseVisitorTest.php @@ -0,0 +1,21 @@ + 'reasonPhrase', 'name' => 'phrase')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals('OK', $this->value['phrase']); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php new file mode 100644 index 0000000..7211a58 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/StatusCodeVisitorTest.php @@ -0,0 +1,21 @@ + 'statusCode', 'name' => 'code')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(200, $this->value['code']); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php new file mode 100644 index 0000000..f87cec7 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/Response/XmlVisitorTest.php @@ -0,0 +1,431 @@ +getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setMethods(array('getResponse')) + ->getMockForAbstractClass(); + $command->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue(new Response(200, null, 'test'))); + $result = array(); + $visitor->before($command, $result); + $this->assertEquals(array('Bar' => 'test'), $result); + } + + public function testBeforeMethodParsesXmlWithNamespace() + { + $this->markTestSkipped("Response/XmlVisitor cannot accept 'xmlns' in response, see #368 (http://git.io/USa1mA)."); + + $visitor = new Visitor(); + $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setMethods(array('getResponse')) + ->getMockForAbstractClass(); + $command->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue(new Response(200, null, 'test'))); + $result = array(); + $visitor->before($command, $result); + $this->assertEquals(array('Bar' => 'test'), $result); + } + + public function testBeforeMethodParsesNestedXml() + { + $visitor = new Visitor(); + $command = $this->getMockBuilder('Guzzle\Service\Command\AbstractCommand') + ->setMethods(array('getResponse')) + ->getMockForAbstractClass(); + $command->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue(new Response(200, null, 'test'))); + $result = array(); + $visitor->before($command, $result); + $this->assertEquals(array('Items' => array('Bar' => 'test')), $result); + } + + public function testCanExtractAndRenameTopLevelXmlValues() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'xml', + 'name' => 'foo', + 'sentAs' => 'Bar' + )); + $value = array('Bar' => 'test'); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertArrayHasKey('foo', $value); + $this->assertEquals('test', $value['foo']); + } + + public function testEnsuresRepeatedArraysAreInCorrectLocations() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'xml', + 'name' => 'foo', + 'sentAs' => 'Foo', + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'Bar' => array('type' => 'string'), + 'Baz' => array('type' => 'string'), + 'Bam' => array('type' => 'string') + ) + ) + )); + + $xml = new \SimpleXMLElement('12'); + $value = json_decode(json_encode($xml), true); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array( + 'foo' => array( + array ( + 'Bar' => '1', + 'Baz' => '2' + ) + ) + ), $value); + } + + public function testEnsuresFlatArraysAreFlat() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'location' => 'xml', + 'name' => 'foo', + 'type' => 'array', + 'items' => array('type' => 'string') + )); + + $value = array('foo' => array('bar', 'baz')); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array('foo' => array('bar', 'baz')), $value); + + $value = array('foo' => 'bar'); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array('foo' => array('bar')), $value); + } + + public function xmlDataProvider() + { + $param = new Parameter(array( + 'location' => 'xml', + 'name' => 'Items', + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'name' => 'Item', + 'properties' => array( + 'Bar' => array('type' => 'string'), + 'Baz' => array('type' => 'string') + ) + ) + )); + + return array( + array($param, '12', array( + 'Items' => array( + array('Bar' => 1), + array('Bar' => 2) + ) + )), + array($param, '1', array( + 'Items' => array( + array('Bar' => 1) + ) + )), + array($param, '', array( + 'Items' => array() + )) + ); + } + + /** + * @dataProvider xmlDataProvider + */ + public function testEnsuresWrappedArraysAreInCorrectLocations($param, $xml, $result) + { + $visitor = new Visitor(); + $xml = new \SimpleXMLElement($xml); + $value = json_decode(json_encode($xml), true); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals($result, $value); + } + + public function testCanRenameValues() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'TerminatingInstances', + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'instancesSet', + 'items' => array( + 'name' => 'item', + 'type' => 'object', + 'sentAs' => 'item', + 'properties' => array( + 'InstanceId' => array( + 'type' => 'string', + 'sentAs' => 'instanceId', + ), + 'CurrentState' => array( + 'type' => 'object', + 'sentAs' => 'currentState', + 'properties' => array( + 'Code' => array( + 'type' => 'numeric', + 'sentAs' => 'code', + ), + 'Name' => array( + 'type' => 'string', + 'sentAs' => 'name', + ), + ), + ), + 'PreviousState' => array( + 'type' => 'object', + 'sentAs' => 'previousState', + 'properties' => array( + 'Code' => array( + 'type' => 'numeric', + 'sentAs' => 'code', + ), + 'Name' => array( + 'type' => 'string', + 'sentAs' => 'name', + ), + ), + ), + ), + ) + )); + + $value = array( + 'instancesSet' => array ( + 'item' => array ( + 'instanceId' => 'i-3ea74257', + 'currentState' => array( + 'code' => '32', + 'name' => 'shutting-down', + ), + 'previousState' => array( + 'code' => '16', + 'name' => 'running', + ), + ), + ) + ); + + $visitor->visit($this->command, $this->response, $param, $value); + + $this->assertEquals(array( + 'TerminatingInstances' => array( + array( + 'InstanceId' => 'i-3ea74257', + 'CurrentState' => array( + 'Code' => '32', + 'Name' => 'shutting-down', + ), + 'PreviousState' => array( + 'Code' => '16', + 'Name' => 'running', + ) + ) + ) + ), $value); + } + + public function testCanRenameAttributes() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'RunningQueues', + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'type' => 'object', + 'sentAs' => 'item', + 'properties' => array( + 'QueueId' => array( + 'type' => 'string', + 'sentAs' => 'queue_id', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + 'CurrentState' => array( + 'type' => 'object', + 'properties' => array( + 'Code' => array( + 'type' => 'numeric', + 'sentAs' => 'code', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + 'Name' => array( + 'sentAs' => 'name', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + ), + ), + 'PreviousState' => array( + 'type' => 'object', + 'properties' => array( + 'Code' => array( + 'type' => 'numeric', + 'sentAs' => 'code', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + 'Name' => array( + 'sentAs' => 'name', + 'data' => array( + 'xmlAttribute' => true, + ), + ), + ), + ), + ), + ) + )); + + $xml = ''; + $value = json_decode(json_encode(new \SimpleXMLElement($xml)), true); + $visitor->visit($this->command, $this->response, $param, $value); + + $this->assertEquals(array( + 'RunningQueues' => array( + array( + 'QueueId' => 'q-3ea74257', + 'CurrentState' => array( + 'Code' => '32', + 'Name' => 'processing', + ), + 'PreviousState' => array( + 'Code' => '16', + 'Name' => 'wait', + ), + ), + ) + ), $value); + } + + public function testAddsEmptyArraysWhenValueIsMissing() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'Foo', + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'Baz' => array('type' => 'array'), + 'Bar' => array( + 'type' => 'object', + 'properties' => array( + 'Baz' => array('type' => 'array'), + ) + ) + ) + ) + )); + + $value = array(); + $visitor->visit($this->command, $this->response, $param, $value); + + $value = array( + 'Foo' => array( + 'Bar' => array() + ) + ); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array( + 'Foo' => array( + array( + 'Bar' => array() + ) + ) + ), $value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownProperties() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'bar' => array( + 'type' => 'string', + 'name' => 'bar', + ), + ), + )); + $this->value = array('foo' => array('bar' => 15, 'unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('bar' => 15)), $this->value); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testDiscardingUnknownPropertiesWithAliasing() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'bar' => array( + 'name' => 'bar', + 'sentAs' => 'baz', + ), + ), + )); + $this->value = array('foo' => array('baz' => 15, 'unknown' => 'Unknown')); + $visitor->visit($this->command, $this->response, $param, $this->value); + $this->assertEquals(array('foo' => array('bar' => 15)), $this->value); + } + + public function testProperlyHandlesEmptyStringValues() + { + $visitor = new Visitor(); + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array( + 'bar' => array('type' => 'string') + ), + )); + $xml = ''; + $value = json_decode(json_encode(new \SimpleXMLElement($xml)), true); + $visitor->visit($this->command, $this->response, $param, $value); + $this->assertEquals(array('foo' => array('bar' => '')), $value); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php new file mode 100644 index 0000000..a252ffe --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/LocationVisitor/VisitorFlyweightTest.php @@ -0,0 +1,53 @@ +assertInstanceOf('Guzzle\Service\Command\LocationVisitor\Request\JsonVisitor', $f->getRequestVisitor('json')); + $this->assertInstanceOf('Guzzle\Service\Command\LocationVisitor\Response\JsonVisitor', $f->getResponseVisitor('json')); + } + + public function testCanUseCustomMappings() + { + $f = new VisitorFlyweight(array()); + $this->assertEquals(array(), $this->readAttribute($f, 'mappings')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage No request visitor has been mapped for foo + */ + public function testThrowsExceptionWhenRetrievingUnknownVisitor() + { + VisitorFlyweight::getInstance()->getRequestVisitor('foo'); + } + + public function testCachesVisitors() + { + $f = new VisitorFlyweight(); + $v1 = $f->getRequestVisitor('json'); + $this->assertSame($v1, $f->getRequestVisitor('json')); + } + + public function testAllowsAddingVisitors() + { + $f = new VisitorFlyweight(); + $j1 = new JsonRequestVisitor(); + $j2 = new JsonResponseVisitor(); + $f->addRequestVisitor('json', $j1); + $f->addResponseVisitor('json', $j2); + $this->assertSame($j1, $f->getRequestVisitor('json')); + $this->assertSame($j2, $f->getResponseVisitor('json')); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php new file mode 100644 index 0000000..95fb533 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationCommandTest.php @@ -0,0 +1,102 @@ +getRequestSerializer(); + $b = new DefaultRequestSerializer(VisitorFlyweight::getInstance()); + $operation->setRequestSerializer($b); + $this->assertNotSame($a, $operation->getRequestSerializer()); + } + + public function testPreparesRequestUsingSerializer() + { + $op = new OperationCommand(array(), new Operation()); + $op->setClient(new Client()); + $s = $this->getMockBuilder('Guzzle\Service\Command\RequestSerializerInterface') + ->setMethods(array('prepare')) + ->getMockForAbstractClass(); + $s->expects($this->once()) + ->method('prepare') + ->will($this->returnValue(new EntityEnclosingRequest('POST', 'http://foo.com'))); + $op->setRequestSerializer($s); + $op->prepare(); + } + + public function testParsesResponsesWithResponseParser() + { + $op = new OperationCommand(array(), new Operation()); + $p = $this->getMockBuilder('Guzzle\Service\Command\ResponseParserInterface') + ->setMethods(array('parse')) + ->getMockForAbstractClass(); + $p->expects($this->once()) + ->method('parse') + ->will($this->returnValue(array('foo' => 'bar'))); + $op->setResponseParser($p); + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200), true); + $this->assertEquals(array('foo' => 'bar'), $op->execute()); + } + + public function testParsesResponsesUsingModelParserWhenMatchingModelIsFound() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'foo' => array('responseClass' => 'bar', 'responseType' => 'model') + ), + 'models' => array( + 'bar' => array( + 'type' => 'object', + 'properties' => array( + 'Baz' => array('type' => 'string', 'location' => 'xml') + ) + ) + ) + )); + + $op = new OperationCommand(array(), $description->getOperation('foo')); + $op->setClient(new Client()); + $request = $op->prepare(); + $request->setResponse(new Response(200, array( + 'Content-Type' => 'application/xml' + ), 'Bar'), true); + $result = $op->execute(); + $this->assertEquals(new Model(array('Baz' => 'Bar')), $result); + } + + public function testAllowsRawResponses() + { + $description = new ServiceDescription(array( + 'operations' => array('foo' => array('responseClass' => 'bar', 'responseType' => 'model')), + 'models' => array('bar' => array()) + )); + $op = new OperationCommand(array( + OperationCommand::RESPONSE_PROCESSING => OperationCommand::TYPE_RAW + ), $description->getOperation('foo')); + $op->setClient(new Client()); + $request = $op->prepare(); + $response = new Response(200, array( + 'Content-Type' => 'application/xml' + ), 'Bar'); + $request->setResponse($response, true); + $this->assertSame($response, $op->execute()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php new file mode 100644 index 0000000..69ba1fc --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Command/OperationResponseParserTest.php @@ -0,0 +1,335 @@ +addVisitor('foo', $visitor); + $this->assertSame($visitor, $this->readAttribute($p, 'factory')->getResponseVisitor('foo')); + } + + public function testUsesParentParser() + { + $p = new OperationResponseParser(new VisitorFlyweight()); + $operation = new Operation(); + $operation->setServiceDescription(new ServiceDescription()); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($p)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/xml'), 'C'), true); + $this->assertInstanceOf('SimpleXMLElement', $op->execute()); + } + + public function testVisitsLocations() + { + $parser = new OperationResponseParser(new VisitorFlyweight(array())); + $parser->addVisitor('statusCode', new StatusCodeVisitor()); + $parser->addVisitor('reasonPhrase', new ReasonPhraseVisitor()); + $parser->addVisitor('json', new JsonVisitor()); + $op = new OperationCommand(array(), $this->getDescription()->getOperation('test')); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(201), true); + $result = $op->execute(); + $this->assertEquals(201, $result['code']); + $this->assertEquals('Created', $result['phrase']); + } + + public function testVisitsLocationsForJsonResponse() + { + $parser = OperationResponseParser::getInstance(); + $operation = $this->getDescription()->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array( + 'Content-Type' => 'application/json' + ), '{"baz":"bar","enigma":"123"}'), true); + $result = $op->execute(); + $this->assertEquals(array( + 'baz' => 'bar', + 'enigma' => '123', + 'code' => 200, + 'phrase' => 'OK' + ), $result->toArray()); + } + + public function testSkipsUnkownModels() + { + $parser = OperationResponseParser::getInstance(); + $operation = $this->getDescription()->getOperation('test'); + $operation->setResponseClass('Baz')->setResponseType('model'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(201), true); + $this->assertInstanceOf('Guzzle\Http\Message\Response', $op->execute()); + } + + public function testAllowsModelProcessingToBeDisabled() + { + $parser = OperationResponseParser::getInstance(); + $operation = $this->getDescription()->getOperation('test'); + $op = new OperationCommand(array('command.response_processing' => 'native'), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array( + 'Content-Type' => 'application/json' + ), '{"baz":"bar","enigma":"123"}'), true); + $result = $op->execute(); + $this->assertInstanceOf('Guzzle\Service\Resource\Model', $result); + $this->assertEquals(array( + 'baz' => 'bar', + 'enigma' => '123' + ), $result->toArray()); + } + + public function testCanInjectModelSchemaIntoModels() + { + $parser = new OperationResponseParser(VisitorFlyweight::getInstance(), true); + $desc = $this->getDescription(); + $operation = $desc->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array( + 'Content-Type' => 'application/json' + ), '{"baz":"bar","enigma":"123"}'), true); + $result = $op->execute(); + $this->assertSame($result->getStructure(), $desc->getModel('Foo')); + } + + public function testDoesNotParseXmlWhenNotUsingXmlVisitor() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Foo' => array( + 'type' => 'object', + 'properties' => array('baz' => array('location' => 'body')) + ) + ) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $brokenXml = '<><><>>>>'; + $op->prepare()->setResponse(new Response(200, array( + 'Content-Type' => 'application/xml' + ), $brokenXml), true); + $result = $op->execute(); + $this->assertEquals(array('baz'), $result->getKeys()); + $this->assertEquals($brokenXml, (string) $result['baz']); + } + + public function testVisitsAdditionalProperties() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Foo' => array( + 'type' => 'object', + 'properties' => array( + 'code' => array('location' => 'statusCode') + ), + 'additionalProperties' => array( + 'location' => 'json', + 'type' => 'object', + 'properties' => array( + 'a' => array( + 'type' => 'string', + 'filters' => 'strtoupper' + ) + ) + ) + ) + ) + )); + + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $json = '[{"a":"test"},{"a":"baz"}]'; + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true); + $result = $op->execute()->toArray(); + $this->assertEquals(array( + 'code' => 200, + array('a' => 'TEST'), + array('a' => 'BAZ') + ), $result); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/399 + */ + public function testAdditionalPropertiesDisabledDiscardsData() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Foo' => array( + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'name' => array( + 'location' => 'json', + 'type' => 'string', + ), + 'nested' => array( + 'location' => 'json', + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'width' => array( + 'type' => 'integer' + ) + ), + ), + 'code' => array('location' => 'statusCode') + ), + + ) + ) + )); + + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $json = '{"name":"test", "volume":2.0, "nested":{"width":10,"bogus":1}}'; + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true); + $result = $op->execute()->toArray(); + $this->assertEquals(array( + 'name' => 'test', + 'nested' => array( + 'width' => 10, + ), + 'code' => 200 + ), $result); + } + + public function testCreatesCustomResponseClassInterface() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Guzzle\Tests\Mock\CustomResponseModel')) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true); + $result = $op->execute(); + $this->assertInstanceOf('Guzzle\Tests\Mock\CustomResponseModel', $result); + $this->assertSame($op, $result->command); + } + + /** + * @expectedException \Guzzle\Service\Exception\ResponseClassException + * @expectedExceptionMessage must exist + */ + public function testEnsuresResponseClassExists() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo\Baz\Bar')) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true); + $op->execute(); + } + + /** + * @expectedException \Guzzle\Service\Exception\ResponseClassException + * @expectedExceptionMessage and implement + */ + public function testEnsuresResponseClassImplementsResponseClassInterface() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => __CLASS__)) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), 'hi!'), true); + $op->execute(); + } + + protected function getDescription() + { + return ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Foo' => array( + 'type' => 'object', + 'properties' => array( + 'baz' => array('type' => 'string', 'location' => 'json'), + 'code' => array('location' => 'statusCode'), + 'phrase' => array('location' => 'reasonPhrase'), + ) + ) + ) + )); + } + + public function testCanAddListenerToParseDomainObjects() + { + $client = new Client(); + $client->setDescription(ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'FooBazBar')) + ))); + $foo = new \stdClass(); + $client->getEventDispatcher()->addListener('command.parse_response', function ($e) use ($foo) { + $e['result'] = $foo; + }); + $command = $client->getCommand('test'); + $command->prepare()->setResponse(new Response(200), true); + $result = $command->execute(); + $this->assertSame($result, $foo); + } + + /** + * @group issue-399 + * @link https://github.com/guzzle/guzzle/issues/501 + */ + public function testAdditionalPropertiesWithRefAreResolved() + { + $parser = OperationResponseParser::getInstance(); + $description = ServiceDescription::factory(array( + 'operations' => array('test' => array('responseClass' => 'Foo')), + 'models' => array( + 'Baz' => array('type' => 'string'), + 'Foo' => array( + 'type' => 'object', + 'additionalProperties' => array('$ref' => 'Baz', 'location' => 'json') + ) + ) + )); + $operation = $description->getOperation('test'); + $op = new OperationCommand(array(), $operation); + $op->setResponseParser($parser)->setClient(new Client()); + $json = '{"a":"a","b":"b","c":"c"}'; + $op->prepare()->setResponse(new Response(200, array('Content-Type' => 'application/json'), $json), true); + $result = $op->execute()->toArray(); + $this->assertEquals(array('a' => 'a', 'b' => 'b', 'c' => 'c'), $result); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php new file mode 100644 index 0000000..ae33b69 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/OperationTest.php @@ -0,0 +1,308 @@ + 'test', + 'summary' => 'doc', + 'notes' => 'notes', + 'documentationUrl' => 'http://www.example.com', + 'httpMethod' => 'POST', + 'uri' => '/api/v1', + 'responseClass' => 'array', + 'responseNotes' => 'returns the json_decoded response', + 'deprecated' => true, + 'parameters' => array( + 'key' => array( + 'required' => true, + 'type' => 'string', + 'maxLength' => 10 + ), + 'key_2' => array( + 'required' => true, + 'type' => 'integer', + 'default' => 10 + ) + ) + )); + + $this->assertEquals('test', $c->getName()); + $this->assertEquals('doc', $c->getSummary()); + $this->assertEquals('http://www.example.com', $c->getDocumentationUrl()); + $this->assertEquals('POST', $c->getHttpMethod()); + $this->assertEquals('/api/v1', $c->getUri()); + $this->assertEquals('array', $c->getResponseClass()); + $this->assertEquals('returns the json_decoded response', $c->getResponseNotes()); + $this->assertTrue($c->getDeprecated()); + $this->assertEquals('Guzzle\\Service\\Command\\OperationCommand', $c->getClass()); + $this->assertEquals(array( + 'key' => new Parameter(array( + 'name' => 'key', + 'required' => true, + 'type' => 'string', + 'maxLength' => 10, + 'parent' => $c + )), + 'key_2' => new Parameter(array( + 'name' => 'key_2', + 'required' => true, + 'type' => 'integer', + 'default' => 10, + 'parent' => $c + )) + ), $c->getParams()); + + $this->assertEquals(new Parameter(array( + 'name' => 'key_2', + 'required' => true, + 'type' => 'integer', + 'default' => 10, + 'parent' => $c + )), $c->getParam('key_2')); + + $this->assertNull($c->getParam('afefwef')); + $this->assertArrayNotHasKey('parent', $c->getParam('key_2')->toArray()); + } + + public function testAllowsConcreteCommands() + { + $c = new Operation(array( + 'name' => 'test', + 'class' => 'Guzzle\\Service\\Command\ClosureCommand', + 'parameters' => array( + 'p' => new Parameter(array( + 'name' => 'foo' + )) + ) + )); + $this->assertEquals('Guzzle\\Service\\Command\ClosureCommand', $c->getClass()); + } + + public function testConvertsToArray() + { + $data = array( + 'name' => 'test', + 'class' => 'Guzzle\\Service\\Command\ClosureCommand', + 'summary' => 'test', + 'documentationUrl' => 'http://www.example.com', + 'httpMethod' => 'PUT', + 'uri' => '/', + 'parameters' => array('p' => array('name' => 'foo')) + ); + $c = new Operation($data); + $toArray = $c->toArray(); + unset($data['name']); + $this->assertArrayHasKey('parameters', $toArray); + $this->assertInternalType('array', $toArray['parameters']); + + // Normalize the array + unset($data['parameters']); + unset($toArray['parameters']); + + $data['responseType'] = 'primitive'; + $data['responseClass'] = 'array'; + $this->assertEquals($data, $toArray); + } + + public function testDeterminesIfHasParam() + { + $command = $this->getTestCommand(); + $this->assertTrue($command->hasParam('data')); + $this->assertFalse($command->hasParam('baz')); + } + + public function testReturnsParamNames() + { + $command = $this->getTestCommand(); + $this->assertEquals(array('data'), $command->getParamNames()); + } + + protected function getTestCommand() + { + return new Operation(array( + 'parameters' => array( + 'data' => new Parameter(array( + 'type' => 'string' + )) + ) + )); + } + + public function testCanBuildUpCommands() + { + $c = new Operation(array()); + $c->setName('foo') + ->setClass('Baz') + ->setDeprecated(false) + ->setSummary('summary') + ->setDocumentationUrl('http://www.foo.com') + ->setHttpMethod('PUT') + ->setResponseNotes('oh') + ->setResponseClass('string') + ->setUri('/foo/bar') + ->addParam(new Parameter(array( + 'name' => 'test' + ))); + + $this->assertEquals('foo', $c->getName()); + $this->assertEquals('Baz', $c->getClass()); + $this->assertEquals(false, $c->getDeprecated()); + $this->assertEquals('summary', $c->getSummary()); + $this->assertEquals('http://www.foo.com', $c->getDocumentationUrl()); + $this->assertEquals('PUT', $c->getHttpMethod()); + $this->assertEquals('oh', $c->getResponseNotes()); + $this->assertEquals('string', $c->getResponseClass()); + $this->assertEquals('/foo/bar', $c->getUri()); + $this->assertEquals(array('test'), $c->getParamNames()); + } + + public function testCanRemoveParams() + { + $c = new Operation(array()); + $c->addParam(new Parameter(array('name' => 'foo'))); + $this->assertTrue($c->hasParam('foo')); + $c->removeParam('foo'); + $this->assertFalse($c->hasParam('foo')); + } + + public function testAddsNameToParametersIfNeeded() + { + $command = new Operation(array('parameters' => array('foo' => new Parameter(array())))); + $this->assertEquals('foo', $command->getParam('foo')->getName()); + } + + public function testContainsApiErrorInformation() + { + $command = $this->getOperation(); + $this->assertEquals(1, count($command->getErrorResponses())); + $arr = $command->toArray(); + $this->assertEquals(1, count($arr['errorResponses'])); + $command->addErrorResponse(400, 'Foo', 'Baz\\Bar'); + $this->assertEquals(2, count($command->getErrorResponses())); + $command->setErrorResponses(array()); + $this->assertEquals(0, count($command->getErrorResponses())); + } + + public function testHasNotes() + { + $o = new Operation(array('notes' => 'foo')); + $this->assertEquals('foo', $o->getNotes()); + $o->setNotes('bar'); + $this->assertEquals('bar', $o->getNotes()); + } + + public function testHasData() + { + $o = new Operation(array('data' => array('foo' => 'baz', 'bar' => 123))); + $o->setData('test', false); + $this->assertEquals('baz', $o->getData('foo')); + $this->assertEquals(123, $o->getData('bar')); + $this->assertNull($o->getData('wfefwe')); + $this->assertEquals(array( + 'parameters' => array(), + 'class' => 'Guzzle\Service\Command\OperationCommand', + 'data' => array('foo' => 'baz', 'bar' => 123, 'test' => false), + 'responseClass' => 'array', + 'responseType' => 'primitive' + ), $o->toArray()); + } + + public function testHasServiceDescription() + { + $s = new ServiceDescription(); + $o = new Operation(array(), $s); + $this->assertSame($s, $o->getServiceDescription()); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesResponseType() + { + $o = new Operation(array('responseClass' => 'array', 'responseType' => 'foo')); + } + + public function testInfersResponseType() + { + $o = $this->getOperation(); + $o->setServiceDescription(new ServiceDescription(array('models' => array('Foo' => array())))); + $this->assertEquals('primitive', $o->getResponseType()); + $this->assertEquals('primitive', $o->setResponseClass('boolean')->getResponseType()); + $this->assertEquals('primitive', $o->setResponseClass('array')->getResponseType()); + $this->assertEquals('primitive', $o->setResponseClass('integer')->getResponseType()); + $this->assertEquals('primitive', $o->setResponseClass('string')->getResponseType()); + $this->assertEquals('class', $o->setResponseClass('foo')->getResponseType()); + $this->assertEquals('class', $o->setResponseClass(__CLASS__)->getResponseType()); + $this->assertEquals('model', $o->setResponseClass('Foo')->getResponseType()); + } + + public function testHasResponseType() + { + // infers in the constructor + $o = new Operation(array('responseClass' => 'array')); + $this->assertEquals('primitive', $o->getResponseType()); + // Infers when set + $o = new Operation(); + $this->assertEquals('primitive', $o->getResponseType()); + $this->assertEquals('model', $o->setResponseType('model')->getResponseType()); + } + + public function testHasAdditionalParameters() + { + $o = new Operation(array( + 'additionalParameters' => array( + 'type' => 'string', 'name' => 'binks' + ), + 'parameters' => array( + 'foo' => array('type' => 'integer') + ) + )); + $this->assertEquals('string', $o->getAdditionalParameters()->getType()); + $arr = $o->toArray(); + $this->assertEquals(array( + 'type' => 'string' + ), $arr['additionalParameters']); + } + + /** + * @return Operation + */ + protected function getOperation() + { + return new Operation(array( + 'name' => 'OperationTest', + 'class' => get_class($this), + 'parameters' => array( + 'test' => array('type' => 'object'), + 'bool_1' => array('default' => true, 'type' => 'boolean'), + 'bool_2' => array('default' => false), + 'float' => array('type' => 'numeric'), + 'int' => array('type' => 'integer'), + 'date' => array('type' => 'string'), + 'timestamp' => array('type' => 'string'), + 'string' => array('type' => 'string'), + 'username' => array('type' => 'string', 'required' => true, 'filters' => 'strtolower'), + 'test_function' => array('type' => 'string', 'filters' => __CLASS__ . '::strtoupper') + ), + 'errorResponses' => array( + array('code' => 503, 'reason' => 'InsufficientCapacity', 'class' => 'Guzzle\\Exception\\RuntimeException') + ) + )); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php new file mode 100644 index 0000000..b9c162a --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ParameterTest.php @@ -0,0 +1,411 @@ + 'foo', + 'type' => 'bar', + 'required' => true, + 'default' => '123', + 'description' => '456', + 'minLength' => 2, + 'maxLength' => 5, + 'location' => 'body', + 'static' => 'static!', + 'filters' => array('trim', 'json_encode') + ); + + public function testCreatesParamFromArray() + { + $p = new Parameter($this->data); + $this->assertEquals('foo', $p->getName()); + $this->assertEquals('bar', $p->getType()); + $this->assertEquals(true, $p->getRequired()); + $this->assertEquals('123', $p->getDefault()); + $this->assertEquals('456', $p->getDescription()); + $this->assertEquals(2, $p->getMinLength()); + $this->assertEquals(5, $p->getMaxLength()); + $this->assertEquals('body', $p->getLocation()); + $this->assertEquals('static!', $p->getStatic()); + $this->assertEquals(array('trim', 'json_encode'), $p->getFilters()); + } + + public function testCanConvertToArray() + { + $p = new Parameter($this->data); + unset($this->data['name']); + $this->assertEquals($this->data, $p->toArray()); + } + + public function testUsesStatic() + { + $d = $this->data; + $d['default'] = 'booboo'; + $d['static'] = true; + $p = new Parameter($d); + $this->assertEquals('booboo', $p->getValue('bar')); + } + + public function testUsesDefault() + { + $d = $this->data; + $d['default'] = 'foo'; + $d['static'] = null; + $p = new Parameter($d); + $this->assertEquals('foo', $p->getValue(null)); + } + + public function testReturnsYourValue() + { + $d = $this->data; + $d['static'] = null; + $p = new Parameter($d); + $this->assertEquals('foo', $p->getValue('foo')); + } + + public function testZeroValueDoesNotCauseDefaultToBeReturned() + { + $d = $this->data; + $d['default'] = '1'; + $d['static'] = null; + $p = new Parameter($d); + $this->assertEquals('0', $p->getValue('0')); + } + + public function testFiltersValues() + { + $d = $this->data; + $d['static'] = null; + $d['filters'] = 'strtoupper'; + $p = new Parameter($d); + $this->assertEquals('FOO', $p->filter('foo')); + } + + public function testConvertsBooleans() + { + $p = new Parameter(array('type' => 'boolean')); + $this->assertEquals(true, $p->filter('true')); + $this->assertEquals(false, $p->filter('false')); + } + + public function testUsesArrayByDefaultForFilters() + { + $d = $this->data; + $d['filters'] = null; + $p = new Parameter($d); + $this->assertEquals(array(), $p->getFilters()); + } + + public function testAllowsSimpleLocationValue() + { + $p = new Parameter(array('name' => 'myname', 'location' => 'foo', 'sentAs' => 'Hello')); + $this->assertEquals('foo', $p->getLocation()); + $this->assertEquals('Hello', $p->getSentAs()); + } + + public function testParsesTypeValues() + { + $p = new Parameter(array('type' => 'foo')); + $this->assertEquals('foo', $p->getType()); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage A [method] value must be specified for each complex filter + */ + public function testValidatesComplexFilters() + { + $p = new Parameter(array('filters' => array(array('args' => 'foo')))); + } + + public function testCanBuildUpParams() + { + $p = new Parameter(array()); + $p->setName('foo') + ->setDescription('c') + ->setFilters(array('d')) + ->setLocation('e') + ->setSentAs('f') + ->setMaxLength(1) + ->setMinLength(1) + ->setMinimum(2) + ->setMaximum(2) + ->setMinItems(3) + ->setMaxItems(3) + ->setRequired(true) + ->setStatic(true) + ->setDefault('h') + ->setType('i'); + + $p->addFilter('foo'); + + $this->assertEquals('foo', $p->getName()); + $this->assertEquals('h', $p->getDefault()); + $this->assertEquals('c', $p->getDescription()); + $this->assertEquals(array('d', 'foo'), $p->getFilters()); + $this->assertEquals('e', $p->getLocation()); + $this->assertEquals('f', $p->getSentAs()); + $this->assertEquals(1, $p->getMaxLength()); + $this->assertEquals(1, $p->getMinLength()); + $this->assertEquals(2, $p->getMaximum()); + $this->assertEquals(2, $p->getMinimum()); + $this->assertEquals(3, $p->getMaxItems()); + $this->assertEquals(3, $p->getMinItems()); + $this->assertEquals(true, $p->getRequired()); + $this->assertEquals(true, $p->getStatic()); + $this->assertEquals('i', $p->getType()); + } + + public function testAllowsNestedShape() + { + $command = $this->getServiceBuilder()->get('mock')->getCommand('mock_command')->getOperation(); + $param = new Parameter(array( + 'parent' => $command, + 'name' => 'foo', + 'type' => 'object', + 'location' => 'query', + 'properties' => array( + 'foo' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'baz' => array( + 'name' => 'baz', + 'type' => 'bool', + ) + ) + ), + 'bar' => array( + 'name' => 'bar', + 'default' => '123' + ) + ) + )); + + $this->assertSame($command, $param->getParent()); + $this->assertNotEmpty($param->getProperties()); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getProperty('foo')); + $this->assertSame($param, $param->getProperty('foo')->getParent()); + $this->assertSame($param->getProperty('foo'), $param->getProperty('foo')->getProperty('baz')->getParent()); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getProperty('bar')); + $this->assertSame($param, $param->getProperty('bar')->getParent()); + + $array = $param->toArray(); + $this->assertInternalType('array', $array['properties']); + $this->assertArrayHasKey('foo', $array['properties']); + $this->assertArrayHasKey('bar', $array['properties']); + } + + public function testAllowsComplexFilters() + { + $that = $this; + $param = new Parameter(array()); + $param->setFilters(array(array('method' => function ($a, $b, $c, $d) use ($that, $param) { + $that->assertEquals('test', $a); + $that->assertEquals('my_value!', $b); + $that->assertEquals('bar', $c); + $that->assertSame($param, $d); + return 'abc' . $b; + }, 'args' => array('test', '@value', 'bar', '@api')))); + $this->assertEquals('abcmy_value!', $param->filter('my_value!')); + } + + public function testCanChangeParentOfNestedParameter() + { + $param1 = new Parameter(array('name' => 'parent')); + $param2 = new Parameter(array('name' => 'child')); + $param2->setParent($param1); + $this->assertSame($param1, $param2->getParent()); + } + + public function testCanRemoveFromNestedStructure() + { + $param1 = new Parameter(array('name' => 'parent')); + $param2 = new Parameter(array('name' => 'child')); + $param1->addProperty($param2); + $this->assertSame($param1, $param2->getParent()); + $this->assertSame($param2, $param1->getProperty('child')); + + // Remove a single child from the structure + $param1->removeProperty('child'); + $this->assertNull($param1->getProperty('child')); + // Remove the entire structure + $param1->addProperty($param2); + $param1->removeProperty('child'); + $this->assertNull($param1->getProperty('child')); + } + + public function testAddsAdditionalProperties() + { + $p = new Parameter(array( + 'type' => 'object', + 'additionalProperties' => array('type' => 'string') + )); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $p->getAdditionalProperties()); + $this->assertNull($p->getAdditionalProperties()->getAdditionalProperties()); + $p = new Parameter(array('type' => 'object')); + $this->assertTrue($p->getAdditionalProperties()); + } + + public function testAddsItems() + { + $p = new Parameter(array( + 'type' => 'array', + 'items' => array('type' => 'string') + )); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $p->getItems()); + $out = $p->toArray(); + $this->assertEquals('array', $out['type']); + $this->assertInternalType('array', $out['items']); + } + + public function testHasExtraProperties() + { + $p = new Parameter(); + $this->assertEquals(array(), $p->getData()); + $p->setData(array('foo' => 'bar')); + $this->assertEquals('bar', $p->getData('foo')); + $p->setData('baz', 'boo'); + $this->assertEquals(array('foo' => 'bar', 'baz' => 'boo'), $p->getData()); + } + + public function testCanRetrieveKnownPropertiesUsingDataMethod() + { + $p = new Parameter(); + $this->assertEquals(null, $p->getData('foo')); + $p->setName('test'); + $this->assertEquals('test', $p->getData('name')); + } + + public function testHasInstanceOf() + { + $p = new Parameter(); + $this->assertNull($p->getInstanceOf()); + $p->setInstanceOf('Foo'); + $this->assertEquals('Foo', $p->getInstanceOf()); + } + + public function testHasPattern() + { + $p = new Parameter(); + $this->assertNull($p->getPattern()); + $p->setPattern('/[0-9]+/'); + $this->assertEquals('/[0-9]+/', $p->getPattern()); + } + + public function testHasEnum() + { + $p = new Parameter(); + $this->assertNull($p->getEnum()); + $p->setEnum(array('foo', 'bar')); + $this->assertEquals(array('foo', 'bar'), $p->getEnum()); + } + + public function testSerializesItems() + { + $p = new Parameter(array( + 'type' => 'object', + 'additionalProperties' => array('type' => 'string') + )); + $this->assertEquals(array( + 'type' => 'object', + 'additionalProperties' => array('type' => 'string') + ), $p->toArray()); + } + + public function testResolvesRefKeysRecursively() + { + $description = new ServiceDescription(array( + 'models' => array( + 'JarJar' => array('type' => 'string', 'default' => 'Mesa address tha senate!'), + 'Anakin' => array('type' => 'array', 'items' => array('$ref' => 'JarJar')) + ) + )); + $p = new Parameter(array('$ref' => 'Anakin', 'description' => 'added'), $description); + $this->assertEquals(array( + 'type' => 'array', + 'items' => array('type' => 'string', 'default' => 'Mesa address tha senate!'), + 'description' => 'added' + ), $p->toArray()); + } + + public function testResolvesExtendsRecursively() + { + $jarJar = array('type' => 'string', 'default' => 'Mesa address tha senate!', 'description' => 'a'); + $anakin = array('type' => 'array', 'items' => array('extends' => 'JarJar', 'description' => 'b')); + $description = new ServiceDescription(array( + 'models' => array('JarJar' => $jarJar, 'Anakin' => $anakin) + )); + // Description attribute will be updated, and format added + $p = new Parameter(array('extends' => 'Anakin', 'format' => 'date'), $description); + $this->assertEquals(array( + 'type' => 'array', + 'format' => 'date', + 'items' => array( + 'type' => 'string', + 'default' => 'Mesa address tha senate!', + 'description' => 'b' + ) + ), $p->toArray()); + } + + public function testHasKeyMethod() + { + $p = new Parameter(array('name' => 'foo', 'sentAs' => 'bar')); + $this->assertEquals('bar', $p->getWireName()); + $p->setSentAs(null); + $this->assertEquals('foo', $p->getWireName()); + } + + public function testIncludesNameInToArrayWhenItemsAttributeHasName() + { + $p = new Parameter(array( + 'type' => 'array', + 'name' => 'Abc', + 'items' => array( + 'name' => 'Foo', + 'type' => 'object' + ) + )); + $result = $p->toArray(); + $this->assertEquals(array( + 'type' => 'array', + 'items' => array( + 'name' => 'Foo', + 'type' => 'object', + 'additionalProperties' => true + ) + ), $result); + } + + public function dateTimeProvider() + { + $d = 'October 13, 2012 16:15:46 UTC'; + + return array( + array($d, 'date-time', '2012-10-13T16:15:46Z'), + array($d, 'date', '2012-10-13'), + array($d, 'timestamp', strtotime($d)), + array(new \DateTime($d), 'timestamp', strtotime($d)) + ); + } + + /** + * @dataProvider dateTimeProvider + */ + public function testAppliesFormat($d, $format, $result) + { + $p = new Parameter(); + $p->setFormat($format); + $this->assertEquals($format, $p->getFormat()); + $this->assertEquals($result, $p->filter($d)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php new file mode 100644 index 0000000..eb3619b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaFormatterTest.php @@ -0,0 +1,61 @@ +assertEquals($result, SchemaFormatter::format($format, $value)); + } + + /** + * @expectedException \Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesDateTimeInput() + { + SchemaFormatter::format('date-time', false); + } + + public function testEnsuresTimestampsAreIntegers() + { + $t = time(); + $result = SchemaFormatter::format('timestamp', $t); + $this->assertSame($t, $result); + $this->assertInternalType('int', $result); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php new file mode 100644 index 0000000..4d6cc87 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/SchemaValidatorTest.php @@ -0,0 +1,326 @@ +validator = new SchemaValidator(); + } + + public function testValidatesArrayListsAreNumericallyIndexed() + { + $value = array(array(1)); + $this->assertFalse($this->validator->validate($this->getComplexParam(), $value)); + $this->assertEquals( + array('[Foo][0] must be an array of properties. Got a numerically indexed array.'), + $this->validator->getErrors() + ); + } + + public function testValidatesArrayListsContainProperItems() + { + $value = array(true); + $this->assertFalse($this->validator->validate($this->getComplexParam(), $value)); + $this->assertEquals( + array('[Foo][0] must be of type object'), + $this->validator->getErrors() + ); + } + + public function testAddsDefaultValuesInLists() + { + $value = array(array()); + $this->assertTrue($this->validator->validate($this->getComplexParam(), $value)); + $this->assertEquals(array(array('Bar' => true)), $value); + } + + public function testMergesDefaultValuesInLists() + { + $value = array( + array('Baz' => 'hello!'), + array('Bar' => false) + ); + $this->assertTrue($this->validator->validate($this->getComplexParam(), $value)); + $this->assertEquals(array( + array( + 'Baz' => 'hello!', + 'Bar' => true + ), + array('Bar' => false) + ), $value); + } + + public function testCorrectlyConvertsParametersToArrayWhenArraysArePresent() + { + $param = $this->getComplexParam(); + $result = $param->toArray(); + $this->assertInternalType('array', $result['items']); + $this->assertEquals('array', $result['type']); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $param->getItems()); + } + + public function testAllowsInstanceOf() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'instanceOf' => get_class($this) + )); + $this->assertTrue($this->validator->validate($p, $this)); + $this->assertFalse($this->validator->validate($p, $p)); + $this->assertEquals(array('[foo] must be an instance of ' . __CLASS__), $this->validator->getErrors()); + } + + public function testEnforcesInstanceOfOnlyWhenObject() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => array('object', 'string'), + 'instanceOf' => get_class($this) + )); + $this->assertTrue($this->validator->validate($p, $this)); + $s = 'test'; + $this->assertTrue($this->validator->validate($p, $s)); + } + + public function testConvertsObjectsToArraysWhenToArrayInterface() + { + $o = $this->getMockBuilder('Guzzle\Common\ToArrayInterface') + ->setMethods(array('toArray')) + ->getMockForAbstractClass(); + $o->expects($this->once()) + ->method('toArray') + ->will($this->returnValue(array( + 'foo' => 'bar' + ))); + $p = new Parameter(array( + 'name' => 'test', + 'type' => 'object', + 'properties' => array( + 'foo' => array('required' => 'true') + ) + )); + $this->assertTrue($this->validator->validate($p, $o)); + } + + public function testMergesValidationErrorsInPropertiesWithParent() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array( + 'bar' => array('type' => 'string', 'required' => true, 'description' => 'This is what it does'), + 'test' => array('type' => 'string', 'minLength' => 2, 'maxLength' => 5), + 'test2' => array('type' => 'string', 'minLength' => 2, 'maxLength' => 2), + 'test3' => array('type' => 'integer', 'minimum' => 100), + 'test4' => array('type' => 'integer', 'maximum' => 10), + 'test5' => array('type' => 'array', 'maxItems' => 2), + 'test6' => array('type' => 'string', 'enum' => array('a', 'bc')), + 'test7' => array('type' => 'string', 'pattern' => '/[0-9]+/'), + 'test8' => array('type' => 'number'), + 'baz' => array( + 'type' => 'array', + 'minItems' => 2, + 'required' => true, + "items" => array("type" => "string") + ) + ) + )); + + $value = array( + 'test' => 'a', + 'test2' => 'abc', + 'baz' => array(false), + 'test3' => 10, + 'test4' => 100, + 'test5' => array(1, 3, 4), + 'test6' => 'Foo', + 'test7' => 'abc', + 'test8' => 'abc' + ); + + $this->assertFalse($this->validator->validate($p, $value)); + $this->assertEquals(array ( + '[foo][bar] is a required string: This is what it does', + '[foo][baz] must contain 2 or more elements', + '[foo][baz][0] must be of type string', + '[foo][test2] length must be less than or equal to 2', + '[foo][test3] must be greater than or equal to 100', + '[foo][test4] must be less than or equal to 10', + '[foo][test5] must contain 2 or fewer elements', + '[foo][test6] must be one of "a" or "bc"', + '[foo][test7] must match the following regular expression: /[0-9]+/', + '[foo][test8] must be of type number', + '[foo][test] length must be greater than or equal to 2', + ), $this->validator->getErrors()); + } + + public function testHandlesNullValuesInArraysWithDefaults() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'bar' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'foo' => array('default' => 'hi') + ) + ) + ) + )); + $value = array(); + $this->assertTrue($this->validator->validate($p, $value)); + $this->assertEquals(array('bar' => array('foo' => 'hi')), $value); + } + + public function testFailsWhenNullValuesInArraysWithNoDefaults() + { + $p = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'required' => true, + 'properties' => array( + 'bar' => array( + 'type' => 'object', + 'required' => true, + 'properties' => array('foo' => array('type' => 'string')) + ) + ) + )); + $value = array(); + $this->assertFalse($this->validator->validate($p, $value)); + $this->assertEquals(array('[foo][bar] is a required object'), $this->validator->getErrors()); + } + + public function testChecksTypes() + { + $p = new SchemaValidator(); + $r = new \ReflectionMethod($p, 'determineType'); + $r->setAccessible(true); + $this->assertEquals('any', $r->invoke($p, 'any', 'hello')); + $this->assertEquals(false, $r->invoke($p, 'foo', 'foo')); + $this->assertEquals('string', $r->invoke($p, 'string', 'hello')); + $this->assertEquals(false, $r->invoke($p, 'string', false)); + $this->assertEquals('integer', $r->invoke($p, 'integer', 1)); + $this->assertEquals(false, $r->invoke($p, 'integer', 'abc')); + $this->assertEquals('numeric', $r->invoke($p, 'numeric', 1)); + $this->assertEquals('numeric', $r->invoke($p, 'numeric', '1')); + $this->assertEquals('number', $r->invoke($p, 'number', 1)); + $this->assertEquals('number', $r->invoke($p, 'number', '1')); + $this->assertEquals(false, $r->invoke($p, 'numeric', 'a')); + $this->assertEquals('boolean', $r->invoke($p, 'boolean', true)); + $this->assertEquals('boolean', $r->invoke($p, 'boolean', false)); + $this->assertEquals(false, $r->invoke($p, 'boolean', 'false')); + $this->assertEquals('null', $r->invoke($p, 'null', null)); + $this->assertEquals(false, $r->invoke($p, 'null', 'abc')); + $this->assertEquals('array', $r->invoke($p, 'array', array())); + $this->assertEquals(false, $r->invoke($p, 'array', 'foo')); + } + + public function testValidatesFalseAdditionalProperties() + { + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array('bar' => array('type' => 'string')), + 'additionalProperties' => false + )); + $value = array('test' => '123'); + $this->assertFalse($this->validator->validate($param, $value)); + $this->assertEquals(array('[foo][test] is not an allowed property'), $this->validator->getErrors()); + $value = array('bar' => '123'); + $this->assertTrue($this->validator->validate($param, $value)); + } + + public function testAllowsUndefinedAdditionalProperties() + { + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array('bar' => array('type' => 'string')) + )); + $value = array('test' => '123'); + $this->assertTrue($this->validator->validate($param, $value)); + } + + public function testValidatesAdditionalProperties() + { + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'properties' => array('bar' => array('type' => 'string')), + 'additionalProperties' => array('type' => 'integer') + )); + $value = array('test' => 'foo'); + $this->assertFalse($this->validator->validate($param, $value)); + $this->assertEquals(array('[foo][test] must be of type integer'), $this->validator->getErrors()); + } + + public function testValidatesAdditionalPropertiesThatArrayArrays() + { + $param = new Parameter(array( + 'name' => 'foo', + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'array', + 'items' => array('type' => 'string') + ) + )); + $value = array('test' => array(true)); + $this->assertFalse($this->validator->validate($param, $value)); + $this->assertEquals(array('[foo][test][0] must be of type string'), $this->validator->getErrors()); + } + + public function testIntegersCastToStringWhenTypeMismatch() + { + $param = new Parameter(array('name' => 'test', 'type' => 'string')); + $value = 12; + $this->assertTrue($this->validator->validate($param, $value)); + $this->assertEquals('12', $value); + } + + public function testRequiredMessageIncludesType() + { + $param = new Parameter(array('name' => 'test', 'type' => array('string', 'boolean'), 'required' => true)); + $value = null; + $this->assertFalse($this->validator->validate($param, $value)); + $this->assertEquals(array('[test] is a required string or boolean'), $this->validator->getErrors()); + } + + protected function getComplexParam() + { + return new Parameter(array( + 'name' => 'Foo', + 'type' => 'array', + 'required' => true, + 'min' => 1, + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'Baz' => array( + 'type' => 'string', + ), + 'Bar' => array( + 'required' => true, + 'type' => 'boolean', + 'default' => true + ) + ) + ) + )); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php new file mode 100644 index 0000000..bbfd1d6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionLoaderTest.php @@ -0,0 +1,177 @@ + true, + 'baz' => array('bar'), + 'apiVersion' => '123', + 'operations' => array() + )); + + $this->assertEquals(true, $d->getData('foo')); + $this->assertEquals(array('bar'), $d->getData('baz')); + $this->assertEquals('123', $d->getApiVersion()); + } + + public function testAllowsDeepNestedInheritance() + { + $d = ServiceDescription::factory(array( + 'operations' => array( + 'abstract' => array( + 'httpMethod' => 'HEAD', + 'parameters' => array( + 'test' => array('type' => 'string', 'required' => true) + ) + ), + 'abstract2' => array('uri' => '/test', 'extends' => 'abstract'), + 'concrete' => array('extends' => 'abstract2'), + 'override' => array('extends' => 'abstract', 'httpMethod' => 'PUT'), + 'override2' => array('extends' => 'override', 'httpMethod' => 'POST', 'uri' => '/') + ) + )); + + $c = $d->getOperation('concrete'); + $this->assertEquals('/test', $c->getUri()); + $this->assertEquals('HEAD', $c->getHttpMethod()); + $params = $c->getParams(); + $param = $params['test']; + $this->assertEquals('string', $param->getType()); + $this->assertTrue($param->getRequired()); + + // Ensure that merging HTTP method does not make an array + $this->assertEquals('PUT', $d->getOperation('override')->getHttpMethod()); + $this->assertEquals('POST', $d->getOperation('override2')->getHttpMethod()); + $this->assertEquals('/', $d->getOperation('override2')->getUri()); + } + + /** + * @expectedException RuntimeException + */ + public function testThrowsExceptionWhenExtendingMissingCommand() + { + ServiceDescription::factory(array( + 'operations' => array( + 'concrete' => array( + 'extends' => 'missing' + ) + ) + )); + } + + public function testAllowsMultipleInheritance() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'a' => array( + 'httpMethod' => 'GET', + 'parameters' => array( + 'a1' => array( + 'default' => 'foo', + 'required' => true, + 'prepend' => 'hi' + ) + ) + ), + 'b' => array( + 'extends' => 'a', + 'parameters' => array( + 'b2' => array() + ) + ), + 'c' => array( + 'parameters' => array( + 'a1' => array( + 'default' => 'bar', + 'required' => true, + 'description' => 'test' + ), + 'c3' => array() + ) + ), + 'd' => array( + 'httpMethod' => 'DELETE', + 'extends' => array('b', 'c'), + 'parameters' => array( + 'test' => array() + ) + ) + ) + )); + + $command = $description->getOperation('d'); + $this->assertEquals('DELETE', $command->getHttpMethod()); + $this->assertContains('a1', $command->getParamNames()); + $this->assertContains('b2', $command->getParamNames()); + $this->assertContains('c3', $command->getParamNames()); + $this->assertContains('test', $command->getParamNames()); + + $this->assertTrue($command->getParam('a1')->getRequired()); + $this->assertEquals('bar', $command->getParam('a1')->getDefault()); + $this->assertEquals('test', $command->getParam('a1')->getDescription()); + } + + public function testAddsOtherFields() + { + $description = ServiceDescription::factory(array( + 'operations' => array(), + 'description' => 'Foo', + 'apiVersion' => 'bar' + )); + $this->assertEquals('Foo', $description->getDescription()); + $this->assertEquals('bar', $description->getApiVersion()); + } + + public function testCanLoadNestedExtends() + { + $description = ServiceDescription::factory(array( + 'operations' => array( + 'root' => array( + 'class' => 'foo' + ), + 'foo' => array( + 'extends' => 'root', + 'parameters' => array( + 'baz' => array('type' => 'string') + ) + ), + 'foo_2' => array( + 'extends' => 'foo', + 'parameters' => array( + 'bar' => array('type' => 'string') + ) + ), + 'foo_3' => array( + 'class' => 'bar', + 'parameters' => array( + 'bar2' => array('type' => 'string') + ) + ), + 'foo_4' => array( + 'extends' => array('foo_2', 'foo_3'), + 'parameters' => array( + 'bar3' => array('type' => 'string') + ) + ) + ) + )); + + $this->assertTrue($description->hasOperation('foo_4')); + $foo4 = $description->getOperation('foo_4'); + $this->assertTrue($foo4->hasParam('baz')); + $this->assertTrue($foo4->hasParam('bar')); + $this->assertTrue($foo4->hasParam('bar2')); + $this->assertTrue($foo4->hasParam('bar3')); + $this->assertEquals('bar', $foo4->getClass()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php new file mode 100644 index 0000000..ff25452 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Description/ServiceDescriptionTest.php @@ -0,0 +1,240 @@ +serviceData = array( + 'test_command' => new Operation(array( + 'name' => 'test_command', + 'description' => 'documentationForCommand', + 'httpMethod' => 'DELETE', + 'class' => 'Guzzle\\Tests\\Service\\Mock\\Command\\MockCommand', + 'parameters' => array( + 'bucket' => array('required' => true), + 'key' => array('required' => true) + ) + )) + ); + } + + /** + * @covers Guzzle\Service\Description\ServiceDescription::factory + * @covers Guzzle\Service\Description\ServiceDescriptionLoader::build + */ + public function testFactoryDelegatesToConcreteFactories() + { + $jsonFile = __DIR__ . '/../../TestData/test_service.json'; + $this->assertInstanceOf('Guzzle\Service\Description\ServiceDescription', ServiceDescription::factory($jsonFile)); + } + + public function testConstructor() + { + $service = new ServiceDescription(array('operations' => $this->serviceData)); + $this->assertEquals(1, count($service->getOperations())); + $this->assertFalse($service->hasOperation('foobar')); + $this->assertTrue($service->hasOperation('test_command')); + } + + public function testIsSerializable() + { + $service = new ServiceDescription(array('operations' => $this->serviceData)); + $data = serialize($service); + $d2 = unserialize($data); + $this->assertEquals(serialize($service), serialize($d2)); + } + + public function testSerializesParameters() + { + $service = new ServiceDescription(array( + 'operations' => array( + 'foo' => new Operation(array('parameters' => array('foo' => array('type' => 'string')))) + ) + )); + $serialized = serialize($service); + $this->assertContains('"parameters":{"foo":', $serialized); + $service = unserialize($serialized); + $this->assertTrue($service->getOperation('foo')->hasParam('foo')); + } + + public function testAllowsForJsonBasedArrayParamsFunctionalTest() + { + $description = new ServiceDescription(array( + 'operations' => array( + 'test' => new Operation(array( + 'httpMethod' => 'PUT', + 'parameters' => array( + 'data' => array( + 'required' => true, + 'filters' => 'json_encode', + 'location' => 'body' + ) + ) + )) + ) + )); + $client = new Client(); + $client->setDescription($description); + $command = $client->getCommand('test', array( + 'data' => array( + 'foo' => 'bar' + ) + )); + + $request = $command->prepare(); + $this->assertEquals('{"foo":"bar"}', (string) $request->getBody()); + } + + public function testContainsModels() + { + $d = new ServiceDescription(array( + 'operations' => array('foo' => array()), + 'models' => array( + 'Tag' => array('type' => 'object'), + 'Person' => array('type' => 'object') + ) + )); + $this->assertTrue($d->hasModel('Tag')); + $this->assertTrue($d->hasModel('Person')); + $this->assertFalse($d->hasModel('Foo')); + $this->assertInstanceOf('Guzzle\Service\Description\Parameter', $d->getModel('Tag')); + $this->assertNull($d->getModel('Foo')); + $this->assertContains('"models":{', serialize($d)); + $this->assertEquals(array('Tag', 'Person'), array_keys($d->getModels())); + } + + public function testCanAddModels() + { + $d = new ServiceDescription(array()); + $this->assertFalse($d->hasModel('Foo')); + $d->addModel(new Parameter(array('name' => 'Foo'))); + $this->assertTrue($d->hasModel('Foo')); + } + + public function testHasAttributes() + { + $d = new ServiceDescription(array( + 'operations' => array(), + 'name' => 'Name', + 'description' => 'Description', + 'apiVersion' => '1.24' + )); + + $this->assertEquals('Name', $d->getName()); + $this->assertEquals('Description', $d->getDescription()); + $this->assertEquals('1.24', $d->getApiVersion()); + + $s = serialize($d); + $this->assertContains('"name":"Name"', $s); + $this->assertContains('"description":"Description"', $s); + $this->assertContains('"apiVersion":"1.24"', $s); + + $d = unserialize($s); + $this->assertEquals('Name', $d->getName()); + $this->assertEquals('Description', $d->getDescription()); + $this->assertEquals('1.24', $d->getApiVersion()); + } + + public function testPersistsCustomAttributes() + { + $data = array( + 'operations' => array('foo' => array('class' => 'foo', 'parameters' => array())), + 'name' => 'Name', + 'description' => 'Test', + 'apiVersion' => '1.24', + 'auth' => 'foo', + 'keyParam' => 'bar' + ); + $d = new ServiceDescription($data); + $d->setData('hello', 'baz'); + $this->assertEquals('foo', $d->getData('auth')); + $this->assertEquals('baz', $d->getData('hello')); + $this->assertEquals('bar', $d->getData('keyParam')); + // responseClass and responseType are added by default + $data['operations']['foo']['responseClass'] = 'array'; + $data['operations']['foo']['responseType'] = 'primitive'; + $this->assertEquals($data + array('hello' => 'baz'), json_decode($d->serialize(), true)); + } + + public function testHasToArray() + { + $data = array( + 'operations' => array(), + 'name' => 'Name', + 'description' => 'Test' + ); + $d = new ServiceDescription($data); + $arr = $d->toArray(); + $this->assertEquals('Name', $arr['name']); + $this->assertEquals('Test', $arr['description']); + } + + public function testReturnsNullWhenRetrievingMissingOperation() + { + $s = new ServiceDescription(array()); + $this->assertNull($s->getOperation('foo')); + } + + public function testCanAddOperations() + { + $s = new ServiceDescription(array()); + $this->assertFalse($s->hasOperation('Foo')); + $s->addOperation(new Operation(array('name' => 'Foo'))); + $this->assertTrue($s->hasOperation('Foo')); + } + + /** + * @expectedException Guzzle\Common\Exception\InvalidArgumentException + */ + public function testValidatesOperationTypes() + { + $s = new ServiceDescription(array( + 'operations' => array('foo' => new \stdClass()) + )); + } + + public function testHasBaseUrl() + { + $description = new ServiceDescription(array('baseUrl' => 'http://foo.com')); + $this->assertEquals('http://foo.com', $description->getBaseUrl()); + $description->setBaseUrl('http://foobar.com'); + $this->assertEquals('http://foobar.com', $description->getBaseUrl()); + } + + public function testCanUseBasePath() + { + $description = new ServiceDescription(array('basePath' => 'http://foo.com')); + $this->assertEquals('http://foo.com', $description->getBaseUrl()); + } + + public function testModelsHaveNames() + { + $desc = array( + 'models' => array( + 'date' => array('type' => 'string'), + 'user'=> array( + 'type' => 'object', + 'properties' => array( + 'dob' => array('$ref' => 'date') + ) + ) + ) + ); + + $s = ServiceDescription::factory($desc); + $this->assertEquals('date', $s->getModel('date')->getName()); + $this->assertEquals('dob', $s->getModel('user')->getProperty('dob')->getName()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php new file mode 100644 index 0000000..be0d4ac --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/CommandTransferExceptionTest.php @@ -0,0 +1,66 @@ +addSuccessfulCommand($c1)->addFailedCommand($c2); + $this->assertSame(array($c1), $e->getSuccessfulCommands()); + $this->assertSame(array($c2), $e->getFailedCommands()); + $this->assertSame(array($c1, $c2), $e->getAllCommands()); + } + + public function testConvertsMultiExceptionIntoCommandTransfer() + { + $r1 = new Request('GET', 'http://foo.com'); + $r2 = new Request('GET', 'http://foobaz.com'); + $e = new MultiTransferException('Test', 123); + $e->addSuccessfulRequest($r1)->addFailedRequest($r2); + $ce = CommandTransferException::fromMultiTransferException($e); + + $this->assertInstanceOf('Guzzle\Service\Exception\CommandTransferException', $ce); + $this->assertEquals('Test', $ce->getMessage()); + $this->assertEquals(123, $ce->getCode()); + $this->assertSame(array($r1), $ce->getSuccessfulRequests()); + $this->assertSame(array($r2), $ce->getFailedRequests()); + } + + public function testCanRetrieveExceptionForCommand() + { + $r1 = new Request('GET', 'http://foo.com'); + $e1 = new \Exception('foo'); + $c1 = $this->getMockBuilder('Guzzle\Tests\Service\Mock\Command\MockCommand') + ->setMethods(array('getRequest')) + ->getMock(); + $c1->expects($this->once())->method('getRequest')->will($this->returnValue($r1)); + + $e = new MultiTransferException('Test', 123); + $e->addFailedRequestWithException($r1, $e1); + $ce = CommandTransferException::fromMultiTransferException($e); + $ce->addFailedCommand($c1); + + $this->assertSame($e1, $ce->getExceptionForFailedCommand($c1)); + } + + public function testAddsNonRequestExceptions() + { + $e = new MultiTransferException(); + $e->add(new \Exception('bar')); + $e->addFailedRequestWithException(new Request('GET', 'http://www.foo.com'), new \Exception('foo')); + $ce = CommandTransferException::fromMultiTransferException($e); + $this->assertEquals(2, count($ce)); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php new file mode 100644 index 0000000..6455295 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/InconsistentClientTransferExceptionTest.php @@ -0,0 +1,15 @@ +assertEquals($items, $e->getCommands()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php new file mode 100644 index 0000000..ef789d8 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Exception/ValidationExceptionTest.php @@ -0,0 +1,17 @@ +setErrors($errors); + $this->assertEquals($errors, $e->getErrors()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php new file mode 100644 index 0000000..4ab423e --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/IterableCommand.php @@ -0,0 +1,31 @@ + 'iterable_command', + 'parameters' => array( + 'page_size' => array('type' => 'integer'), + 'next_token' => array('type' => 'string') + ) + )); + } + + protected function build() + { + $this->request = $this->client->createRequest('GET'); + + // Add the next token and page size query string values + $this->request->getQuery()->set('next_token', $this->get('next_token')); + + if ($this->get('page_size')) { + $this->request->getQuery()->set('page_size', $this->get('page_size')); + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php new file mode 100644 index 0000000..831a7e7 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/MockCommand.php @@ -0,0 +1,32 @@ + get_called_class() == __CLASS__ ? 'mock_command' : 'sub.sub', + 'httpMethod' => 'POST', + 'parameters' => array( + 'test' => array( + 'default' => 123, + 'required' => true, + 'doc' => 'Test argument' + ), + '_internal' => array( + 'default' => 'abc' + ), + 'foo' => array('filters' => array('strtoupper')) + ) + )); + } + + protected function build() + { + $this->request = $this->client->createRequest(); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php new file mode 100644 index 0000000..72ae1f6 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/OtherCommand.php @@ -0,0 +1,30 @@ + 'other_command', + 'parameters' => array( + 'test' => array( + 'default' => '123', + 'required' => true, + 'doc' => 'Test argument' + ), + 'other' => array(), + 'arg' => array('type' => 'string'), + 'static' => array('static' => true, 'default' => 'this is static') + ) + )); + } + + protected function build() + { + $this->request = $this->client->getRequest('HEAD'); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php new file mode 100644 index 0000000..d348480 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Command/Sub/Sub.php @@ -0,0 +1,7 @@ + '{scheme}://127.0.0.1:8124/{api_version}/{subdomain}', + 'scheme' => 'http', + 'api_version' => 'v1' + ), array('username', 'password', 'subdomain')); + + return new self($config->get('base_url'), $config); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php new file mode 100644 index 0000000..8faf412 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Mock/Model/MockCommandIterator.php @@ -0,0 +1,42 @@ +nextToken) { + $this->command->set('next_token', $this->nextToken); + } + + $this->command->set('page_size', (int) $this->calculatePageSize()); + $this->command->execute(); + + $data = json_decode($this->command->getResponse()->getBody(true), true); + + $this->nextToken = $data['next_token']; + + return $data['resources']; + } + + public function next() + { + $this->calledNext++; + parent::next(); + } + + public function getResources() + { + return $this->resources; + } + + public function getIteratedCount() + { + return $this->iteratedCount; + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php new file mode 100644 index 0000000..41c2073 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/CompositeResourceIteratorFactoryTest.php @@ -0,0 +1,37 @@ +assertFalse($factory->canBuild($cmd)); + $factory->build($cmd); + } + + public function testBuildsResourceIterators() + { + $f1 = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model'); + $factory = new CompositeResourceIteratorFactory(array()); + $factory->addFactory($f1); + $command = new MockCommand(); + $iterator = $factory->build($command, array('client.namespace' => 'Guzzle\Tests\Service\Mock')); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php new file mode 100644 index 0000000..d166e92 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/MapResourceIteratorFactoryTest.php @@ -0,0 +1,40 @@ +build(new MockCommand()); + } + + public function testBuildsResourceIterators() + { + $factory = new MapResourceIteratorFactory(array( + 'mock_command' => 'Guzzle\Tests\Service\Mock\Model\MockCommandIterator' + )); + $iterator = $factory->build(new MockCommand()); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } + + public function testUsesWildcardMappings() + { + $factory = new MapResourceIteratorFactory(array( + '*' => 'Guzzle\Tests\Service\Mock\Model\MockCommandIterator' + )); + $iterator = $factory->build(new MockCommand()); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php new file mode 100644 index 0000000..7214133 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ModelTest.php @@ -0,0 +1,65 @@ + 'object')); + $model = new Model(array('foo' => 'bar'), $param); + $this->assertSame($param, $model->getStructure()); + $this->assertEquals('bar', $model->get('foo')); + $this->assertEquals('bar', $model['foo']); + } + + public function testCanBeUsedWithoutStructure() + { + $model = new Model(array( + 'Foo' => 'baz', + 'Bar' => array( + 'Boo' => 'Bam' + ) + )); + $transform = function ($key, $value) { + return ($value && is_array($value)) ? new Collection($value) : $value; + }; + $model = $model->map($transform); + $this->assertInstanceOf('Guzzle\Common\Collection', $model->getPath('Bar')); + } + + public function testAllowsFiltering() + { + $model = new Model(array( + 'Foo' => 'baz', + 'Bar' => 'a' + )); + $model = $model->filter(function ($i, $v) { + return $v[0] == 'a'; + }); + $this->assertEquals(array('Bar' => 'a'), $model->toArray()); + } + + public function testDoesNotIncludeEmptyStructureInString() + { + $model = new Model(array('Foo' => 'baz')); + $str = (string) $model; + $this->assertContains('Debug output of model', $str); + $this->assertNotContains('Model structure', $str); + } + + public function testDoesIncludeModelStructureInString() + { + $model = new Model(array('Foo' => 'baz'), new Parameter(array('name' => 'Foo'))); + $str = (string) $model; + $this->assertContains('Debug output of Foo model', $str); + $this->assertContains('Model structure', $str); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php new file mode 100644 index 0000000..7b061b5 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorClassFactoryTest.php @@ -0,0 +1,41 @@ +registerNamespace('Baz'); + $command = new MockCommand(); + $factory->build($command); + } + + public function testBuildsResourceIterators() + { + $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model'); + $command = new MockCommand(); + $iterator = $factory->build($command, array('client.namespace' => 'Guzzle\Tests\Service\Mock')); + $this->assertInstanceOf('Guzzle\Tests\Service\Mock\Model\MockCommandIterator', $iterator); + } + + public function testChecksIfCanBuild() + { + $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service'); + $this->assertFalse($factory->canBuild(new MockCommand())); + $factory = new ResourceIteratorClassFactory('Guzzle\Tests\Service\Mock\Model'); + $this->assertTrue($factory->canBuild(new MockCommand())); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php new file mode 100644 index 0000000..573fb6d --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Service/Resource/ResourceIteratorTest.php @@ -0,0 +1,184 @@ +assertInternalType('array', ResourceIterator::getAllEvents()); + } + + public function testConstructorConfiguresDefaults() + { + $ri = $this->getMockForAbstractClass('Guzzle\\Service\\Resource\\ResourceIterator', array( + $this->getServiceBuilder()->get('mock')->getCommand('iterable_command'), + array( + 'limit' => 10, + 'page_size' => 3 + ) + ), 'MockIterator'); + + $this->assertEquals(false, $ri->getNextToken()); + $this->assertEquals(false, $ri->current()); + } + + public function testSendsRequestsForNextSetOfResources() + { + // Queue up an array of responses for iterating + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 41\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"j\"] }" + )); + + // Create a new resource iterator using the IterableCommand mock + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'), array( + 'page_size' => 3 + )); + + // Ensure that no requests have been sent yet + $this->assertEquals(0, count($this->getServer()->getReceivedRequests(false))); + + //$this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $ri->toArray()); + $ri->toArray(); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(3, count($requests)); + + $this->assertEquals(3, $requests[0]->getQuery()->get('page_size')); + $this->assertEquals(3, $requests[1]->getQuery()->get('page_size')); + $this->assertEquals(3, $requests[2]->getQuery()->get('page_size')); + + // Reset and resend + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 41\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"j\"] }", + )); + + $d = array(); + foreach ($ri as $data) { + $d[] = $data; + } + $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $d); + } + + public function testCalculatesPageSize() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"g\", \"h\", \"i\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"j\", \"resources\": [\"j\", \"k\"] }" + )); + + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command'), array( + 'page_size' => 3, + 'limit' => 7 + )); + + $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i', 'j'), $ri->toArray()); + $requests = $this->getServer()->getReceivedRequests(true); + $this->assertEquals(3, count($requests)); + $this->assertEquals(3, $requests[0]->getQuery()->get('page_size')); + $this->assertEquals(3, $requests[1]->getQuery()->get('page_size')); + $this->assertEquals(1, $requests[2]->getQuery()->get('page_size')); + } + + public function testUseAsArray() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\nContent-Length: 52\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"g\", \"h\", \"i\"] }" + )); + + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + + // Ensure that the key is never < 0 + $this->assertEquals(0, $ri->key()); + $this->assertEquals(0, count($ri)); + + // Ensure that the iterator can be used as KVP array + $data = array(); + foreach ($ri as $key => $value) { + $data[$key] = $value; + } + + // Ensure that the iterate is countable + $this->assertEquals(6, count($ri)); + $this->assertEquals(array('d', 'e', 'f', 'g', 'h', 'i'), $data); + } + + public function testBailsWhenSendReturnsNoResults() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"g\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [] }" + )); + + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + + // Ensure that the iterator can be used as KVP array + $data = $ri->toArray(); + + // Ensure that the iterate is countable + $this->assertEquals(3, count($ri)); + $this->assertEquals(array('d', 'e', 'f'), $data); + + $this->assertEquals(2, $ri->getRequestCount()); + } + + public function testHoldsDataOptions() + { + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + $this->assertNull($ri->get('foo')); + $this->assertSame($ri, $ri->set('foo', 'bar')); + $this->assertEquals('bar', $ri->get('foo')); + } + + public function testSettingLimitOrPageSizeClearsData() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }", + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }" + )); + + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + $ri->toArray(); + $this->assertNotEmpty($this->readAttribute($ri, 'resources')); + + $ri->setLimit(10); + $this->assertEmpty($this->readAttribute($ri, 'resources')); + + $ri->toArray(); + $this->assertNotEmpty($this->readAttribute($ri, 'resources')); + $ri->setPageSize(10); + $this->assertEmpty($this->readAttribute($ri, 'resources')); + } + + public function testWorksWithCustomAppendIterator() + { + $this->getServer()->flush(); + $this->getServer()->enqueue(array( + "HTTP/1.1 200 OK\r\n\r\n{ \"next_token\": \"\", \"resources\": [\"d\", \"e\", \"f\"] }" + )); + $ri = new MockCommandIterator($this->getServiceBuilder()->get('mock')->getCommand('iterable_command')); + $a = new \Guzzle\Iterator\AppendIterator(); + $a->append($ri); + $results = iterator_to_array($a, false); + $this->assertEquals(4, $ri->calledNext); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php new file mode 100644 index 0000000..083aaa0 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/PhpStreamRequestFactoryTest.php @@ -0,0 +1,172 @@ +client = new Client($this->getServer()->getUrl()); + $this->factory = new PhpStreamRequestFactory(); + } + + public function testOpensValidStreamByCreatingContext() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + $request = $this->client->get('/'); + $stream = $this->factory->fromRequest($request); + $this->assertEquals('hi', (string) $stream); + $headers = $this->factory->getLastResponseHeaders(); + $this->assertContains('HTTP/1.1 200 OK', $headers); + $this->assertContains('Content-Length: 2', $headers); + $this->assertSame($headers, $stream->getCustomData('response_headers')); + $this->assertEquals(2, $stream->getSize()); + } + + public function testOpensValidStreamByPassingContextAndMerging() + { + $request = $this->client->get('/'); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createContext', 'createStream')) + ->getMock(); + $this->factory->expects($this->never()) + ->method('createContext'); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + + $context = array('http' => array('method' => 'HEAD', 'ignore_errors' => false)); + $this->factory->fromRequest($request, stream_context_create($context)); + $options = stream_context_get_options($this->readAttribute($this->factory, 'context')); + $this->assertEquals('HEAD', $options['http']['method']); + $this->assertFalse($options['http']['ignore_errors']); + $this->assertEquals('1.1', $options['http']['protocol_version']); + } + + public function testAppliesProxySettings() + { + $request = $this->client->get('/'); + $request->getCurlOptions()->set(CURLOPT_PROXY, 'tcp://foo.com'); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createStream')) + ->getMock(); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + $this->factory->fromRequest($request); + $options = stream_context_get_options($this->readAttribute($this->factory, 'context')); + $this->assertEquals('tcp://foo.com', $options['http']['proxy']); + } + + public function testAddsPostFields() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + $request = $this->client->post('/', array('Foo' => 'Bar'), array('foo' => 'baz bar')); + $stream = $this->factory->fromRequest($request); + $this->assertEquals('hi', (string) $stream); + + $headers = $this->factory->getLastResponseHeaders(); + $this->assertContains('HTTP/1.1 200 OK', $headers); + $this->assertContains('Content-Length: 2', $headers); + $this->assertSame($headers, $stream->getCustomData('response_headers')); + + $received = $this->getServer()->getReceivedRequests(); + $this->assertEquals(1, count($received)); + $this->assertContains('POST / HTTP/1.1', $received[0]); + $this->assertContains('host: ', $received[0]); + $this->assertContains('user-agent: Guzzle/', $received[0]); + $this->assertContains('foo: Bar', $received[0]); + $this->assertContains('content-length: 13', $received[0]); + $this->assertContains('foo=baz%20bar', $received[0]); + } + + public function testAddsBody() + { + $this->getServer()->flush(); + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + $request = $this->client->put('/', array('Foo' => 'Bar'), 'Testing...123'); + $stream = $this->factory->fromRequest($request); + $this->assertEquals('hi', (string) $stream); + + $headers = $this->factory->getLastResponseHeaders(); + $this->assertContains('HTTP/1.1 200 OK', $headers); + $this->assertContains('Content-Length: 2', $headers); + $this->assertSame($headers, $stream->getCustomData('response_headers')); + + $received = $this->getServer()->getReceivedRequests(); + $this->assertEquals(1, count($received)); + $this->assertContains('PUT / HTTP/1.1', $received[0]); + $this->assertContains('host: ', $received[0]); + $this->assertContains('user-agent: Guzzle/', $received[0]); + $this->assertContains('foo: Bar', $received[0]); + $this->assertContains('content-length: 13', $received[0]); + $this->assertContains('Testing...123', $received[0]); + } + + public function testCanDisableSslValidation() + { + $request = $this->client->get('/'); + $request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createStream')) + ->getMock(); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + $this->factory->fromRequest($request); + $options = stream_context_get_options($this->readAttribute($this->factory, 'context')); + $this->assertFalse($options['ssl']['verify_peer']); + } + + public function testUsesSslValidationByDefault() + { + $request = $this->client->get('/'); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createStream')) + ->getMock(); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + $this->factory->fromRequest($request); + $options = stream_context_get_options($this->readAttribute($this->factory, 'context')); + $this->assertTrue($options['ssl']['verify_peer']); + $this->assertSame($request->getCurlOptions()->get(CURLOPT_CAINFO), $options['ssl']['cafile']); + } + + public function testBasicAuthAddsUserAndPassToUrl() + { + $request = $this->client->get('/'); + $request->setAuth('Foo', 'Bar'); + $this->factory = $this->getMockBuilder('Guzzle\Stream\PhpStreamRequestFactory') + ->setMethods(array('createStream')) + ->getMock(); + $this->factory->expects($this->once()) + ->method('createStream') + ->will($this->returnValue(new Stream(fopen('php://temp', 'r')))); + $this->factory->fromRequest($request); + $this->assertContains('Foo:Bar@', (string) $this->readAttribute($this->factory, 'url')); + } + + public function testCanCreateCustomStreamClass() + { + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nhi"); + $request = $this->client->get('/'); + $stream = $this->factory->fromRequest($request, array(), array('stream_class' => 'Guzzle\Http\EntityBody')); + $this->assertInstanceOf('Guzzle\Http\EntityBody', $stream); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php new file mode 100644 index 0000000..4973f25 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/Stream/StreamTest.php @@ -0,0 +1,189 @@ +assertEquals($handle, $stream->getStream()); + $this->assertTrue($stream->isReadable()); + $this->assertTrue($stream->isWritable()); + $this->assertTrue($stream->isLocal()); + $this->assertTrue($stream->isSeekable()); + $this->assertEquals('PHP', $stream->getWrapper()); + $this->assertEquals('TEMP', $stream->getStreamType()); + $this->assertEquals(4, $stream->getSize()); + $this->assertEquals('php://temp', $stream->getUri()); + $this->assertEquals(array(), $stream->getWrapperData()); + $this->assertFalse($stream->isConsumed()); + unset($stream); + } + + public function testCanModifyStream() + { + $handle1 = fopen('php://temp', 'r+'); + $handle2 = fopen('php://temp', 'r+'); + $stream = new Stream($handle1); + $this->assertSame($handle1, $stream->getStream()); + $stream->setStream($handle2, 10); + $this->assertEquals(10, $stream->getSize()); + $this->assertSame($handle2, $stream->getStream()); + } + + public function testStreamClosesHandleOnDestruct() + { + $handle = fopen('php://temp', 'r'); + $stream = new Stream($handle); + unset($stream); + $this->assertFalse(is_resource($handle)); + } + + public function testConvertsToString() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertEquals('data', (string) $stream); + unset($stream); + + $handle = fopen(__DIR__ . '/../TestData/FileBody.txt', 'r'); + $stream = new Stream($handle); + $this->assertEquals('', (string) $stream); + unset($stream); + } + + public function testConvertsToStringAndRestoresCursorPos() + { + $handle = fopen('php://temp', 'w+'); + $stream = new Stream($handle); + $stream->write('foobazbar'); + $stream->seek(3); + $this->assertEquals('foobazbar', (string) $stream); + $this->assertEquals(3, $stream->ftell()); + } + + public function testIsConsumed() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertFalse($stream->isConsumed()); + $stream->read(4); + $this->assertTrue($stream->isConsumed()); + } + + public function testAllowsSettingManualSize() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $stream->setSize(10); + $this->assertEquals(10, $stream->getSize()); + unset($stream); + } + + public function testWrapsStream() + { + $handle = fopen('php://temp', 'w+'); + fwrite($handle, 'data'); + $stream = new Stream($handle); + $this->assertTrue($stream->isSeekable()); + $this->assertTrue($stream->isReadable()); + $this->assertTrue($stream->seek(0)); + $this->assertEquals('da', $stream->read(2)); + $this->assertEquals('ta', $stream->read(2)); + $this->assertTrue($stream->seek(0)); + $this->assertEquals('data', $stream->read(4)); + $stream->write('_appended'); + $stream->seek(0); + $this->assertEquals('data_appended', $stream->read(13)); + } + + public function testGetSize() + { + $size = filesize(__DIR__ . '/../../../bootstrap.php'); + $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r'); + $stream = new Stream($handle); + $this->assertEquals($handle, $stream->getStream()); + $this->assertEquals($size, $stream->getSize()); + $this->assertEquals($size, $stream->getSize()); + unset($stream); + + // Make sure that false is returned when the size cannot be determined + $this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); + $handle = fopen('http://127.0.0.1:' . $this->getServer()->getPort(), 'r'); + $stream = new Stream($handle); + $this->assertEquals(false, $stream->getSize()); + unset($stream); + } + + public function testEnsuresSizeIsConsistent() + { + $h = fopen('php://temp', 'r+'); + fwrite($h, 'foo'); + $stream = new Stream($h); + $this->assertEquals(3, $stream->getSize()); + $stream->write('test'); + $this->assertEquals(7, $stream->getSize()); + fclose($h); + } + + public function testAbstractsMetaData() + { + $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r'); + $stream = new Stream($handle); + $this->assertEquals('plainfile', $stream->getMetaData('wrapper_type')); + $this->assertEquals(null, $stream->getMetaData('wrapper_data')); + $this->assertInternalType('array', $stream->getMetaData()); + } + + public function testDoesNotAttemptToWriteToReadonlyStream() + { + $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r'); + $stream = new Stream($handle); + $this->assertEquals(0, $stream->write('foo')); + } + + public function testProvidesStreamPosition() + { + $handle = fopen(__DIR__ . '/../../../bootstrap.php', 'r'); + $stream = new Stream($handle); + $stream->read(2); + $this->assertSame(ftell($handle), $stream->ftell()); + $this->assertEquals(2, $stream->ftell()); + } + + public function testRewindIsSeekZero() + { + $stream = new Stream(fopen('php://temp', 'w+')); + $stream->write('foobazbar'); + $this->assertTrue($stream->rewind()); + $this->assertEquals('foobazbar', $stream->read(9)); + } + + public function testCanDetachStream() + { + $r = fopen('php://temp', 'w+'); + $stream = new Stream($r); + $stream->detachStream(); + $this->assertNull($stream->getStream()); + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/FileBody.txt b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/FileBody.txt new file mode 100644 index 0000000..e69de29 diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json new file mode 100644 index 0000000..c354ed7 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/bar.json @@ -0,0 +1,3 @@ +{ + "includes": ["foo.json"] +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json new file mode 100644 index 0000000..765237b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/baz.json @@ -0,0 +1,3 @@ +{ + "includes": ["foo.json", "bar.json"] +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json new file mode 100644 index 0000000..cee5005 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/foo.json @@ -0,0 +1,8 @@ +{ + "includes": ["recursive.json"], + "operations": { + "abstract": { + "httpMethod": "POST" + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json new file mode 100644 index 0000000..c354ed7 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/description/recursive.json @@ -0,0 +1,3 @@ +{ + "includes": ["foo.json"] +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response new file mode 100644 index 0000000..b6938a2 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/mock_response @@ -0,0 +1,3 @@ +HTTP/1.1 200 OK +Content-Length: 0 + diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json new file mode 100644 index 0000000..7b2a9da --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json1.json @@ -0,0 +1,18 @@ +{ + "includes": [ "json2.json" ], + "services": { + "abstract": { + "access_key": "xyz", + "secret": "abc" + }, + "mock": { + "class": "Guzzle\\Tests\\Service\\Mock\\MockClient", + "extends": "abstract", + "params": { + "username": "foo", + "password": "baz", + "subdomain": "bar" + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json new file mode 100644 index 0000000..08e5566 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/json2.json @@ -0,0 +1,11 @@ +{ + "services": { + "foo": { + "class": "Guzzle\\Tests\\Service\\Mock\\MockClient", + "extends": "abstract", + "params": { + "baz": "bar" + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json new file mode 100644 index 0000000..25452e4 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/services/services.json @@ -0,0 +1,71 @@ +{ + "abstract": { + "access_key": "xyz", + "secret": "abc" + }, + "mock": { + "class": "Guzzle\\Tests\\Service\\Mock\\MockClient", + "extends": "abstract", + "params": { + "username": "foo", + "password": "baz", + "subdomain": "bar" + } + }, + + "test.abstract.aws": { + "params": { + "access_key": "12345", + "secret_key": "abcd" + } + }, + + "test.s3": { + "class": "Guzzle\\Service\\Aws\\S3Client", + "extends": "test.abstract.aws", + "params": { + "devpay_product_token": "", + "devpay_user_token": "" + } + }, + + "test.simple_db": { + "class": "Guzzle\\Service\\Aws\\SimpleDb\\SimpleDbClient", + "extends": "test.abstract.aws" + }, + + "test.sqs": { + "class": "Guzzle\\Service\\Aws\\Sqs\\SqsClient", + "extends": "test.abstract.aws" + }, + + "test.centinel": { + "class": "Guzzle\\Service\\CardinalCommerce\\Centinel.CentinelClient", + "params": { + "password": "test", + "processor_id": "123", + "merchant_id": "456" + } + }, + + "test.mws": { + "class": "Guzzle\\Service\\Mws\\MwsClient", + "extends": "test.abstract.aws", + "params": { + "merchant_id": "ABCDE", + "marketplace_id": "FGHIJ", + "application_name": "GuzzleTest", + "application_version": "0.1", + "base_url": "https://mws.amazonservices.com" + } + }, + + "mock": { + "class": "Guzzle\\Tests\\Service\\Mock\\MockClient", + "params": { + "username": "test_user", + "password": "****", + "subdomain": "test" + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json new file mode 100644 index 0000000..01557ca --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service.json @@ -0,0 +1,40 @@ +{ + "includes": [ "test_service2.json" ], + "operations": { + "test": { + "uri": "/path" + }, + "concrete": { + "extends": "abstract" + }, + "foo_bar": { + "uri": "/testing", + "parameters": { + "other": { + "location": "json", + "location_key": "Other" + }, + "test": { + "type": "object", + "location": "json", + "properties": { + "baz": { + "type": "boolean", + "default": true + }, + "bar": { + "type": "string", + "filters": [ + { + "method": "strtolower", + "args": ["test", "@value"] + }, + "strtoupper" + ] + } + } + } + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json new file mode 100644 index 0000000..66dd9ef --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service2.json @@ -0,0 +1,7 @@ +{ + "operations": { + "abstract": { + "uri": "/abstract" + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json new file mode 100644 index 0000000..ae2ae0b --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/Guzzle/Tests/TestData/test_service_3.json @@ -0,0 +1,40 @@ +{ + "includes": [ "test_service2.json" ], + "operations": { + "test": { + "uri": "/path" + }, + "concrete": { + "extends": "abstract" + }, + "baz_qux": { + "uri": "/testing", + "parameters": { + "other": { + "location": "json", + "location_key": "Other" + }, + "test": { + "type": "object", + "location": "json", + "properties": { + "baz": { + "type": "boolean", + "default": true + }, + "bar": { + "type": "string", + "filters": [ + { + "method": "strtolower", + "args": ["test", "@value"] + }, + "strtoupper" + ] + } + } + } + } + } + } +} diff --git a/source/vendor/guzzle/guzzle/tests/bootstrap.php b/source/vendor/guzzle/guzzle/tests/bootstrap.php new file mode 100644 index 0000000..28908d3 --- /dev/null +++ b/source/vendor/guzzle/guzzle/tests/bootstrap.php @@ -0,0 +1,10 @@ +> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi + +before_script: + - composer self-update + - composer install --prefer-source --no-interaction --dev + - sudo mkdir -p /home/travis/build/kosinix/grafika/tests/tmp + - sudo chmod -R 0777 /home/travis/build/kosinix/grafika/tests/ + - if [[ ${TRAVIS_PHP_VERSION:0:3} == "7.0" ]]; then phpenv config-rm xdebug.ini; fi + + +script: + - if [[ ${TRAVIS_PHP_VERSION:0:3} == "5.3" ]]; then phpunit; fi + - if [[ ${TRAVIS_PHP_VERSION:0:3} == "5.4" ]]; then phpunit; fi + - if [[ ${TRAVIS_PHP_VERSION:0:3} == "5.5" ]]; then phpunit; fi + - if [[ ${TRAVIS_PHP_VERSION:0:3} == "5.6" ]]; then phpunit; fi + - if [[ ${TRAVIS_PHP_VERSION:0:3} == "7.0" ]]; then phpdbg -qrr phpunit; fi \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/README.md b/source/vendor/kosinix/grafika/README.md new file mode 100644 index 0000000..36eb9d0 --- /dev/null +++ b/source/vendor/kosinix/grafika/README.md @@ -0,0 +1,32 @@ +# Grafika + +[![Build Status](https://travis-ci.org/kosinix/grafika.svg?branch=master)](https://travis-ci.org/kosinix/grafika) + +An image processing library for PHP + +## Unique Features + +These are the features that make Grafika unique from other libs: + +- [Smart Crop](https://kosinix.github.io/grafika/smart-crop.html) - Grafika can guess the crop position based on the image content where the most important regions are preserved. +- [Animated GIF Support](https://kosinix.github.io/grafika/animated-gif.html) - It can resize animated GIFs on both GD and Imagick. On GD, Grafika uses its own GIF parser to do this. +- [5 Resize Modes](https://kosinix.github.io/grafika/resizing.html) - Resize is a first class citizen in Grafika. Call them directly using resizeFit, resizeFill, resizeExact, resizeExactWidth, and resizeExactHeight or use the generic resize api. +- [Image Compare](https://kosinix.github.io/grafika/compare-images.html) - Find how similar two images are using perceptual hashes or check if they are exactly equal. +- [Advance Filters](https://kosinix.github.io/grafika/filters/Sobel.html) - Sobel and Floyd-Steinberg Dithering. More will be added in future releases. +- [Image Blending](https://kosinix.github.io/grafika/editor/blend.html) - Blend 2 images using the following modes: normal, multiply, overlay and screen. +- **Normalized API** - No need to worry about the differences between GD and Imagick API, Grafika normalizes these operations for you. + +See documentation for a full list of features. + +## Documentation +[https://kosinix.github.io/grafika](https://kosinix.github.io/grafika) + +## API: +[https://kosinix.github.io/grafika/api](https://kosinix.github.io/grafika/api) + +## Packagist +[https://packagist.org/packages/kosinix/grafika](https://packagist.org/packages/kosinix/grafika) + +## License +- MIT License +- GPL 2 diff --git a/source/vendor/kosinix/grafika/composer.json b/source/vendor/kosinix/grafika/composer.json new file mode 100644 index 0000000..0f0f36e --- /dev/null +++ b/source/vendor/kosinix/grafika/composer.json @@ -0,0 +1,25 @@ +{ + "name": "kosinix/grafika", + "description": "An image manipulation library for PHP.", + "keywords": ["grafika"], + "homepage": "http://kosinix.github.io/grafika", + "type": "library", + "license": [ + "MIT", + "GPL-2.0+" + ], + "authors": [ + { + "name": "Nico Amarilla", + "homepage": "https://www.kosinix.com" + } + ], + "require": { + "php": ">=5.3" + }, + "autoload": { + "psr-4": { + "Grafika\\": "src/Grafika" + } + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/fonts/LiberationSans-Regular.ttf b/source/vendor/kosinix/grafika/fonts/LiberationSans-Regular.ttf new file mode 100644 index 0000000..59d2e25 Binary files /dev/null and b/source/vendor/kosinix/grafika/fonts/LiberationSans-Regular.ttf differ diff --git a/source/vendor/kosinix/grafika/fonts/st-heiti-light.ttc b/source/vendor/kosinix/grafika/fonts/st-heiti-light.ttc new file mode 100644 index 0000000..88aeb7f Binary files /dev/null and b/source/vendor/kosinix/grafika/fonts/st-heiti-light.ttc differ diff --git a/source/vendor/kosinix/grafika/license.txt b/source/vendor/kosinix/grafika/license.txt new file mode 100644 index 0000000..5e05137 --- /dev/null +++ b/source/vendor/kosinix/grafika/license.txt @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright (c) 2016 Nico G. Amarilla + +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/source/vendor/kosinix/grafika/phpunit.xml b/source/vendor/kosinix/grafika/phpunit.xml new file mode 100644 index 0000000..8fb9d78 --- /dev/null +++ b/source/vendor/kosinix/grafika/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./tests + + + \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/sami.phar b/source/vendor/kosinix/grafika/sami.phar new file mode 100644 index 0000000..3cee921 Binary files /dev/null and b/source/vendor/kosinix/grafika/sami.phar differ diff --git a/source/vendor/kosinix/grafika/samiconf.php b/source/vendor/kosinix/grafika/samiconf.php new file mode 100644 index 0000000..ec3bbd8 --- /dev/null +++ b/source/vendor/kosinix/grafika/samiconf.php @@ -0,0 +1,6 @@ + __DIR__.'/api', + 'cache_dir' => __DIR__.'/sami-cache' +)); \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Color.php b/source/vendor/kosinix/grafika/src/Grafika/Color.php new file mode 100644 index 0000000..6520287 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Color.php @@ -0,0 +1,105 @@ +hexString = $hexString; // TODO: Validate hexstring + $this->alpha = $alpha; + } + + /** + * Get RGB array + * + * @return array Contains array($r, $g, $b) + */ + public function getRgb(){ + return $this->hexToRgb( $this->hexString ); + } + + /** + * Get RGBA array + * + * @return array Contains array($r, $g, $b, $a) + */ + public function getRgba(){ + $rgba = $this->hexToRgb( $this->hexString ); + $rgba[] = $this->alpha; + return $rgba; + } + + /** + * Convert hex string to RGB + * @param string $hex Hex string. Possible values: #ffffff, #fff, fff + * @return array Contains (RGB) values red, green and blue + */ + public function hexToRgb( $hex ) { + $hex = ltrim($hex, '#'); // Remove # + + if(strlen($hex) == 3) { + $r = hexdec(substr($hex,0,1).substr($hex,0,1)); + $g = hexdec(substr($hex,1,1).substr($hex,1,1)); + $b = hexdec(substr($hex,2,1).substr($hex,2,1)); + } else { + $r = hexdec(substr($hex,0,2)); + $g = hexdec(substr($hex,2,2)); + $b = hexdec(substr($hex,4,2)); + } + return array($r, $g, $b); // Returns an array with the rgb values + } + + /** + * Get hex string. + * + * @return string + */ + public function getHexString() { + return $this->hexString; + } + + /** + * Set hex string. + * + * @param string $hexString + */ + public function setHexString($hexString) { + $this->hexString = $hexString; + } + + /** + * Alpha value. + * @return float + */ + public function getAlpha() { + return $this->alpha; + } + + /** + * @param float $alpha + */ + public function setAlpha($alpha) { + $this->alpha = $alpha; + } + + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/CubicBezier.php b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/CubicBezier.php new file mode 100644 index 0000000..980e2b4 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/CubicBezier.php @@ -0,0 +1,105 @@ +point1 = $point1; + $this->control1 = $control1; + $this->control2 = $control2; + $this->point2 = $point2; + $this->color = $color; + + } + + /** + * @return array + */ + public function getPoint1() + { + return $this->point1; + } + + /** + * @return array + */ + public function getControl1() + { + return $this->control1; + } + + /** + * @return array + */ + public function getControl2() + { + return $this->control2; + } + + /** + * @return array + */ + public function getPoint2() + { + return $this->point2; + } + + /** + * @return Color + */ + public function getColor() + { + return $this->color; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Ellipse.php b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Ellipse.php new file mode 100644 index 0000000..dcea008 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Ellipse.php @@ -0,0 +1,128 @@ +width = $width; + $this->height = $height; + $this->pos = $pos; + $this->borderSize = $borderSize; + $this->borderColor = $borderColor; + $this->fillColor = $fillColor; + } + + /** + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * @return array + */ + public function getPos() + { + return $this->pos; + } + + /** + * @return int + */ + public function getBorderSize() + { + return $this->borderSize; + } + + /** + * @return Color + */ + public function getFillColor() + { + return $this->fillColor; + } + + /** + * @return Color + */ + public function getBorderColor() + { + return $this->borderColor; + } + + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Line.php b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Line.php new file mode 100644 index 0000000..1abfd50 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Line.php @@ -0,0 +1,86 @@ +point1 = $point1; + $this->point2 = $point2; + $this->thickness = $thickness; + $this->color = $color; + } + + /** + * @return array + */ + public function getPoint1() + { + return $this->point1; + } + + /** + * @return array + */ + public function getPoint2() + { + return $this->point2; + } + + /** + * @return int + */ + public function getThickness() + { + return $this->thickness; + } + + /** + * @return Color + */ + public function getColor() + { + return $this->color; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Polygon.php b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Polygon.php new file mode 100644 index 0000000..bef3b32 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Polygon.php @@ -0,0 +1,116 @@ +points = $points; + $this->borderSize = $borderSize; + $this->borderColor = $borderColor; + $this->fillColor = $fillColor; + } + + /** + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * @return array + */ + public function getPoints() + { + return $this->points; + } + + /** + * @return int + */ + public function getBorderSize() + { + return $this->borderSize; + } + + /** + * @return Color + */ + public function getFillColor() + { + return $this->fillColor; + } + + /** + * @return Color + */ + public function getBorderColor() + { + return $this->borderColor; + } + + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/QuadraticBezier.php b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/QuadraticBezier.php new file mode 100644 index 0000000..cf0f04f --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/QuadraticBezier.php @@ -0,0 +1,89 @@ +point1 = $point1; + $this->control = $control; + $this->point2 = $point2; + $this->color = $color; + } + + /** + * @return array + */ + public function getPoint1() + { + return $this->point1; + } + + /** + * @return array + */ + public function getControl() + { + return $this->control; + } + + /** + * @return array + */ + public function getPoint2() + { + return $this->point2; + } + + /** + * @return Color + */ + public function getColor() + { + return $this->color; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Rectangle.php b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Rectangle.php new file mode 100644 index 0000000..7715e09 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/DrawingObject/Rectangle.php @@ -0,0 +1,126 @@ +width = $width; + $this->height = $height; + $this->pos = $pos; + $this->borderSize = $borderSize; + $this->borderColor = $borderColor; + $this->fillColor = $fillColor; + } + + /** + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * @return array + */ + public function getPos() + { + return $this->pos; + } + + /** + * @return int + */ + public function getBorderSize() + { + return $this->borderSize; + } + + /** + * @return Color + */ + public function getFillColor() + { + return $this->fillColor; + } + + /** + * @return Color + */ + public function getBorderColor() + { + return $this->borderColor; + } + + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/DrawingObjectInterface.php b/source/vendor/kosinix/grafika/src/Grafika/DrawingObjectInterface.php new file mode 100644 index 0000000..09ec771 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/DrawingObjectInterface.php @@ -0,0 +1,17 @@ +getWidth(); + $height = $image->getHeight(); + $gd = $image->getCore(); + + list($x0, $y0) = $this->point1; + list($x1, $y1) = $this->control1; + list($x2, $y2) = $this->control2; + list($x3, $y3) = $this->point2; + + $this->plot($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3); + + $type = $image->getType(); + $file = $image->getImageFile(); + return new Image($gd, $file, $width, $height, $type); // Create new image with updated core + } + + protected function plot($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) + { /* plot any cubic Bezier curve */ + $n = 0; + $i = 0; + $xc = $x0 + $x1 - $x2 - $x3; + $xa = $xc - 4 * ($x1 - $x2); + $xb = $x0 - $x1 - $x2 + $x3; + $xd = $xb + 4 * ($x1 + $x2); + $yc = $y0 + $y1 - $y2 - $y3; + $ya = $yc - 4 * ($y1 - $y2); + $yb = $y0 - $y1 - $y2 + $y3; + $yd = $yb + 4 * ($y1 + $y2); + $fx0 = $x0; + $fx1 = 0; + $fx2 = 0; + $fx3 = 0; + $fy0 = $y0; + $fy1 = 0; + $fy2 = 0; + $fy3 = 0; + $t1 = $xb * $xb - $xa * $xc; + $t2 = 0; + $t = array(); + /* sub-divide curve at gradient sign changes */ + if ($xa == 0) { /* horizontal */ + if (abs($xc) < 2 * abs($xb)) { + $t[$n++] = $xc / (2.0 * $xb); + } /* one change */ + } else { + if ($t1 > 0.0) { /* two changes */ + $t2 = sqrt($t1); + $t1 = ($xb - $t2) / $xa; + if (abs($t1) < 1.0) { + $t[$n++] = $t1; + } + $t1 = ($xb + $t2) / $xa; + if (abs($t1) < 1.0) { + $t[$n++] = $t1; + } + } + } + $t1 = $yb * $yb - $ya * $yc; + if ($ya == 0) { /* vertical */ + if (abs($yc) < 2 * abs($yb)) { + $t[$n++] = $yc / (2.0 * $yb); + } /* one change */ + } else { + if ($t1 > 0.0) { /* two changes */ + $t2 = sqrt($t1); + $t1 = ($yb - $t2) / $ya; + if (abs($t1) < 1.0) { + $t[$n++] = $t1; + } + $t1 = ($yb + $t2) / $ya; + if (abs($t1) < 1.0) { + $t[$n++] = $t1; + } + } + } + for ($i = 1; $i < $n; $i++) /* bubble sort of 4 points */ { + if (($t1 = $t[$i - 1]) > $t[$i]) { + $t[$i - 1] = $t[$i]; + $t[$i] = $t1; + $i = 0; + } + } + $t1 = -1.0; + $t[$n] = 1.0; /* begin / end point */ + for ($i = 0; $i <= $n; $i++) { /* plot each segment separately */ + $t2 = $t[$i]; /* sub-divide at $t[$i-1], $t[$i] */ + $fx1 = ($t1 * ($t1 * $xb - 2 * $xc) - $t2 * ($t1 * ($t1 * $xa - 2 * $xb) + $xc) + $xd) / 8 - $fx0; + $fy1 = ($t1 * ($t1 * $yb - 2 * $yc) - $t2 * ($t1 * ($t1 * $ya - 2 * $yb) + $yc) + $yd) / 8 - $fy0; + $fx2 = ($t2 * ($t2 * $xb - 2 * $xc) - $t1 * ($t2 * ($t2 * $xa - 2 * $xb) + $xc) + $xd) / 8 - $fx0; + $fy2 = ($t2 * ($t2 * $yb - 2 * $yc) - $t1 * ($t2 * ($t2 * $ya - 2 * $yb) + $yc) + $yd) / 8 - $fy0; + $fx0 -= $fx3 = ($t2 * ($t2 * (3 * $xb - $t2 * $xa) - 3 * $xc) + $xd) / 8; + $fy0 -= $fy3 = ($t2 * ($t2 * (3 * $yb - $t2 * $ya) - 3 * $yc) + $yd) / 8; + $x3 = floor($fx3 + 0.5); + $y3 = floor($fy3 + 0.5); /* scale bounds to int */ + if ($fx0 != 0.0) { + $fx1 *= $fx0 = ($x0 - $x3) / $fx0; + $fx2 *= $fx0; + } + if ($fy0 != 0.0) { + $fy1 *= $fy0 = ($y0 - $y3) / $fy0; + $fy2 *= $fy0; + } + if ($x0 != $x3 || $y0 != $y3) /* segment $t1 - $t2 */ { + $this->plotCubicSegment($gd, $x0, $y0, $x0 + $fx1, $y0 + $fy1, $x0 + $fx2, $y0 + $fy2, $x3, $y3); + } + $x0 = $x3; + $y0 = $y3; + $fx0 = $fx3; + $fy0 = $fy3; + $t1 = $t2; + } + } + + protected function plotCubicSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) + { /* plot limited anti-aliased cubic Bezier segment */ + $f = 0; + $fx = 0; + $fy = 0; + $leg = 1; + $sx = $x0 < $x3 ? 1 : -1; + $sy = $y0 < $y3 ? 1 : -1; /* step direction */ + + $xc = -abs($x0 + $x1 - $x2 - $x3); + $xa = $xc - 4 * $sx * ($x1 - $x2); + $xb = $sx * ($x0 - $x1 - $x2 + $x3); + $yc = -abs($y0 + $y1 - $y2 - $y3); + $ya = $yc - 4 * $sy * ($y1 - $y2); + $yb = $sy * ($y0 - $y1 - $y2 + $y3); + + $ab = 0; + $ac = 0; + $bc = 0; + $ba = 0; + $xx = 0; + $xy = 0; + $yy = 0; + $dx = 0; + $dy = 0; + $ex = 0; + $px = 0; + $py = 0; + $ed = 0; + $ip = 0; + $EP = 0.01; + /* check for curve restrains */ + /* slope P0-P1 == P2-P3 and (P0-P3 == P1-P2 or no slope change) */ + assert(($x1 - $x0) * ($x2 - $x3) < $EP && (($x3 - $x0) * ($x1 - $x2) < $EP || $xb * $xb < $xa * $xc + $EP)); + assert(($y1 - $y0) * ($y2 - $y3) < $EP && (($y3 - $y0) * ($y1 - $y2) < $EP || $yb * $yb < $ya * $yc + $EP)); + if ($xa == 0 && $ya == 0) { /* quadratic Bezier */ + $sx = floor((3 * $x1 - $x0 + 1) / 2); + $sy = floor((3 * $y1 - $y0 + 1) / 2); /* new midpoint */ + $this->plotQuadSegment($gd, $x0, $y0, $sx, $sy, $x3, $y3); + return; + } + $x1 = ($x1 - $x0) * ($x1 - $x0) + ($y1 - $y0) * ($y1 - $y0) + 1; /* line lengths */ + $x2 = ($x2 - $x3) * ($x2 - $x3) + ($y2 - $y3) * ($y2 - $y3) + 1; + do { /* loop over both ends */ + $ab = $xa * $yb - $xb * $ya; + $ac = $xa * $yc - $xc * $ya; + $bc = $xb * $yc - $xc * $yb; + $ip = 4 * $ab * $bc - $ac * $ac; /* self intersection loop at all? */ + $ex = $ab * ($ab + $ac - 3 * $bc) + $ac * $ac; /* P0 part of self-intersection loop? */ + $f = $ex > 0 ? 1 : sqrt(1 + 1024 / $x1); /* calculate resolution */ + $ab *= $f; + $ac *= $f; + $bc *= $f; + $ex *= $f * $f; /* increase resolution */ + $xy = 9 * ($ab + $ac + $bc) / 8; + $ba = 8 * ($xa - $ya);/* init differences of 1st degree */ + $dx = 27 * (8 * $ab * ($yb * $yb - $ya * $yc) + $ex * ($ya + 2 * $yb + $yc)) / 64 - $ya * $ya * ($xy - $ya); + $dy = 27 * (8 * $ab * ($xb * $xb - $xa * $xc) - $ex * ($xa + 2 * $xb + $xc)) / 64 - $xa * $xa * ($xy + $xa); + /* init differences of 2nd degree */ + $xx = 3 * (3 * $ab * (3 * $yb * $yb - $ya * $ya - 2 * $ya * $yc) - $ya * (3 * $ac * ($ya + $yb) + $ya * $ba)) / 4; + $yy = 3 * (3 * $ab * (3 * $xb * $xb - $xa * $xa - 2 * $xa * $xc) - $xa * (3 * $ac * ($xa + $xb) + $xa * $ba)) / 4; + $xy = $xa * $ya * (6 * $ab + 6 * $ac - 3 * $bc + $ba); + $ac = $ya * $ya; + $ba = $xa * $xa; + $xy = 3 * ($xy + 9 * $f * ($ba * $yb * $yc - $xb * $xc * $ac) - 18 * $xb * $yb * $ab) / 8; + if ($ex < 0) { /* negate values if inside self-intersection loop */ + $dx = -$dx; + $dy = -$dy; + $xx = -$xx; + $yy = -$yy; + $xy = -$xy; + $ac = -$ac; + $ba = -$ba; + } /* init differences of 3rd degree */ + $ab = 6 * $ya * $ac; + $ac = -6 * $xa * $ac; + $bc = 6 * $ya * $ba; + $ba = -6 * $xa * $ba; + $dx += $xy; + $ex = $dx + $dy; + $dy += $xy; /* error of 1st step */ + for ($fx = $fy = $f; $x0 != $x3 && $y0 != $y3;) { + $y1 = min($xy - $dx, $dy - $xy); + $ed = max($xy - $dx, $dy - $xy); /* approximate error distance */ + $ed = $f * ($ed + 2 * $ed * $y1 * $y1 / (4 * $ed * $ed + $y1 * $y1)); + $y1 = 255 * abs($ex - ($f - $fx + 1) * $dx - ($f - $fy + 1) * $dy + $f * $xy) / $ed; + if ($y1 < 256) { + $this->setPixel($gd, $x0, $y0, $y1 / 255); + } /* plot curve */ + $px = abs($ex - ($f - $fx + 1) * $dx + ($fy - 1) * $dy); /* pixel intensity x move */ + $py = abs($ex + ($fx - 1) * $dx - ($f - $fy + 1) * $dy); /* pixel intensity y move */ + $y2 = $y0; + do { /* move sub-steps of one pixel */ + if ($ip >= -$EP) /* intersection possible? -> check.. */ { + if ($dx + $xx > $xy || $dy + $yy < $xy) { + goto exits; + } + } /* two x or y steps */ + $y1 = 2 * $ex + $dx; /* save value for test of y step */ + if (2 * $ex + $dy > 0) { /* x sub-step */ + $fx--; + $ex += $dx += $xx; + $dy += $xy += $ac; + $yy += $bc; + $xx += $ab; + } else { + if ($y1 > 0) { + goto exits; + } + } /* tiny nearly cusp */ + if ($y1 <= 0) { /* y sub-step */ + $fy--; + $ex += $dy += $yy; + $dx += $xy += $bc; + $xx += $ac; + $yy += $ba; + } + } while ($fx > 0 && $fy > 0); /* pixel complete? */ + if (2 * $fy <= $f) { /* x+ anti-aliasing pixel */ + if ($py < $ed) { + $this->setPixel($gd, $x0 + $sx, $y0, $py / $ed); + } /* plot curve */ + $y0 += $sy; + $fy += $f; /* y step */ + } + if (2 * $fx <= $f) { /* y+ anti-aliasing pixel */ + if ($px < $ed) { + $this->setPixel($gd, $x0, $y2 + $sy, $px / $ed); + } /* plot curve */ + $x0 += $sx; + $fx += $f; /* x step */ + } + } + break; /* finish curve by line */ + exits: + if (2 * $ex < $dy && 2 * $fy <= $f + 2) { /* round x+ approximation pixel */ + if ($py < $ed) { + $this->setPixel($gd, $x0 + $sx, $y0, $py / $ed); + } /* plot curve */ + $y0 += $sy; + } + if (2 * $ex > $dx && 2 * $fx <= $f + 2) { /* round y+ approximation pixel */ + if ($px < $ed) { + $this->setPixel($gd, $x0, $y2 + $sy, $px / $ed); + } /* plot curve */ + $x0 += $sx; + } + $xx = $x0; + $x0 = $x3; + $x3 = $xx; + $sx = -$sx; + $xb = -$xb; /* swap legs */ + $yy = $y0; + $y0 = $y3; + $y3 = $yy; + $sy = -$sy; + $yb = -$yb; + $x1 = $x2; + } while ($leg--); /* try other end */ + $this->plotLine($gd, $x0, $y0, $x3, $y3); /* remaining part in case of cusp or crunode */ + } + + protected function plotQuadSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2) + { /* draw an limited anti-aliased quadratic Bezier segment */ + $sx = $x2 - $x1; + $sy = $y2 - $y1; + $xx = $x0 - $x1; + $yy = $y0 - $y1; + $xy = $dx = $dy = $err = $ed = 0; + $cur = $xx * $sy - $yy * $sx; /* $curvature */ + if ($sx * (int)$sx + $sy * (int)$sy > $xx * $xx + $yy * $yy) { /* begin with longer part */ + $x2 = $x0; + $x0 = $sx + $x1; + $y2 = $y0; + $y0 = $sy + $y1; + $cur = -$cur; /* swap P0 P2 */ + } + if ($cur != 0) { /* no straight line */ + $xx += $sx; + $xx *= $sx = $x0 < $x2 ? 1 : -1; /* x step direction */ + $yy += $sy; + $yy *= $sy = $y0 < $y2 ? 1 : -1; /* y step direction */ + $xy = 2 * $xx * $yy; + $xx *= $xx; + $yy *= $yy; /* differences 2nd degree */ + if ($cur * $sx * $sy < 0) { /* negat$ed $curvature? */ + $xx = -$xx; + $yy = -$yy; + $xy = -$xy; + $cur = -$cur; + } + $dx = 4.0 * $sy * ($x1 - $x0) * $cur + $xx - $xy; /* differences 1st degree */ + $dy = 4.0 * $sx * ($y0 - $y1) * $cur + $yy - $xy; + $xx += $xx; + $yy += $yy; + $err = $dx + $dy + $xy; /* $error 1st step */ + do { + $cur = min($dx + $xy, -$xy - $dy); + $ed = max($dx + $xy, -$xy - $dy); /* approximate $error distance */ + $ed += 2 * $ed * $cur * $cur / (4 * $ed * $ed + $cur * $cur); + $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy - $xy) / $ed); /* plot $curve */ + if ($x0 == $x2 || $y0 == $y2) { + break; + } /* $curve finish$ed */ + $x1 = $x0; + $cur = $dx - $err; + $y1 = 2 * $err + $dy < 0; + if (2 * $err + $dx > 0) { /* x step */ + if ($err - $dy < $ed) { + $this->setPixel($gd, $x0, $y0 + $sy, abs($err - $dy) / $ed); + } + $x0 += $sx; + $dx -= $xy; + $err += $dy += $yy; + } + if ($y1) { /* y step */ + if ($cur < $ed) { + $this->setPixel($gd, $x1 + $sx, $y0, abs($cur) / $ed); + } + $y0 += $sy; + $dy -= $xy; + $err += $dx += $xx; + } + } while ($dy < $dx); /* gradient negates -> close curves */ + } + $this->plotLine($gd, $x0, $y0, $x2, $y2); /* plot remaining needle to end */ + } + + protected function plotLine($gd, $x0, $y0, $x1, $y1) + { /* draw a black (0) anti-aliased line on white (255) background */ + $dx = abs($x1 - $x0); + $sx = $x0 < $x1 ? 1 : -1; + $dy = -abs($y1 - $y0); + $sy = $y0 < $y1 ? 1 : -1; + $err = $dx + $dy; + $e2 = $x2 = 0; /* $error value e_xy */ + $ed = $dx - $dy == 0 ? 1 : sqrt((float)$dx * $dx + (float)$dy * $dy); + for (; ;) { /* pixel loop */ + $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy) / $ed); + $e2 = $err; + $x2 = $x0; + if (2 * $e2 + $dx >= 0) { /* x step */ + if ($x0 == $x1) { + break; + } + if ($e2 - $dy < $ed) { + $this->setPixel($gd, $x0, $y0 + $sy, ($e2 - $dy) / $ed); + } + $err += $dy; + $x0 += $sx; + } + if (2 * $e2 + $dy <= 0) { /* y step */ + if ($y0 == $y1) { + break; + } + if ($dx - $e2 < $ed) { + $this->setPixel($gd, $x2 + $sx, $y0, ($dx - $e2) / $ed); + } + $err += $dx; + $y0 += $sy; + } + } + } + + /** + * @param resource $gd + * @param int $x + * @param int $y + * @param float $ar Alpha ratio + */ + protected function setPixel($gd, $x, $y, $ar) + { + list($r, $g, $b) = $this->color->getRgb(); + $c = imagecolorallocatealpha($gd, $r, $g, $b, 127 * $ar); + imagesetpixel($gd, $x, $y, $c); + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Ellipse.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Ellipse.php new file mode 100644 index 0000000..94b0c6f --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Ellipse.php @@ -0,0 +1,42 @@ +pos; + $left = $x + $this->width / 2; + $top = $y + $this->height / 2; + + if( null !== $this->fillColor ){ + list($r, $g, $b, $alpha) = $this->fillColor->getRgba(); + $fillColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha)); + imagefilledellipse($image->getCore(), $left, $top, $this->width, $this->height, $fillColorResource); + } + // Create borders. It will be placed on top of the filled ellipse (if present) + if ( 0 < $this->getBorderSize() and null !== $this->borderColor) { // With border > 0 AND borderColor !== null + list($r, $g, $b, $alpha) = $this->borderColor->getRgba(); + $borderColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha)); + imageellipse($image->getCore(), $left, $top, $this->width, $this->height, $borderColorResource); + } + + return $image; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Line.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Line.php new file mode 100644 index 0000000..5b7c712 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Line.php @@ -0,0 +1,36 @@ +point1; + list( $x2, $y2 ) = $this->point2; + list( $r, $g, $b ) = $this->color->getRgb(); + $color = imagecolorallocate( $image->getCore(), $r, $g, $b ); + if ( function_exists( 'imageantialias' ) ) { // Not available on some if PHP is not precompiled with it even if GD is enabled + imageantialias( $image->getCore(), true ); + } + imageline( $image->getCore(), $x1, $y1, $x2, $y2, $color ); + + return $image; + } + + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Polygon.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Polygon.php new file mode 100644 index 0000000..eac435d --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Polygon.php @@ -0,0 +1,64 @@ +getCore(), true); + } + + $points = $this->points(); + $count = count($this->points); + + + // Create filled polygon + if( null !== $this->fillColor){ + list($r, $g, $b, $alpha) = $this->getFillColor()->getRgba(); + $fillColorResource = imagecolorallocatealpha( + $image->getCore(), $r, $g, $b, + Editor::gdAlpha($alpha) + ); + imagefilledpolygon($image->getCore(), $points, + $count, + $fillColorResource + ); + } + + // Create polygon borders. It will be placed on top of the filled polygon (if present) + if ( 0 < $this->getBorderSize() and null !== $this->borderColor) { // With border > 0 AND borderColor !== null + list($r, $g, $b, $alpha) = $this->getBorderColor()->getRgba(); + $borderColorResource = imagecolorallocatealpha( + $image->getCore(), $r, $g, $b, + Editor::gdAlpha($alpha) + ); + imagepolygon($image->getCore(), $points, + $count, + $borderColorResource + ); + } + return $image; + } + + protected function points(){ + $points = array(); + foreach($this->points as $point){ + $points[] = $point[0]; + $points[] = $point[1]; + } + if( count($points) < 6 ){ + throw new \Exception('Polygon needs at least 3 points.'); + } + return $points; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/QuadraticBezier.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/QuadraticBezier.php new file mode 100644 index 0000000..9776059 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/QuadraticBezier.php @@ -0,0 +1,208 @@ +getWidth(); + $height = $image->getHeight(); + $gd = $image->getCore(); + + list($x0, $y0) = $this->point1; + list($x1, $y1) = $this->control; + list($x2, $y2) = $this->point2; + + $this->plot($gd, $x0, $y0, $x1, $y1, $x2, $y2); + + $type = $image->getType(); + $file = $image->getImageFile(); + return new Image($gd, $file, $width, $height, $type); // Create new image with updated core + } + + protected function plot($gd, $x0, $y0, $x1, $y1, $x2, $y2) + { + /* plot any quadratic Bezier curve */ + $x = $x0 - $x1; + $y = $y0 - $y1; + $t = $x0 - 2 * $x1 + $x2; //double + + if ((int)$x * ($x2 - $x1) > 0) { /* horizontal cut at P4? */ + if ((int)$y * ($y2 - $y1) > 0) /* vertical cut at P6 too? */ { + if (abs(($y0 - 2 * $y1 + $y2) / $t * $x) > abs($y)) { /* which first? */ + $x0 = $x2; + $x2 = $x + $x1; + $y0 = $y2; + $y2 = $y + $y1; /* swap points */ + } + } /* now horizontal cut at P4 comes first */ + $t = ($x0 - $x1) / $t; + $r = (1 - $t) * ((1 - $t) * $y0 + 2.0 * $t * $y1) + $t * $t * $y2; /* By(t=P4) */ + $t = ($x0 * $x2 - $x1 * $x1) * $t / ($x0 - $x1); /* gradient dP4/dx=0 */ + $x = floor($t + 0.5); + $y = floor($r + 0.5); + $r = ($y1 - $y0) * ($t - $x0) / ($x1 - $x0) + $y0; /* intersect P3 | P0 P1 */ + $this->plotSegment($gd, $x0, $y0, $x, floor($r + 0.5), $x, $y); + $r = ($y1 - $y2) * ($t - $x2) / ($x1 - $x2) + $y2; /* intersect P4 | P1 P2 */ + $x0 = $x1 = $x; + $y0 = $y; + $y1 = floor($r + 0.5); /* P0 = P4, P1 = P8 */ + } + if ((int)($y0 - $y1) * ($y2 - $y1) > 0) { /* vertical cut at P6? */ + $t = $y0 - 2 * $y1 + $y2; + $t = ($y0 - $y1) / $t; + $r = (1 - $t) * ((1 - $t) * $x0 + 2.0 * $t * $x1) + $t * $t * $x2; /* Bx(t=P6) */ + $t = ($y0 * $y2 - $y1 * $y1) * $t / ($y0 - $y1); /* gradient dP6/dy=0 */ + $x = floor($r + 0.5); + $y = floor($t + 0.5); + $r = ($x1 - $x0) * ($t - $y0) / ($y1 - $y0) + $x0; /* intersect P6 | P0 P1 */ + $this->plotSegment($gd, $x0, $y0, floor($r + 0.5), $y, $x, $y); + $r = ($x1 - $x2) * ($t - $y2) / ($y1 - $y2) + $x2; /* intersect P7 | P1 P2 */ + $x0 = $x; + $x1 = floor($r + 0.5); + $y0 = $y1 = $y; /* P0 = P6, P1 = P7 */ + } + $this->plotSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2); /* remaining part */ + } + + /** + * Draw an limited anti-aliased quadratic Bezier segment. + * @param $gd + * @param $x0 + * @param $y0 + * @param $x1 + * @param $y1 + * @param $x2 + * @param $y2 + */ + protected function plotSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2) + { + $sx = $x2 - $x1; + $sy = $y2 - $y1; + $xx = $x0 - $x1; + $yy = $y0 - $y1; + + $cur = $xx * $sy - $yy * $sx; /* $curvature */ + assert($xx * $sx <= 0 && $yy * $sy <= 0); + if ($sx * (int)$sx + $sy * (int)$sy > $xx * $xx + $yy * $yy) { /* begin with longer part */ + $x2 = $x0; + $x0 = $sx + $x1; + $y2 = $y0; + $y0 = $sy + $y1; + $cur = -$cur; /* swap P0 P2 */ + } + if ($cur != 0) { /* no straight line */ + $xx += $sx; + $xx *= $sx = $x0 < $x2 ? 1 : -1; /* x step direction */ + $yy += $sy; + $yy *= $sy = $y0 < $y2 ? 1 : -1; /* y step direction */ + $xy = 2 * $xx * $yy; + $xx *= $xx; + $yy *= $yy; /* differences 2nd degree */ + if ($cur * $sx * $sy < 0) { /* negat$ed $curvature? */ + $xx = -$xx; + $yy = -$yy; + $xy = -$xy; + $cur = -$cur; + } + $dx = 4.0 * $sy * ($x1 - $x0) * $cur + $xx - $xy; /* differences 1st degree */ + $dy = 4.0 * $sx * ($y0 - $y1) * $cur + $yy - $xy; + $xx += $xx; + $yy += $yy; + $err = $dx + $dy + $xy; /* $error 1st step */ + do { + $cur = min($dx + $xy, -$xy - $dy); + $ed = max($dx + $xy, -$xy - $dy); /* approximate $error distance */ + $ed += 2 * $ed * $cur * $cur / (4 * $ed * $ed + $cur * $cur); + $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy - $xy) / $ed); /* plot $curve */ + if ($x0 == $x2 || $y0 == $y2) { + break; + } /* $curve finish$ed */ + $x1 = $x0; + $cur = $dx - $err; + $y1 = 2 * $err + $dy < 0; + if (2 * $err + $dx > 0) { /* x step */ + if ($err - $dy < $ed) { + $this->setPixel($gd, $x0, $y0 + $sy, abs($err - $dy) / $ed); + } + $x0 += $sx; + $dx -= $xy; + $err += $dy += $yy; + } + if ($y1) { /* y step */ + if ($cur < $ed) { + $this->setPixel($gd, $x1 + $sx, $y0, abs($cur) / $ed); + } + $y0 += $sy; + $dy -= $xy; + $err += $dx += $xx; + } + } while ($dy < $dx); /* gradient negates -> close curves */ + } + $this->plotLine($gd, $x0, $y0, $x2, $y2); /* plot remaining needle to end */ + } + + protected function plotLine($gd, $x0, $y0, $x1, $y1) + { + $dx = abs($x1 - $x0); + $sx = $x0 < $x1 ? 1 : -1; + $dy = -abs($y1 - $y0); + $sy = $y0 < $y1 ? 1 : -1; + $err = $dx + $dy; + + $ed = $dx - $dy == 0 ? 1 : sqrt((float)$dx * $dx + (float)$dy * $dy); + for (; ;) { /* pixel loop */ + $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy) / $ed); + $e2 = $err; + $x2 = $x0; + if (2 * $e2 + $dx >= 0) { /* x step */ + if ($x0 == $x1) { + break; + } + if ($e2 - $dy < $ed) { + $this->setPixel($gd, $x0, $y0 + $sy, ($e2 - $dy) / $ed); + } + $err += $dy; + $x0 += $sx; + } + if (2 * $e2 + $dy <= 0) { /* y step */ + if ($y0 == $y1) { + break; + } + if ($dx - $e2 < $ed) { + $this->setPixel($gd, $x2 + $sx, $y0, ($dx - $e2) / $ed); + } + $err += $dx; + $y0 += $sy; + } + } + } + + /** + * @param resource $gd + * @param int $x + * @param int $y + * @param float $ar Alpha ratio + */ + protected function setPixel($gd, $x, $y, $ar) + { + list($r, $g, $b) = $this->color->getRgb(); + $c = imagecolorallocatealpha($gd, $r, $g, $b, 127 * $ar); + imagesetpixel($gd, $x, $y, $c); + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Rectangle.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Rectangle.php new file mode 100644 index 0000000..cfbfa16 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/DrawingObject/Rectangle.php @@ -0,0 +1,36 @@ +pos[0]; + $x2 = $x1 + $this->getWidth(); + $y1 = $this->pos[1]; + $y2 = $y1 + $this->getHeight(); + + if( null !== $this->fillColor ){ + list($r, $g, $b, $alpha) = $this->fillColor->getRgba(); + $fillColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha)); + imagefilledrectangle($image->getCore(), $x1, $y1, $x2, $y2, $fillColorResource); + } + // Create borders. It will be placed on top of the filled rectangle (if present) + if ( 0 < $this->getBorderSize() and null !== $this->borderColor) { // With border > 0 AND borderColor !== null + list($r, $g, $b, $alpha) = $this->borderColor->getRgba(); + $borderColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha)); + imagerectangle($image->getCore(), $x1, $y1, $x2, $y2, $borderColorResource); + } + + return $image; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Editor.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Editor.php new file mode 100644 index 0000000..1fb29a5 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Editor.php @@ -0,0 +1,1179 @@ +isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $image = $filter->apply($image); + + return $this; + } + + /** + * Blend two images together with the first image as the base and the second image on top. Supports several blend modes. + * + * @param Image $image1 The base image. + * @param Image $image2 The image placed on top of the base image. + * @param string $type The blend mode. Can be: normal, multiply, overlay or screen. + * @param float $opacity The opacity of $image2. Possible values 0.0 to 1.0 where 0.0 is fully transparent and 1.0 is fully opaque. Defaults to 1.0. + * @param string $position The position of $image2 on $image1. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to top-left. + * @param int $offsetX Number of pixels to add to the X position of $image2. + * @param int $offsetY Number of pixels to add to the Y position of $image2. + * + * @return Editor + * @throws \Exception When added image is outside of canvas or invalid blend type + */ + public function blend(&$image1, $image2, $type='normal', $opacity = 1.0, $position = 'top-left', $offsetX = 0, $offsetY = 0 ){ + + // Turn into position object + $position = new Position($position, $offsetX, $offsetY); + + // Position is for $image2. $image1 is canvas. + list($offsetX, $offsetY) = $position->getXY($image1->getWidth(), $image1->getHeight(), $image2->getWidth(), $image2->getHeight()); + + // Check if it overlaps + if( ($offsetX >= $image1->getWidth() ) or + ($offsetX + $image2->getWidth() <= 0) or + ($offsetY >= $image1->getHeight() ) or + ($offsetY + $image2->getHeight() <= 0)){ + + throw new \Exception('Invalid blending. Image 2 is outside the canvas.'); + } + + // Loop start X + $loopStartX = 0; + $canvasStartX = $offsetX; + if($canvasStartX < 0){ + $diff = 0 - $canvasStartX; + $loopStartX += $diff; + } + + // Loop end X + $loopEndX = $image2->getWidth(); + $canvasEndX = $offsetX + $image2->getWidth(); + if($canvasEndX > $image1->getWidth()){ + $diff = $canvasEndX - $image1->getWidth(); + $loopEndX -= $diff; + } + + // Loop start Y + $loopStartY = 0; + $canvasStartY = $offsetY; + if($canvasStartY < 0){ + $diff = 0 - $canvasStartY; + $loopStartY += $diff; + } + + // Loop end Y + $loopEndY = $image2->getHeight(); + $canvasEndY = $offsetY + $image2->getHeight(); + if($canvasEndY > $image1->getHeight()){ + $diff = $canvasEndY - ($image1->getHeight()); + $loopEndY -= $diff; + } + + $w = $image1->getWidth(); + $h = $image1->getHeight(); + $gd1 = $image1->getCore(); + $gd2 = $image2->getCore(); + + $canvas = imagecreatetruecolor( $w, $h ); + imagecopy( $canvas, $gd1, 0, 0, 0, 0, $w, $h ); + + $type = strtolower( $type ); + if($type==='normal') { + if ( $opacity !== 1 ) { + $this->opacity($image2, $opacity); + } + imagecopy( $canvas, $gd2, $loopStartX + $offsetX, $loopStartY + $offsetY, 0, 0, $image2->getWidth(), $image2->getHeight()); + } else if($type==='multiply'){ + $this->_blendMultiply( $canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity ); + } else if($type==='overlay'){ + $this->_blendOverlay( $canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity ); + } else if($type==='screen'){ + $this->_blendScreen( $canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity ); + } else { + throw new \Exception(sprintf('Invalid blend type "%s".', $type)); + } + + imagedestroy( $gd1 ); // Free resource + + $image1 = new Image( + $canvas, + $image1->getImageFile(), + $w, + $h, + $image1->getType() + ); + + return $this; + } + + /** + * Compare two images and returns a hamming distance. A value of 0 indicates a likely similar picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is likely a different image. + * + * @param ImageInterface|string $image1 + * @param ImageInterface|string $image2 + * + * @return int Hamming distance. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor. + * @throws \Exception + */ + public function compare($image1, $image2) + { + + if (is_string($image1)) { // If string passed, turn it into a Image object + $image1 = Image::createFromFile($image1); + $this->flatten( $image1 ); + } + + if (is_string($image2)) { // If string passed, turn it into a Image object + $image2 = Image::createFromFile($image2); + $this->flatten( $image2 ); + } + + $hash = new DifferenceHash(); + + $bin1 = $hash->hash($image1, $this); + $bin2 = $hash->hash($image2, $this); + $str1 = str_split($bin1); + $str2 = str_split($bin2); + $distance = 0; + foreach ($str1 as $i => $char) { + if ($char !== $str2[$i]) { + $distance++; + } + } + + return $distance; + + } + + /** + * Crop the image to the given dimension and position. + * + * @param Image $image + * @param int $cropWidth Crop width in pixels. + * @param int $cropHeight Crop Height in pixels. + * @param string $position The crop position. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to center. + * @param int $offsetX Number of pixels to add to the X position of the crop. + * @param int $offsetY Number of pixels to add to the Y position of the crop. + * + * @return Editor + * @throws \Exception + */ + public function crop( &$image, $cropWidth, $cropHeight, $position = 'center', $offsetX = 0, $offsetY = 0) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + if ( 'smart' === $position ) { // Smart crop + list( $x, $y ) = $this->_smartCrop( $image, $cropWidth, $cropHeight ); + } else { + // Turn into an instance of Position + $position = new Position( $position, $offsetX, $offsetY ); + + // Crop position as x,y coordinates + list( $x, $y ) = $position->getXY( $image->getWidth(), $image->getHeight(), $cropWidth, $cropHeight ); + + } + + // Create blank image + $newImageResource = imagecreatetruecolor($cropWidth, $cropHeight); + + // Now crop + imagecopyresampled( + $newImageResource, // Target image + $image->getCore(), // Source image + 0, // Target x + 0, // Target y + $x, // Src x + $y, // Src y + $cropWidth, // Target width + $cropHeight, // Target height + $cropWidth, // Src width + $cropHeight // Src height + ); + + // Free memory of old resource + imagedestroy($image->getCore()); + + // Cropped image instance + $image = new Image( + $newImageResource, + $image->getImageFile(), + $cropWidth, + $cropHeight, + $image->getType() + ); + + return $this; + } + + /** + * Draw a DrawingObject on the image. See Drawing Objects section. + * + * @param Image $image + * @param DrawingObjectInterface $drawingObject + * + * @return $this + */ + public function draw( &$image, $drawingObject) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $image = $drawingObject->draw($image); + + return $this; + } + + /** + * Compare if two images are equal. It will compare if the two images are of the same width and height. If the dimensions differ, it will return false. If the dimensions are equal, it will loop through each pixels. If one of the pixel don't match, it will return false. The pixels are compared using their RGB (Red, Green, Blue) values. + * + * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image. + * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image. + * + * @return bool True if equals false if not. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor. + * @throws \Exception + */ + public function equal($image1, $image2) + { + + if (is_string($image1)) { // If string passed, turn it into a Image object + $image1 = Image::createFromFile($image1); + $this->flatten( $image1 ); + } + + if (is_string($image2)) { // If string passed, turn it into a Image object + $image2 = Image::createFromFile($image2); + $this->flatten( $image2 ); + } + + // Check if image dimensions are equal + if ($image1->getWidth() !== $image2->getWidth() or $image1->getHeight() !== $image2->getHeight()) { + + return false; + + } else { + + // Loop using image1 + for ($y = 0; $y < $image1->getHeight(); $y++) { + for ($x = 0; $x < $image1->getWidth(); $x++) { + + // Get image1 pixel + $rgb1 = imagecolorat($image1->getCore(), $x, $y); + $r1 = ($rgb1 >> 16) & 0xFF; + $g1 = ($rgb1 >> 8) & 0xFF; + $b1 = $rgb1 & 0xFF; + + // Get image2 pixel + $rgb2 = imagecolorat($image2->getCore(), $x, $y); + $r2 = ($rgb2 >> 16) & 0xFF; + $g2 = ($rgb2 >> 8) & 0xFF; + $b2 = $rgb2 & 0xFF; + + // Compare pixel value + if ( + $r1 !== $r2 or + $g1 !== $g2 or + $b1 !== $b2 + ) { + return false; + } + } + } + } + + return true; + } + + /** + * Fill entire image with color. + * + * @param Image $image + * @param Color $color Color object + * @param int $x X-coordinate of start point + * @param int $y Y-coordinate of start point + * + * @return Editor + */ + public function fill( &$image, $color, $x = 0, $y = 0) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + list($r, $g, $b, $alpha) = $color->getRgba(); + + $colorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, + $this->gdAlpha($alpha)); + imagefill($image->getCore(), $x, $y, $colorResource); + + return $this; + } + + /** + * Flatten if animated GIF. Do nothing otherwise. + * + * @param Image $image + * + * @return Editor + */ + public function flatten(&$image){ + + if($image->isAnimated()) { + $old = $image->getCore(); + $gift = new GifHelper(); + $hex = $gift->encode($image->getBlocks()); + $gd = imagecreatefromstring(pack('H*', $hex)); // Recreate resource from blocks + + imagedestroy( $old ); // Free resource + $image = new Image( + $gd, + $image->getImageFile(), + $image->getWidth(), + $image->getHeight(), + $image->getType(), + '', // blocks + false // animated + ); + } + return $this; + } + + /** + * Flip or mirrors the image. + * + * @param Image $image + * @param string $mode The type of flip: 'h' for horizontal flip or 'v' for vertical. + * + * @return Editor + * @throws \Exception + */ + public function flip(&$image, $mode){ + + $image = $this->_flip($image, $mode); + return $this; + } + + /** + * Free the image clearing resources associated with it. + * + * @param Image $image + * + * @return Editor + */ + public function free( &$image ) + { + imagedestroy($image->getCore()); + return $this; + } + + /** + * Convert alpha value of 0 - 1 to GD compatible alpha value of 0 - 127 where 0 is opaque and 127 is transparent + * + * @param float $alpha Alpha value of 0 - 1. Example: 0, 0.60, 0.9, 1 + * + * @return int + */ + public static function gdAlpha($alpha) + { + + $scale = round(127 * $alpha); + + return $invert = 127 - $scale; + } + + /** + * Checks if the editor is available on the current PHP install. + * + * @return bool True if available false if not. + */ + public function isAvailable() + { + if (false === extension_loaded('gd') || false === function_exists('gd_info')) { + return false; + } + + // On some setups GD library does not provide imagerotate() + if ( ! function_exists('imagerotate')) { + + return false; + } + + return true; + } + + /** + * Sets the image to the specified opacity level where 1.0 is fully opaque and 0.0 is fully transparent. + * Warning: This function loops thru each pixel manually which can be slow. Use sparingly. + * + * @param Image $image + * @param float $opacity + * + * @return Editor + * @throws \Exception + */ + public function opacity( &$image, $opacity ) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + // Bounds checks + $opacity = ($opacity > 1) ? 1 : $opacity; + $opacity = ($opacity < 0) ? 0 : $opacity; + + for ($y = 0; $y < $image->getHeight(); $y++) { + for ($x = 0; $x < $image->getWidth(); $x++) { + $rgb = imagecolorat($image->getCore(), $x, $y); + $alpha = ($rgb >> 24) & 0x7F; // 127 in hex. These are binary operations. + $r = ($rgb >> 16) & 0xFF; + $g = ($rgb >> 8) & 0xFF; + $b = $rgb & 0xFF; + + // Reverse alpha values from 127-0 (transparent to opaque) to 0-127 for easy math + // Previously: 0 = opaque, 127 = transparent. + // Now: 0 = transparent, 127 = opaque + $reverse = 127 - $alpha; + $reverse = round($reverse * $opacity); + + if ($alpha < 127) { // Process non transparent pixels only + imagesetpixel($image->getCore(), $x, $y, + imagecolorallocatealpha($image->getCore(), $r, $g, $b, 127 - $reverse)); + } + } + } + + return $this; + } + + /** + * Open an image file and assign Image to first parameter. + * + * @param Image $image + * @param string $imageFile + * + * @return Editor + */ + public function open(&$image, $imageFile){ + $image = Image::createFromFile( $imageFile ); + return $this; + } + + /** + * Wrapper function for the resizeXXX family of functions. Resize image given width, height and mode. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * @param int $newHeight Height in pixels. + * @param string $mode Resize mode. Possible values: "exact", "exactHeight", "exactWidth", "fill", "fit". + * + * @return Editor + * @throws \Exception + */ + public function resize(&$image, $newWidth, $newHeight, $mode = 'fit') + { + /* + * Resize formula: + * ratio = w / h + * h = w / ratio + * w = h * ratio + */ + switch ($mode) { + case 'exact': + $this->resizeExact($image, $newWidth, $newHeight); + break; + case 'fill': + $this->resizeFill($image, $newWidth, $newHeight); + break; + case 'exactWidth': + $this->resizeExactWidth($image, $newWidth); + break; + case 'exactHeight': + $this->resizeExactHeight($image, $newHeight); + break; + case 'fit': + $this->resizeFit($image, $newWidth, $newHeight); + break; + default: + throw new \Exception(sprintf('Invalid resize mode "%s".', $mode)); + } + + return $this; + } + + /** + * Resize image to exact dimensions ignoring aspect ratio. Useful if you want to force exact width and height. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * @param int $newHeight Height in pixels. + * + * @return Editor + */ + public function resizeExact(&$image, $newWidth, $newHeight) + { + + $this->_resize($image, $newWidth, $newHeight); + + return $this; + } + + /** + * Resize image to exact height. Width is auto calculated. Useful for creating row of images with the same height. + * + * @param Image $image + * @param int $newHeight Height in pixels. + * + * @return Editor + */ + public function resizeExactHeight(&$image, $newHeight) + { + + $width = $image->getWidth(); + $height = $image->getHeight(); + $ratio = $width / $height; + + $resizeHeight = $newHeight; + $resizeWidth = $newHeight * $ratio; + + $this->_resize($image, $resizeWidth, $resizeHeight); + + return $this; + } + + /** + * Resize image to exact width. Height is auto calculated. Useful for creating column of images with the same width. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * + * @return Editor + */ + public function resizeExactWidth(&$image, $newWidth) + { + + $width = $image->getWidth(); + $height = $image->getHeight(); + $ratio = $width / $height; + + $resizeWidth = $newWidth; + $resizeHeight = round($newWidth / $ratio); + + $this->_resize($image, $resizeWidth, $resizeHeight); + + return $this; + } + + /** + * Resize image to fill all the space in the given dimension. Excess parts are cropped. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * @param int $newHeight Height in pixels. + * + * @return Editor + */ + public function resizeFill(&$image, $newWidth, $newHeight) + { + $width = $image->getWidth(); + $height = $image->getHeight(); + $ratio = $width / $height; + + // Base optimum size on new width + $optimumWidth = $newWidth; + $optimumHeight = round($newWidth / $ratio); + + if (($optimumWidth < $newWidth) or ($optimumHeight < $newHeight)) { // Oops, where trying to fill and there are blank areas + // So base optimum size on height instead + $optimumWidth = $newHeight * $ratio; + $optimumHeight = $newHeight; + } + + $this->_resize($image, $optimumWidth, $optimumHeight); + $this->crop($image, $newWidth, $newHeight); // Trim excess parts + + return $this; + } + + /** + * Resize image to fit inside the given dimension. No part of the image is lost. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * @param int $newHeight Height in pixels. + * + * @return Editor + */ + public function resizeFit(&$image, $newWidth, $newHeight) + { + + $width = $image->getWidth(); + $height = $image->getHeight(); + $ratio = $width / $height; + + // Try basing it on width first + $resizeWidth = $newWidth; + $resizeHeight = round($newWidth / $ratio); + + if (($resizeWidth > $newWidth) or ($resizeHeight > $newHeight)) { // Oops, either with or height does not fit + // So base on height instead + $resizeHeight = $newHeight; + $resizeWidth = $newHeight * $ratio; + } + + $this->_resize($image, $resizeWidth, $resizeHeight); + + return $this; + } + + /** + * Rotate an image counter-clockwise. + * + * @param Image $image + * @param int $angle The angle in degrees. + * @param Color|null $color The Color object containing the background color. + * + * @return EditorInterface An instance of image editor. + * @throws \Exception + */ + public function rotate(&$image, $angle, $color = null) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $color = ($color !== null) ? $color : new Color('#000000'); + list($r, $g, $b, $alpha) = $color->getRgba(); + + $old = $image->getCore(); + $new = imagerotate($old, $angle, imagecolorallocatealpha($old, $r, $g, $b, $alpha)); + + if(false === $new){ + throw new \Exception('Error rotating image.'); + } + + imagedestroy( $old ); // Free resource + $image = new Image( $new, $image->getImageFile(), $image->getWidth(), $image->getHeight(), $image->getType() ); + + return $this; + } + + /** + * Save the image to an image format. + * + * @param Image $image + * @param string $file File path where to save the image. + * @param null|string $type Type of image. Can be null, "GIF", "PNG", or "JPEG". + * @param null|string $quality Quality of image. Applies to JPEG only. Accepts number 0 - 100 where 0 is lowest and 100 is the highest quality. Or null for default. + * @param bool|false $interlace Set to true for progressive JPEG. Applies to JPEG only. + * @param int $permission Default permission when creating non-existing target directory. + * + * @return Editor + * @throws \Exception + */ + public function save($image, $file, $type = null, $quality = null, $interlace = false, $permission = 0755) + { + + if (null === $type) { + + $type = $this->_getImageTypeFromFileName($file); // Null given, guess type from file extension + if (ImageType::UNKNOWN === $type) { + $type = $image->getType(); // 0 result, use original image type + } + } + + $targetDir = dirname($file); // $file's directory + if (false === is_dir($targetDir)) { // Check if $file's directory exist + // Create and set default perms to 0755 + if ( ! mkdir($targetDir, $permission, true)) { + throw new \Exception(sprintf('Cannot create %s', $targetDir)); + } + } + + switch (strtoupper($type)) { + case ImageType::GIF : + if($image->isAnimated()){ + $blocks = $image->getBlocks(); + $gift = new GifHelper(); + $hex = $gift->encode($blocks); + file_put_contents($file, pack('H*', $hex)); + } else { + imagegif($image->getCore(), $file); + } + + break; + + case ImageType::PNG : + // PNG is lossless and does not need compression. Although GD allow values 0-9 (0 = no compression), we leave it alone. + imagepng($image->getCore(), $file); + break; + + default: // Defaults to jpeg + $quality = ($quality === null) ? 75 : $quality; // Default to 75 (GDs default) if null. + $quality = ($quality > 100) ? 100 : $quality; + $quality = ($quality < 0) ? 0 : $quality; + imageinterlace($image->getCore(), $interlace); + imagejpeg($image->getCore(), $file, $quality); + } + + return $this; + } + + /** + * Write text to image. + * + * @param Image $image + * @param string $text The text to be written. + * @param int $size The font size. Defaults to 12. + * @param int $x The distance from the left edge of the image to the left of the text. Defaults to 0. + * @param int $y The distance from the top edge of the image to the top of the text. Defaults to 12 (equal to font size) so that the text is placed within the image. + * @param Color $color The Color object. Default text color is black. + * @param string $font Full path to font file. If blank, will default to Liberation Sans font. + * @param int $angle Angle of text from 0 - 359. Defaults to 0. + * + * @return EditorInterface + * @throws \Exception + */ + public function text(&$image, $text, $size = 12, $x = 0, $y = 0, $color = null, $font = '', $angle = 0) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $y += $size; + + $color = ($color !== null) ? $color : new Color('#000000'); + $font = ($font !== '') ? $font : Grafika::fontsDir() . DIRECTORY_SEPARATOR . 'LiberationSans-Regular.ttf'; + + list($r, $g, $b, $alpha) = $color->getRgba(); + + $colorResource = imagecolorallocatealpha( + $image->getCore(), + $r, $g, $b, + $this->gdAlpha($alpha) + ); + + imagettftext( + $image->getCore(), + $size, + $angle, + $x, + $y, + $colorResource, + $font, + $text + ); + + return $this; + } + + /** + * @param $canvas + * @param $gd1 + * @param $gd2 + * @param $loopStartY + * @param $loopEndY + * @param $loopStartX + * @param $loopEndX + * @param $offsetX + * @param $offsetY + * + * @param $opacity + * + * @return $this + */ + private function _blendMultiply($canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity){ + + for ( $y = $loopStartY; $y < $loopEndY; $y++ ) { + for ( $x = $loopStartX; $x < $loopEndX; $x++ ) { + $canvasX = $x + $offsetX; + $canvasY = $y + $offsetY; + $argb1 = imagecolorat( $gd1, $canvasX, $canvasY ); + $r1 = ( $argb1 >> 16 ) & 0xFF; + $g1 = ( $argb1 >> 8 ) & 0xFF; + $b1 = $argb1 & 0xFF; + + $argb2 = imagecolorat( $gd2, $x, $y ); + $a2 = ($argb2 >> 24) & 0x7F; // 127 in hex. These are binary operations. + $r2 = ( $argb2 >> 16 ) & 0xFF; + $g2 = ( $argb2 >> 8 ) & 0xFF; + $b2 = $argb2 & 0xFF; + + $r3 = round($r1 * $r2 / 255); + $g3 = round($g1 * $g2 / 255); + $b3 = round($b1 * $b2 / 255); + + $reverse = 127 - $a2; + $reverse = round($reverse * $opacity); + + $argb3 = imagecolorallocatealpha( $canvas, $r3, $g3, $b3, 127 - $reverse ); + imagesetpixel( $canvas, $canvasX, $canvasY, $argb3 ); + } + } + return $canvas; + } + + /** + * @param $canvas + * @param $gd1 + * @param $gd2 + * @param $loopStartY + * @param $loopEndY + * @param $loopStartX + * @param $loopEndX + * @param $offsetX + * @param $offsetY + * + * @param $opacity + * + * @return $this + */ + private function _blendOverlay($canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity){ + + for ( $y = $loopStartY; $y < $loopEndY; $y++ ) { + for ( $x = $loopStartX; $x < $loopEndX; $x++ ) { + $canvasX = $x + $offsetX; + $canvasY = $y + $offsetY; + $argb1 = imagecolorat( $gd1, $canvasX, $canvasY ); + $r1 = ( $argb1 >> 16 ) & 0xFF; + $g1 = ( $argb1 >> 8 ) & 0xFF; + $b1 = $argb1 & 0xFF; + + $argb2 = imagecolorat( $gd2, $x, $y ); + $a2 = ($argb2 >> 24) & 0x7F; // 127 in hex. These are binary operations. + $r2 = ( $argb2 >> 16 ) & 0xFF; + $g2 = ( $argb2 >> 8 ) & 0xFF; + $b2 = $argb2 & 0xFF; + + $r1 /= 255; + $r2 /= 255; + if ($r1 < 0.5) { + $r3 = 2 * ($r1 * $r2); + } else { + $r3 = (1 - (2 *(1-$r1)) * (1-$r2)); + } + + $g1 /= 255; + $g2 /= 255; + if ($g1 < 0.5) { + $g3 = 2 * ($g1 * $g2); + } else { + $g3 = (1 - (2 *(1-$g1)) * (1-$g2)); + } + + $b1 /= 255; + $b2 /= 255; + if ($b1 < 0.5) { + $b3 = 2 * ($b1 * $b2); + } else { + $b3 = (1 - (2 *(1-$b1)) * (1-$b2)); + } + + $reverse = 127 - $a2; + $reverse = round($reverse * $opacity); + + $argb3 = imagecolorallocatealpha( $canvas, $r3*255, $g3*255, $b3*255, 127 - $reverse ); + imagesetpixel( $canvas, $canvasX, $canvasY, $argb3 ); + } + } + return $canvas; + } + + /** + * Calculate entropy based on histogram. + * + * @param $hist + * + * @return float|int + */ + private function _entropy($hist){ + $entropy = 0; + $hist_size = array_sum($hist['r']) + array_sum($hist['g']) + array_sum($hist['b']); + foreach($hist['r'] as $p){ + $p = $p / $hist_size; + $entropy += $p * log($p, 2); + } + foreach($hist['g'] as $p){ + $p = $p / $hist_size; + $entropy += $p * log($p, 2); + } + foreach($hist['b'] as $p){ + $p = $p / $hist_size; + $entropy += $p * log($p, 2); + } + return $entropy * -1; + } + + /** + * @param $canvas + * @param $gd1 + * @param $gd2 + * @param $loopStartY + * @param $loopEndY + * @param $loopStartX + * @param $loopEndX + * @param $offsetX + * @param $offsetY + * + * @param $opacity + * + * @return $this + */ + private function _blendScreen($canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity){ + + for ( $y = $loopStartY; $y < $loopEndY; $y++ ) { + for ( $x = $loopStartX; $x < $loopEndX; $x++ ) { + $canvasX = $x + $offsetX; + $canvasY = $y + $offsetY; + $argb1 = imagecolorat( $gd1, $canvasX, $canvasY ); + $r1 = ( $argb1 >> 16 ) & 0xFF; + $g1 = ( $argb1 >> 8 ) & 0xFF; + $b1 = $argb1 & 0xFF; + + $argb2 = imagecolorat( $gd2, $x, $y ); + $a2 = ($argb2 >> 24) & 0x7F; // 127 in hex. These are binary operations. + $r2 = ( $argb2 >> 16 ) & 0xFF; + $g2 = ( $argb2 >> 8 ) & 0xFF; + $b2 = $argb2 & 0xFF; + + $r3 = 255 - ( ( 255 - $r1 ) * ( 255 - $r2 ) ) / 255; + $g3 = 255 - ( ( 255 - $g1 ) * ( 255 - $g2 ) ) / 255; + $b3 = 255 - ( ( 255 - $b1 ) * ( 255 - $b2 ) ) / 255; + + $reverse = 127 - $a2; + $reverse = round($reverse * $opacity); + + $argb3 = imagecolorallocatealpha( $canvas, $r3, $g3, $b3, 127 - $reverse ); + imagesetpixel( $canvas, $canvasX, $canvasY, $argb3 ); + } + } + return $canvas; + } + + /** + * Flips image. + * @param Image $image + * @param $mode + * + * @return Image + * @throws \Exception + */ + private function _flip($image, $mode) + { + $old = $image->getCore(); + $w = $image->getWidth(); + $h = $image->getHeight(); + if ($mode === 'h') { + $new = imagecreatetruecolor($w, $h); + for ($x = 0; $x < $w; $x++) { + imagecopy($new, $old, $w - $x - 1, 0, $x, 0, 1, $h); + } + imagedestroy($old); // Free resource + return new Image( + $new, + $image->getImageFile(), + $w, + $h, + $image->getType(), + $image->getBlocks(), + $image->isAnimated() + ); + } else if ($mode === 'v') { + $new = imagecreatetruecolor($w, $h); + for ($y = 0; $y < $h; $y++) { + imagecopy($new, $old, 0, $h - $y - 1, 0, $y, $w, 1); + } + imagedestroy($old); // Free resource + return new Image( + $new, + $image->getImageFile(), + $w, + $h, + $image->getType(), + $image->getBlocks(), + $image->isAnimated() + ); + } else { + throw new \Exception(sprintf('Unsupported mode "%s"', $mode)); + } + } + + /** + * Get image type base on file extension. + * + * @param int $imageFile File path to image. + * + * @return ImageType string Type of image. + */ + private function _getImageTypeFromFileName($imageFile) + { + $ext = strtolower((string)pathinfo($imageFile, PATHINFO_EXTENSION)); + + if ('jpg' === $ext or 'jpeg' === $ext) { + return ImageType::JPEG; + } else if ('gif' === $ext) { + return ImageType::GIF; + } else if ('png' === $ext) { + return ImageType::PNG; + } else if ('wbm' === $ext or 'wbmp' === $ext) { + return ImageType::WBMP; + } else { + return ImageType::UNKNOWN; + } + } + + /** + * Resize helper function. + * + * @param Image $image + * @param int $newWidth + * @param int $newHeight + * @param int $targetX + * @param int $targetY + * @param int $srcX + * @param int $srcY + * + */ + private function _resize(&$image, $newWidth, $newHeight, $targetX = 0, $targetY = 0, $srcX = 0, $srcY = 0) + { + +// $this->_imageCheck(); + + if ($image->isAnimated()) { // Animated GIF + $gift = new GifHelper(); + $blocks = $gift->resize($image->getBlocks(), $newWidth, $newHeight); + // Resize image instance + $image = new Image( + $image->getCore(), + $image->getImageFile(), + $newWidth, + $newHeight, + $image->getType(), + $blocks, + true + ); + } else { + + // Create blank image + $newImage = Image::createBlank($newWidth, $newHeight); + + if (ImageType::PNG === $image->getType()) { + // Preserve PNG transparency + $newImage->fullAlphaMode(true); + } + + imagecopyresampled( + $newImage->getCore(), + $image->getCore(), + $targetX, + $targetY, + $srcX, + $srcY, + $newWidth, + $newHeight, + $image->getWidth(), + $image->getHeight() + ); + + // Free memory of old resource + imagedestroy($image->getCore()); + + // Resize image instance + $image = new Image( + $newImage->getCore(), + $image->getImageFile(), + $newWidth, + $newHeight, + $image->getType() + ); + + } + } + + /** + * Crop based on entropy. + * + * @param Image $oldImage + * @param $cropW + * @param $cropH + * + * @return array + */ + private function _smartCrop($oldImage, $cropW, $cropH){ + $image = clone $oldImage; + + $this->resizeFit($image, 30, 30); + + $origW = $oldImage->getWidth(); + $origH = $oldImage->getHeight(); + $resizeW = $image->getWidth(); + $resizeH = $image->getHeight(); + + $smallCropW = round(($resizeW / $origW) * $cropW); + $smallCropH = round(($resizeH / $origH) * $cropH); + + $step = 1; + + for($y = 0; $y < $resizeH-$smallCropH; $y+=$step){ + for($x = 0; $x < $resizeW-$smallCropW; $x+=$step){ + $hist[$x.'-'.$y] = $this->_entropy($image->histogram(array(array($x, $y), array($smallCropW, $smallCropH)))); + } + if($resizeW-$smallCropW <= 0){ + $hist['0-'.$y] = $this->_entropy($image->histogram(array(array(0, 0), array($smallCropW, $smallCropH)))); + } + } + if($resizeH-$smallCropH <= 0){ + $hist['0-0'] = $this->_entropy($image->histogram(array(array(0, 0), array($smallCropW, $smallCropH)))); + } + + asort($hist); + end($hist); + $pos = key($hist); // last key + list($x, $y) = explode('-', $pos); + $x = round($x*($origW / $resizeW)); + $y = round($y*($origH / $resizeH)); + + return array($x,$y); + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Blur.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Blur.php new file mode 100644 index 0000000..18455fa --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Blur.php @@ -0,0 +1,40 @@ +amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply($image) + { + for ($i=0; $i < $this->amount; $i++) { + imagefilter($image->getCore(), IMG_FILTER_GAUSSIAN_BLUR); + } + return $image; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Brightness.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Brightness.php new file mode 100644 index 0000000..1f3d31a --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Brightness.php @@ -0,0 +1,39 @@ += 0 >= 100 + + /** + * Brightness constructor. + * @param int $amount The amount of brightness to apply. >= -100 and <= -1 to darken. 0 for no change. >= 1 and <= 100 to brighten. + */ + public function __construct($amount) + { + $this->amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + imagefilter($image->getCore(), IMG_FILTER_BRIGHTNESS, ($this->amount * 2.55)); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Colorize.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Colorize.php new file mode 100644 index 0000000..1f73c83 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Colorize.php @@ -0,0 +1,50 @@ += 0 >= 100 + /** + * @var int + */ + protected $green; // -100 >= 0 >= 100 + /** + * @var int + */ + protected $blue; // -100 >= 0 >= 100 + + /** + * Colorize constructor. + * @param int $red The amount of red colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add. + * @param int $green The amount of green colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add. + * @param int $blue The amount of blue colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add. + */ + public function __construct($red, $green, $blue) + { + $this->red = round($red * 2.55); + $this->green = round($green * 2.55); + $this->blue = round($blue * 2.55); + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + + imagefilter($image->getCore(), IMG_FILTER_COLORIZE, $this->red, $this->green, $this->blue); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Contrast.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Contrast.php new file mode 100644 index 0000000..40d6e6f --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Contrast.php @@ -0,0 +1,38 @@ += 0 >= 100 + + /** + * Contrast constructor. + * @param int $amount The amount of contrast to apply. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to increase. + */ + public function __construct($amount) + { + $this->amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + + imagefilter($image->getCore(), IMG_FILTER_CONTRAST, ($this->amount * -1)); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Dither.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Dither.php new file mode 100644 index 0000000..f9c9b95 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Dither.php @@ -0,0 +1,190 @@ +type = $type; + } + + + /** + * Apply filter. + * + * @param Image $image + * + * @return Image + * @throws \Exception + */ + public function apply( $image ) { + if ( $this->type === 'ordered' ) { + return $this->ordered( $image ); + } else if ( $this->type === 'diffusion' ) { + return $this->diffusion( $image ); + } + throw new \Exception( sprintf( 'Invalid dither type "%s".', $this->type ) ); + } + + /** + * Dither using error diffusion. + * + * @param Image $image + * + * @return Image + */ + private function diffusion( $image ){ + $pixel = array(); + + // Localize vars + $width = $image->getWidth(); + $height = $image->getHeight(); + $old = $image->getCore(); + + $new = imagecreatetruecolor($width, $height); + + for ( $y = 0; $y < $height; $y+=1 ) { + for ( $x = 0; $x < $width; $x+=1 ) { + + $color = imagecolorat( $old, $x, $y ); + $r = ($color >> 16) & 0xFF; + $g = ($color >> 8) & 0xFF; + $b = $color & 0xFF; + + $gray = round($r * 0.3 + $g * 0.59 + $b * 0.11); + + if(isset($pixel[$x][$y])){ // Add errors to color if there are + $gray += $pixel[$x][$y]; + } + + if ( $gray <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess val due to adding the error + $blackOrWhite = 0; + } else { + $blackOrWhite = 255; + } + + $oldPixel = $gray; + $newPixel = $blackOrWhite; + + // Current pixel + imagesetpixel( $new, $x, $y, + imagecolorallocate( $new, + $newPixel, + $newPixel, + $newPixel + ) + ); + + $qError = $oldPixel - $newPixel; // Quantization error + + // Propagate error on neighbor pixels + if ( $x + 1 < $width ) { + $pixel[$x+1][$y] = (isset($pixel[$x+1][$y]) ? $pixel[$x+1][$y] : 0) + ($qError * (7 / 16)); + } + + if ( $x - 1 > 0 and $y + 1 < $height ) { + $pixel[$x-1][$y+1] = (isset($pixel[$x-1][$y+1]) ? $pixel[$x-1][$y+1] : 0) + ($qError * (3 / 16)); + } + + if ( $y + 1 < $height ) { + $pixel[$x][$y+1] = (isset($pixel[$x][$y+1]) ? $pixel[$x][$y+1] : 0) + ($qError * (5 / 16)); + } + + if ( $x + 1 < $width and $y + 1 < $height ) { + $pixel[$x+1][$y+1] = (isset($pixel[$x+1][$y+1]) ? $pixel[$x+1][$y+1] : 0) + ($qError * (1 / 16)); + } + + } + } + + imagedestroy($old); // Free resource + // Create new image with updated core + return new Image( + $new, + $image->getImageFile(), + $width, + $height, + $image->getType() + ); + } + + /** + * Dither by applying a threshold map. + * + * @param Image $image + * + * @return Image + */ + private function ordered( $image ) { + // Localize vars + $width = $image->getWidth(); + $height = $image->getHeight(); + $old = $image->getCore(); + + $new = imagecreatetruecolor( $width, $height ); + + $thresholdMap = array( + array( 15, 135, 45, 165 ), + array( 195, 75, 225, 105 ), + array( 60, 180, 30, 150 ), + array( 240, 120, 210, 90 ) + ); + + for ( $y = 0; $y < $height; $y += 1 ) { + for ( $x = 0; $x < $width; $x += 1 ) { + + $color = imagecolorat( $old, $x, $y ); + $r = ( $color >> 16 ) & 0xFF; + $g = ( $color >> 8 ) & 0xFF; + $b = $color & 0xFF; + + $gray = round( $r * 0.3 + $g * 0.59 + $b * 0.11 ); + + $threshold = $thresholdMap[ $x % 4 ][ $y % 4 ]; + $oldPixel = ( $gray + $threshold ) / 2; + if ( $oldPixel <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess value + $newPixel = 0; + } else { + $newPixel = 255; + } + + // Current pixel + imagesetpixel( $new, $x, $y, + imagecolorallocate( $new, + $newPixel, + $newPixel, + $newPixel + ) + ); + + } + } + + imagedestroy( $old ); // Free resource + // Create new image with updated core + return new Image( + $new, + $image->getImageFile(), + $width, + $height, + $image->getType() + ); + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Gamma.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Gamma.php new file mode 100644 index 0000000..6b7432e --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Gamma.php @@ -0,0 +1,38 @@ += 1.0 + + /** + * Gamma constructor. + * @param float $amount The amount of gamma correction to apply. >= 1.0 + */ + public function __construct($amount) + { + $this->amount = (float) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + + imagegammacorrect($image->getCore(), 1, $this->amount); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Grayscale.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Grayscale.php new file mode 100644 index 0000000..ffc0a88 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Grayscale.php @@ -0,0 +1,23 @@ +getCore(), IMG_FILTER_GRAYSCALE); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Invert.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Invert.php new file mode 100644 index 0000000..ce79060 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Invert.php @@ -0,0 +1,24 @@ +getCore(), IMG_FILTER_NEGATE); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Pixelate.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Pixelate.php new file mode 100644 index 0000000..071252f --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Pixelate.php @@ -0,0 +1,38 @@ += 1 + */ + protected $amount; + + /** + * Pixelate constructor. + * @param int $amount The size of pixelation. >= 1 + */ + public function __construct($amount) + { + $this->amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + + imagefilter($image->getCore(), IMG_FILTER_PIXELATE, $this->amount, true); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Sharpen.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Sharpen.php new file mode 100644 index 0000000..ae60cc5 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Sharpen.php @@ -0,0 +1,49 @@ += 1 to <= 100 + */ + public function __construct($amount) + { + $this->amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + $amount = $this->amount; + // build matrix + $min = $amount >= 10 ? $amount * -0.01 : 0; + $max = $amount * -0.025; + $abs = ((4 * $min + 4 * $max) * -1) + 1; + $div = 1; + $matrix = array( + array($min, $max, $min), + array($max, $abs, $max), + array($min, $max, $min) + ); + // apply the matrix + imageconvolution($image->getCore(), $matrix, $div, 0); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Sobel.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Sobel.php new file mode 100644 index 0000000..990b829 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Filter/Sobel.php @@ -0,0 +1,128 @@ +getWidth(); + $height = $image->getHeight(); + $old = $image->getCore(); + + $pixels = array(); + $new = imagecreatetruecolor($width, $height); + for ($y = 0; $y < $height; $y++) { + for ($x = 0; $x < $width; $x++) { + // row 0 + if ($x > 0 and $y > 0) { + $matrix[0][0] = $this->getColor($old, $pixels,$x - 1, $y - 1); + } else { + $matrix[0][0] = $this->getColor($old, $pixels, $x, $y); + } + + if ($y > 0) { + $matrix[1][0] = $this->getColor($old, $pixels, $x, $y - 1); + } else { + $matrix[1][0] = $this->getColor($old, $pixels, $x, $y); + } + + if ($x + 1 < $width and $y > 0) { + $matrix[2][0] = $this->getColor($old, $pixels, $x + 1, $y - 1); + } else { + $matrix[2][0] = $this->getColor($old, $pixels, $x, $y); + } + + // row 1 + if ($x > 0) { + $matrix[0][1] = $this->getColor($old, $pixels, $x - 1, $y); + } else { + $matrix[0][1] = $this->getColor($old, $pixels, $x, $y); + } + + if ($x + 1 < $width) { + $matrix[2][1] = $this->getColor($old, $pixels, $x + 1, $y); + } else { + $matrix[2][1] = $this->getColor($old, $pixels, $x, $y); + } + + // row 1 + if ($x > 0 and $y + 1 < $height) { + $matrix[0][2] = $this->getColor($old, $pixels, $x - 1, $y + 1); + } else { + $matrix[0][2] = $this->getColor($old, $pixels, $x, $y); + } + + if ($y + 1 < $height) { + $matrix[1][2] = $this->getColor($old, $pixels, $x, $y + 1); + } else { + $matrix[1][2] = $this->getColor($old, $pixels, $x, $y); + } + + if ($x + 1 < $width and $y + 1 < $height) { + $matrix[2][2] = $this->getColor($old, $pixels, $x + 1, $y + 1); + } else { + $matrix[2][2] = $this->getColor($old, $pixels, $x, $y); + } + + $edge = $this->convolve($matrix); + $edge = intval($edge / 2); + if ($edge > 255) { + $edge = 255; + } + $color = imagecolorallocate($new, $edge, $edge, $edge); + imagesetpixel($new, $x, $y, $color); + + } + } + imagedestroy($old); // Free resource + // Create and return new image with updated core + return new Image( + $new, + $image->getImageFile(), + $width, + $height, + $image->getType() + ); + } + + private function convolve($matrix) + { + $gx = $matrix[0][0] + ($matrix[2][0] * -1) + + ($matrix[0][1] * 2) + ($matrix[2][1] * -2) + + $matrix[0][2] + ($matrix[2][2] * -1); + + $gy = $matrix[0][0] + ($matrix[1][0] * 2) + $matrix[2][0] + + ($matrix[0][2] * -1) + ($matrix[1][2] * -2) + ($matrix[2][2] * -1); + + return sqrt(($gx * $gx) + ($gy * $gy)); + } + + private function getColor($gd, &$pixels, $x, $y) + { + if (isset($pixels[$x][$y])) { + return $pixels[$x][$y]; + } + $color = imagecolorat($gd, $x, $y); + $r = ($color >> 16) & 0xFF; + $g = ($color >> 8) & 0xFF; + $b = $color & 0xFF; + + return $pixels[$x][$y] = round($r * 0.3 + $g * 0.59 + $b * 0.11); // gray + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Helper/GifByteStream.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Helper/GifByteStream.php new file mode 100644 index 0000000..31bfe4c --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Helper/GifByteStream.php @@ -0,0 +1,123 @@ +position = 0; + $this->bytes = $bytes; + } + + /** + * Take a bite from the byte stream. + * + * @param int $size Byte size in integer. + * + * @return string + */ + public function bite($size) + { + $str = substr($this->bytes, $this->position * 2, $size * 2); + $this->position += $size; + + return $str; + } + + /** + * @param $byteString + * @param $offset + * + * @return bool|float + */ + public function find($byteString, $offset) + { + $pos = strpos($this->bytes, $byteString, $offset * 2); + if ($pos !== false) { + return $pos / 2; + } + + return false; + } + + /** + * @param int $step + */ + public function back($step = 1) + { + $this->position -= $step; + } + + /** + * @param int $step + */ + public function next($step = 1) + { + $this->position += $step; + } + + /** + * @return float + */ + public function length() + { + return strlen($this->bytes) / 2; + } + + /** + * @param $position + */ + public function setPosition($position) + { + $this->position = $position; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @return mixed + */ + public function getBytes() + { + return $this->bytes; + } + + /** + * @return bool + */ + public function isEnd() + { + if ($this->position > $this->length() - 1) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Helper/GifHelper.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Helper/GifHelper.php new file mode 100644 index 0000000..d491e00 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Helper/GifHelper.php @@ -0,0 +1,605 @@ +setPosition(13); + $lastPos = $bytes->getPosition(); + $gceCount = 0; + while (($lastPos = $bytes->find('21f904', $lastPos))!== false) { + $gceCount++; + if($gceCount>1){ + return true; + } + } + return false; + } + + /** + * Encode data into GIF hex string. + * + * @param array $data The array returned by decode. + * + * @return string Hex string of GIF + */ + public function encode($data){ + $hex = ''; + // header block + $hex .= $this->_fixSize($this->_asciiToHex($data['signature']),3); + $hex .= $this->_fixSize($this->_asciiToHex($data['version']),3); + + // logical screen descriptor block + $hex .= $this->_switchEndian($this->_fixSize(dechex($data['canvasWidth']), 4)); + $hex .= $this->_switchEndian($this->_fixSize(dechex($data['canvasHeight']), 4)); + $packedField = decbin($data['globalColorTableFlag']); + $packedField .= $this->_fixSize(decbin($data['colorResolution']), 3); + $packedField .= decbin($data['sortFlag']); + $packedField .= $this->_fixSize(decbin($data['sizeOfGlobalColorTable']), 3); + $hex .= $this->_fixSize(dechex(bindec($packedField)), 2); + $hex .= $this->_fixSize(dechex($data['backgroundColorIndex']), 2); + $hex .= $this->_fixSize(dechex($data['pixelAspectRatio']), 2); + + // global color table optional + if($data['globalColorTableFlag']>0) { + $hex .= $data['globalColorTable']; + } + // app ext optional + if(isset($data['applicationExtension'])){ + foreach($data['applicationExtension'] as $app){ + $hex .= '21ff0b'; + $hex .= $this->_fixSize($this->_asciiToHex($app['appId']),8); + $hex .= $this->_fixSize($this->_asciiToHex($app['appCode']),3); + foreach($app['subBlocks'] as $subBlock){ + $len = $this->_fixSize(dechex(strlen($subBlock)/2),2); + $hex .= $len.$subBlock; + } + $hex .= '00'; + } + } + + foreach($data['frames'] as $i=>$frame){ + + // graphics control optional + if(isset($frame['delayTime'])) { + $hex .= '21f904'; + $packedField = '000'; // reserved + $packedField .= $this->_fixSize(decbin($frame['disposalMethod']), 3); + $packedField .= decbin($frame['userInputFlag']); + $packedField .= decbin($frame['transparentColorFlag']); + $hex .= $this->_fixSize(dechex(bindec($packedField)), 2); + $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['delayTime']), 4)); + $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['transparentColorIndex']), 2)); + $hex .= '00'; + } + + //image desc + $hex .= '2c'; + $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['imageLeft']), 4)); + $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['imageTop']), 4)); + $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['imageWidth']), 4)); + $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['imageHeight']), 4)); + $packedField = decbin($frame['localColorTableFlag']); + $packedField .= decbin($frame['interlaceFlag']); + $packedField .= decbin($frame['sortFlag']); + $packedField .= '00'; // reserved + $packedField .= $this->_fixSize(decbin($frame['sizeOfLocalColorTable']), 3); + $hex .= $this->_fixSize(dechex(bindec($packedField)), 2); + + // local color table optional + if($frame['localColorTableFlag']>0){ + $hex .= $frame['localColorTable']; + } + + $hex .= $frame['imageData']; + } + $hex .= $data['trailer']; + return $hex; + } + + /** + * Decode GIF into array of data for easy use in PHP userland. + * + * @param GifByteStream $bytes Decode byte stream into array of GIF blocks. + * + * @return array Array containing GIF data + * @throws \Exception + * + */ + public function decode($bytes){ + $bytes->setPosition(0); + $blocks = $this->decodeToBlocks($bytes); + + return $this->expandBlocks($blocks); + } + + /** + * Decompose GIF into its block components. The GIF blocks are in the order that they appear in the byte stream. + * + * @param GifByteStream $bytes + * + * @return array + * @throws \Exception + */ + public function decodeToBlocks($bytes){ + $bytes->setPosition(0); + $blocks = array(); + + // Header block + $blocks['header'] = $bytes->bite(6); + + // Logical screen descriptor block + $part = $bytes->bite(2); // canvass w + $hex = $part; + $part = $bytes->bite(2); // canvass h + $hex .= $part; + $part = $bytes->bite(1); // packed field + $hex .= $part; + $bin = $this->_fixSize($this->_hexToBin($part),8); + $globalColorTableFlag = bindec(substr($bin, 0 ,1)); + $sizeOfGlobalColorTable = bindec(substr($bin, 5 ,3)); + + $part = $bytes->bite(1); // backgroundColorIndex + $hex .= $part; + $part = $bytes->bite(1); // pixelAspectRatio + $hex .= $part; + $blocks['logicalScreenDescriptor'] = $hex; + + // Global color table is optional so check its existence + if($globalColorTableFlag > 0){ + // Formula: 3 * (2^(N+1)) + $colorTableLength = 3*(pow(2,($sizeOfGlobalColorTable+1))); + $part = $bytes->bite($colorTableLength); + $blocks['globalColorTable'] = $part; + } + + + $commentC = $plainTextC = $appCount = $gce = $dc = 0; // index count + while(!$bytes->isEnd()){ + $part = $bytes->bite(1); + + if('21'===$part){ // block tests + $hex = $part; + $part = $bytes->bite(1); + if('ff'===$part) { // App extension block + $hex .= $part; + $part = $bytes->bite(1); // app name length should be 0x0b or int 11 but we check anyways + $size = hexdec($part); // turn it to int + $hex .= $part; + $part = $bytes->bite($size); // app name + $hex .= $part; + while (!$bytes->isEnd()) { // loop thru all app sub blocks + $nextSize = $bytes->bite(1); + if($nextSize !== '00'){ + $hex .= $nextSize; + $size = hexdec($nextSize); + $part = $bytes->bite($size); + $hex .= $part; + } else { + $hex .= $nextSize; + $blocks['applicationExtension-'.$appCount] = $hex; + break; + } + + } + + $appCount++; + } else if('f9'===$part){ // graphic + $hex .= $part; + $part = $bytes->bite(1); // size + $hex .= $part; + $part = $bytes->bite(1); // packed field + $hex .= $part; + $part = $bytes->bite(2); // delay time + $hex .= $part; + $part = $bytes->bite(1); // trans color index + $hex .= $part; + $part = $bytes->bite(1); // terminator + $hex .= $part; + $blocks['graphicControlExtension-'.$gce] = $hex; + $gce++; + } else if('01' === $part){ // plain text ext + $hex .= $part; + + while (!$bytes->isEnd()) { // loop thru all app sub blocks + $nextSize = $bytes->bite(1); + if($nextSize !== '00'){ + $hex .= $nextSize; + $size = hexdec($nextSize); + $part = $bytes->bite($size); + $hex .= $part; + } else { + $hex .= $nextSize; + $blocks['plainTextExtension-'.$plainTextC] = $hex; + break; + } + + } + $plainTextC++; + } else if('fe' === $part){ // comment ext + $hex .= $part; + + while (!$bytes->isEnd()) { // loop thru all app sub blocks + $nextSize = $bytes->bite(1); + if($nextSize !== '00'){ + $hex .= $nextSize; + $size = hexdec($nextSize); + $part = $bytes->bite($size); + $hex .= $part; + } else { + $hex .= $nextSize; + $blocks['commentExtension-'.$commentC] = $hex; + break; + } + + } + $commentC++; + } + } else if ('2c'===$part){ // image descriptors + $hex = $part; + $part = $bytes->bite(2); // imageLeft + $hex .= $part; + $part = $bytes->bite(2); // imageTop + $hex .= $part; + $part = $bytes->bite(2); // imageWidth + $hex .= $part; + $part = $bytes->bite(2); // imageHeight + $hex .= $part; + $part = $bytes->bite(1); // packed field + $hex .= $part; + $blocks['imageDescriptor-'.$dc] = $hex; + $bin = $this->_fixSize($this->_hexToBin($part), 8); + $localColorTableFlag = bindec(substr($bin, 0, 1)); + $sizeOfLocalColorTable = bindec(substr($bin, 5, 3)); + + //LC + if($localColorTableFlag){ + // Formula: 3 * (2^(N+1)) + $localColorTableLen = 3 * (pow(2, ($sizeOfLocalColorTable + 1))); + $part = $bytes->bite($localColorTableLen); + $blocks['localColorTable-'.$dc] = $part; + } + + // Image data + $part = $bytes->bite(1); // LZW code + $hex = $part; + while ($bytes->isEnd()===false) { + $nextSize = $bytes->bite(1); + $hex .= $nextSize; + if($nextSize !== '00') { + $subBlockLen = hexdec($nextSize); + $subBlock = $bytes->bite($subBlockLen); + $hex .= $subBlock; + } else { + $blocks['imageData-'.$dc] = $hex; + break; + } + + } + + $dc++; + + } else { + $blocks['trailer'] = $part; + break; + } + } + if($blocks['trailer']!=='3b'){ + throw new \Exception('Error decoding GIF. Stopped at '.$bytes->getPosition().'. Length is '.$bytes->length().'.'); + } + + return $blocks; + } + + /** + * Expand GIF blocks into useful info. + * + * @param array $blocks Accepts the array returned by decodeToBlocks + * + * @return array + */ + public function expandBlocks($blocks){ + + $decoded = array(); + foreach($blocks as $blockName=>$block){ + $bytes = new GifByteStream($block); + if(false !== strpos($blockName, 'header')){ + $part = $bytes->bite(3); + $decoded['signature'] = $this->_hexToAscii($part); + $part = $bytes->bite(3); + $decoded['version'] = $this->_hexToAscii($part); + } else if(false !== strpos($blockName, 'logicalScreenDescriptor')){ + $part = $bytes->bite(2); + $decoded['canvasWidth'] = hexdec($this->_switchEndian($part)); + $part = $bytes->bite(2); + $decoded['canvasHeight'] = hexdec($this->_switchEndian($part)); + $part = $bytes->bite(1); + $bin = $this->_fixSize($this->_hexToBin($part), 8); // Make sure len is correct + $decoded['globalColorTableFlag'] = bindec(substr($bin, 0 ,1)); + $decoded['colorResolution'] = bindec(substr($bin, 1 ,3)); + $decoded['sortFlag'] = bindec(substr($bin, 4 ,1)); + $decoded['sizeOfGlobalColorTable'] = bindec(substr($bin, 5 ,3)); + $part = $bytes->bite(1); + $decoded['backgroundColorIndex'] = hexdec($part); + $part = $bytes->bite(1); + $decoded['pixelAspectRatio'] = hexdec($part); + + } else if(false !== strpos($blockName, 'globalColorTable')){ + $decoded['globalColorTable'] = $block; + } else if(false !== strpos($blockName, 'applicationExtension')){ + $index = explode('-', $blockName, 2); + $index = $index[1]; + + $bytes->next(2); // Skip ext intro and label: 21 ff + $appNameSize = $bytes->bite(1); // 0x0b or 11 according to spec but we check anyways + $appNameSize = hexdec($appNameSize); + $appName = $this->_hexToAscii($bytes->bite($appNameSize)); + $subBlocks = array(); + while (!$bytes->isEnd()) { // loop thru all app sub blocks + $nextSize = $bytes->bite(1); + if($nextSize !== '00'){ + $size = hexdec($nextSize); + $subBlocks[] = $bytes->bite($size); + + } + } + if($appName==='NETSCAPE2.0'){ + $decoded['applicationExtension'][$index]['appId'] = 'NETSCAPE'; + $decoded['applicationExtension'][$index]['appCode'] = '2.0'; + $decoded['applicationExtension'][$index]['subBlocks'] = $subBlocks; + $decoded['loopCount'] = hexdec($this->_switchEndian(substr($subBlocks[0], 2, 4))); + } else { + $decoded['applicationExtension'][$index]['appId'] = substr($appName, 0, 8); + $decoded['applicationExtension'][$index]['appCode'] = substr($appName, 8, 3); + $decoded['applicationExtension'][$index]['subBlocks'] = $subBlocks; + } + } else if(false !== strpos($blockName, 'graphicControlExtension')) { + $index = explode('-', $blockName, 2); + $index = $index[1]; + + $bytes->next(3); // Skip ext intro, label, and block size which is always 4: 21 f9 04 + $part = $bytes->bite(1); // packed field + $bin = $this->_fixSize($this->_hexToBin($part), 8); // Make sure len is correct + $decoded['frames'][$index]['disposalMethod'] = bindec(substr($bin, 3 ,3)); + $decoded['frames'][$index]['userInputFlag'] = bindec(substr($bin, 6 ,1)); + $decoded['frames'][$index]['transparentColorFlag'] = bindec(substr($bin, 7 ,1)); + $part = $bytes->bite(2); + $decoded['frames'][$index]['delayTime'] = hexdec($this->_switchEndian($part)); + $part = $bytes->bite(1); + $decoded['frames'][$index]['transparentColorIndex'] = hexdec($part); + } else if(false !== strpos($blockName, 'imageDescriptor')) { + $index = explode('-', $blockName, 2); + $index = $index[1]; + + $bytes->next(1); // skip separator: 2c + $part = $bytes->bite(2); + $decoded['frames'][$index]['imageLeft'] = hexdec($this->_switchEndian($part)); + $part = $bytes->bite(2); + $decoded['frames'][$index]['imageTop'] = hexdec($this->_switchEndian($part)); + $part = $bytes->bite(2); + $decoded['frames'][$index]['imageWidth'] = hexdec($this->_switchEndian($part)); + $part = $bytes->bite(2); + $decoded['frames'][$index]['imageHeight'] = hexdec($this->_switchEndian($part)); + $part = $bytes->bite(1); // packed field + $bin = $this->_fixSize($this->_hexToBin($part), + 8); + $decoded['frames'][$index]['localColorTableFlag'] = bindec(substr($bin, 0, 1)); + $decoded['frames'][$index]['interlaceFlag'] = bindec(substr($bin, 1, 1)); + $decoded['frames'][$index]['sortFlag'] = bindec(substr($bin, 2, 1)); + $decoded['frames'][$index]['sizeOfLocalColorTable'] = bindec(substr($bin, 5, 3)); + } else if(false !== strpos($blockName, 'localColorTable')){ + $index = explode('-', $blockName, 2); + $index = $index[1]; + $decoded['frames'][$index]['localColorTable'] = $block; + } else if(false !== strpos($blockName, 'imageData')) { + $index = explode('-', $blockName, 2); + $index = $index[1]; + + $decoded['frames'][$index]['imageData'] = $block; + } else if($blockName === 'trailer') { + $decoded['trailer'] = $block; + } + unset($bytes); + } + + return $decoded; + } + + /** + * @param array $blocks The array returned by decode. + * + * @return array Array of images each containing 1 of each frames of the original image. + */ + public function splitFrames($blocks){ + $images = array(); + if (isset($blocks['frames'])){ + foreach($blocks['frames'] as $a=>$unused){ + $images[$a] = $blocks; + unset($images[$a]['frames']); // remove all frames. + foreach($blocks['frames'] as $b=>$frame){ + if($a===$b){ + $images[$a]['frames'][0] = $frame; // Re-add frames but use only 1 frame and discard others + break; + } + } + } + } + return $images; + } + + /** + * @param $blocks + * @param $newW + * @param $newH + * + * @return array $blocks + */ + public function resize($blocks, $newW, $newH){ + $images = $this->splitFrames($blocks); + + // Loop on individual images and resize them using Gd + $firstFrameGd = null; + foreach($images as $imageIndex=>$image){ + $hex = $this->encode($image); + $binaryRaw = pack('H*', $hex); + + // Utilize gd for resizing + $old = imagecreatefromstring($binaryRaw); + $width = imagesx($old); + $height = imagesy($old); + $new = imagecreatetruecolor($newW, $newH); // Create a blank image + if($firstFrameGd){ + $new = $firstFrameGd; + } + // Account for frame imageLeft and imageTop + $cX = $newW / $blocks['canvasWidth']; // change x + $dX = $image['frames'][0]['imageLeft']; + $cY = $newH / $blocks['canvasHeight']; + $dY = $image['frames'][0]['imageTop']; + + imagecopyresampled( + $new, + $old, + $dX * $cX,// dx + $dY * $cY, // dy + 0, + 0, + $image['frames'][0]['imageWidth'] * $cX, + $image['frames'][0]['imageHeight'] * $cY, + $width, + $height + ); + ob_start(); + imagegif($new); + $binaryRaw = ob_get_contents(); + ob_end_clean(); + + if($firstFrameGd===null){ + $firstFrameGd = $new; + } + + // Hex of resized + $bytes = $this->load($binaryRaw); + $hexNew = $this->decode($bytes); + + + + // Update original frames with hex from resized frames + $blocks['frames'][$imageIndex]['imageWidth'] = $hexNew['frames'][0]['imageWidth']; + $blocks['frames'][$imageIndex]['imageHeight'] = $hexNew['frames'][0]['imageHeight']; + $blocks['frames'][$imageIndex]['imageLeft'] = $hexNew['frames'][0]['imageLeft']; + $blocks['frames'][$imageIndex]['imageTop'] = $hexNew['frames'][0]['imageTop']; + $blocks['frames'][$imageIndex]['imageData'] = $hexNew['frames'][0]['imageData']; + + // We use local color tables on each frame. This will result in faster processing since we dont have to process the global color table at the cost of a larger file size. + $blocks['frames'][$imageIndex]['localColorTableFlag'] = $hexNew['globalColorTableFlag']; + $blocks['frames'][$imageIndex]['localColorTable'] = $hexNew['globalColorTable']; + $blocks['frames'][$imageIndex]['sizeOfLocalColorTable'] = $hexNew['sizeOfGlobalColorTable']; + $blocks['frames'][$imageIndex]['transparentColorFlag'] = 0; + } + // Update dimensions or else imagecreatefromgif will choke. + $blocks['canvasWidth'] = $newW; + $blocks['canvasHeight'] = $newH; + // Disable flickering bug. Also we are using localColorTable anyways. + $blocks['globalColorTableFlag'] = 0; + $blocks['globalColorTable'] = ''; + return $blocks; + } + + /** + * @param $asciiString + * + * @return string + */ + private function _asciiToHex($asciiString){ + $chars = str_split($asciiString, 1); + $string = ''; + foreach($chars as $char){ + $string .= dechex(ord($char)); + } + return $string; + } + + /** + * @param $hexString + * + * @return string + */ + private function _hexToAscii($hexString){ + $bytes = str_split($hexString, 2); + $string = ''; + foreach($bytes as $byte){ + $string .= chr(hexdec($byte)); // convert hex to dec to ascii character. See http://www.ascii.cl/ + } + return $string; + } + + /** + * @param $hexString + * + * @return string + */ + private function _hexToBin($hexString){ + return base_convert($hexString, 16, 2); + } + + /** + * @param $string + * @param $size + * @param string $char + * + * @return string + */ + private function _fixSize($string, $size, $char='0'){ + return str_pad($string, $size, $char, STR_PAD_LEFT); + } + + /** + * @param $hexString + * + * @return string + */ + private function _switchEndian($hexString) { + return implode('', array_reverse(str_split($hexString, 2))); + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/Image.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/Image.php new file mode 100644 index 0000000..1b48c0d --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/Image.php @@ -0,0 +1,457 @@ +gd = $gd; + $this->imageFile = $imageFile; + $this->width = $width; + $this->height = $height; + $this->type = $type; + $this->blocks = $blocks; + $this->animated = $animated; + } + + /** + * Method called when 'clone' keyword is used. + */ + public function __clone() + { + $original = $this->gd; + $copy = imagecreatetruecolor($this->width, $this->height); + + imagecopy($copy, $original, 0, 0, 0, 0, $this->width, $this->height); + + $this->gd = $copy; + } + + /** + * Output a binary raw dump of an image in a specified format. + * + * @param string|ImageType $type Image format of the dump. + * + * @throws \Exception When unsupported type. + */ + public function blob( $type = 'PNG' ) { + + $type = strtoupper($type); + if ( ImageType::GIF == $type ) { + + imagegif( $this->gd ); + + } else if ( ImageType::JPEG == $type ) { + + imagejpeg( $this->gd ); + + } else if ( ImageType::PNG == $type ) { + + imagepng( $this->gd ); + + } else if ( ImageType::WBMP == $type ) { + + imagewbmp( $this->gd ); + + } else { + throw new \Exception( sprintf( 'File type "%s" not supported.', $type ) ); + } + } + + /** + * Create Image from image file. + * + * @param string $imageFile Path to image. + * + * @return Image + * @throws \Exception + */ + public static function createFromFile( $imageFile ) { + if ( ! file_exists( $imageFile ) ) { + throw new \Exception( sprintf( 'Could not open "%s". File does not exist.', $imageFile ) ); + } + + $type = self::_guessType( $imageFile ); + if ( ImageType::GIF == $type ) { + + return self::_createGif( $imageFile ); + + } else if ( ImageType::JPEG == $type ) { + + return self::_createJpeg( $imageFile ); + + } else if ( ImageType::PNG == $type ) { + + return self::_createPng( $imageFile ); + + } else if ( ImageType::WBMP == $type ) { + + return self::_createWbmp( $imageFile ); + + } else { + throw new \Exception( sprintf( 'Could not open "%s". File type not supported.', $imageFile ) ); + } + } + + /** + * Create an Image from a GD resource. The file type defaults to unknown. + * + * @param resource $gd GD resource. + * + * @return Image + */ + public static function createFromCore( $gd ) { + return new self( $gd, '', imagesx( $gd ), imagesy( $gd ), ImageType::UNKNOWN ); + } + + /** + * Create a blank image. + * + * @param int $width Width in pixels. + * @param int $height Height in pixels. + * + * @return Image + */ + public static function createBlank($width = 1, $height = 1){ + + return new self(imagecreatetruecolor($width, $height), '', $width, $height, ImageType::UNKNOWN); + + } + + /** + * Set the blending mode for an image. Allows transparent overlays on top of an image. + * + * @param bool $flag True to enable blending mode. + * @return self + */ + public function alphaBlendingMode( $flag ){ + imagealphablending( $this->gd, $flag ); + + return $this; + } + + /** + * Enable/Disable transparency + * + * @param bool $flag True to enable alpha mode. + * @return self + */ + public function fullAlphaMode( $flag ){ + if( true === $flag ){ + $this->alphaBlendingMode( false ); // Must be false for full alpha mode to work + } + imagesavealpha( $this->gd, $flag ); + + return $this; + } + + /** + * Returns animated flag. + * + * @return bool True if animated GIF. + */ + public function isAnimated() { + return $this->animated; + } + + /** + * Get GD resource ID. + * + * @return resource + */ + public function getCore() { + return $this->gd; + } + + /** + * Get image file path. + * + * @return string File path to image. + */ + public function getImageFile() { + return $this->imageFile; + } + + /** + * Get image width in pixels. + * + * @return int + */ + public function getWidth() { + return $this->width; + } + + /** + * Get image height in pixels. + * + * @return int + */ + public function getHeight() { + return $this->height; + } + + /** + * Get image type. + * + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * Get blocks. + * + * @return string. + */ + public function getBlocks() { + return $this->blocks; + } + + /** + * Get histogram from an entire image or its sub-region. + * + * @param array|null $slice Array of slice information. array( array( 0,0), array(100,50)) means x,y is 0,0 and width,height is 100,50 + * + * @return array Returns array containing RGBA bins array('r'=>array(), 'g'=>array(), 'b'=>array(), 'a'=>array()) + */ + public function histogram($slice = null) + { + $gd = $this->getCore(); + + if(null === $slice){ + $sliceX = 0; + $sliceY = 0; + $sliceW = $this->getWidth(); + $sliceH = $this->getHeight(); + } else { + $sliceX = $slice[0][0]; + $sliceY = $slice[0][1]; + $sliceW = $slice[1][0]; + $sliceH = $slice[1][1]; + } + + $rBin = array(); + $gBin = array(); + $bBin = array(); + $aBin = array(); + for ($y = $sliceY; $y < $sliceY+$sliceH; $y++) { + for ($x = $sliceX; $x < $sliceX+$sliceW; $x++) { + $rgb = imagecolorat($gd, $x, $y); + $a = ($rgb >> 24) & 0x7F; // 127 in hex. These are binary operations. + $r = ($rgb >> 16) & 0xFF; + $g = ($rgb >> 8) & 0xFF; + $b = $rgb & 0xFF; + + if ( ! isset($rBin[$r])) { + $rBin[$r] = 1; + } else { + $rBin[$r]++; + } + + if ( ! isset($gBin[$g])) { + $gBin[$g] = 1; + } else { + $gBin[$g]++; + } + + if ( ! isset($bBin[$b])) { + $bBin[$b] = 1; + } else { + $bBin[$b]++; + } + + if ( ! isset($aBin[$a])) { + $aBin[$a] = 1; + } else { + $aBin[$a]++; + } + } + } + return array( + 'r' => $rBin, + 'g' => $gBin, + 'b' => $bBin, + 'a' => $aBin + ); + } + + /** + * Load a GIF image. + * + * @param string $imageFile + * + * @return Image + * @throws \Exception + */ + private static function _createGif( $imageFile ){ + $gift = new GifHelper(); + $bytes = $gift->open($imageFile); + $animated = $gift->isAnimated($bytes); + $blocks = ''; + if($animated){ + $blocks = $gift->decode($bytes); + } + $gd = @imagecreatefromgif( $imageFile ); + + if(!$gd){ + throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::GIF) ); + } + + return new self( + $gd, + $imageFile, + imagesx( $gd ), + imagesy( $gd ), + ImageType::GIF, + $blocks, + $animated + ); + } + + /** + * Load a JPEG image. + * + * @param string $imageFile File path to image. + * + * @return Image + * @throws \Exception + */ + private static function _createJpeg( $imageFile ){ + $gd = @imagecreatefromjpeg( $imageFile ); + + if(!$gd){ + throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::JPEG ) ); + } + + return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::JPEG ); + } + + /** + * Load a PNG image. + * + * @param string $imageFile File path to image. + * + * @return Image + * @throws \Exception + */ + private static function _createPng( $imageFile ){ + $gd = @imagecreatefrompng( $imageFile ); + + if(!$gd){ + throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::PNG) ); + } + + $image = new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::PNG ); + $image->fullAlphaMode( true ); + return $image; + } + + /** + * Load a WBMP image. + * + * @param string $imageFile + * + * @return Image + * @throws \Exception + */ + private static function _createWbmp( $imageFile ){ + $gd = @imagecreatefromwbmp( $imageFile ); + + if(!$gd){ + throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::WBMP) ); + } + + return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::WBMP ); + } + + /** + * @param $imageFile + * + * @return string + */ + private static function _guessType( $imageFile ){ + // Values from http://php.net/manual/en/image.constants.php starting with IMAGETYPE_GIF. + // 0 - unknown, + // 1 - GIF, + // 2 - JPEG, + // 3 - PNG + // 15 - WBMP + list($width, $height, $type) = getimagesize( $imageFile ); + + unset($width, $height); + + if ( 1 == $type) { + + return ImageType::GIF; + + } else if ( 2 == $type) { + + return ImageType::JPEG; + + } else if ( 3 == $type) { + + return ImageType::PNG; + + } else if ( 15 == $type) { + + return ImageType::WBMP; + + } + + return ImageType::UNKNOWN; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/ImageHash/AverageHash.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/ImageHash/AverageHash.php new file mode 100644 index 0000000..328862b --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/ImageHash/AverageHash.php @@ -0,0 +1,71 @@ +resizeExact($image, $width, $height); // Resize to exactly 8x8 + $gd = $image->getCore(); + + // Create an array of greyscale pixel values. + $pixels = array(); + for ($y = 0; $y < $height; $y++) { + for ($x = 0; $x < $width; $x++) { + $rgba = imagecolorat($gd, $x, $y); + $r = ($rgba >> 16) & 0xFF; + $g = ($rgba >> 8) & 0xFF; + $b = $rgba & 0xFF; + + $pixels[] = floor(($r + $g + $b) / 3); // Gray + } + } + + // Get the average pixel value. + $average = floor(array_sum($pixels) / count($pixels)); + // Each hash bit is set based on whether the current pixels value is above or below the average. + $hash = ''; + foreach ($pixels as $pixel) { + if ($pixel > $average) { + $hash .= '1'; + } else { + $hash .= '0'; + } + } + return $hash; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Gd/ImageHash/DifferenceHash.php b/source/vendor/kosinix/grafika/src/Grafika/Gd/ImageHash/DifferenceHash.php new file mode 100644 index 0000000..b95f8a9 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Gd/ImageHash/DifferenceHash.php @@ -0,0 +1,73 @@ +resizeExact($image, $width, $height); // Resize to exactly 9x8 + $gd = $image->getCore(); + + // Build hash + $hash = ''; + for ($y = 0; $y < $height; $y++) { + // Get the pixel value for the leftmost pixel. + $rgba = imagecolorat($gd, 0, $y); + $r = ($rgba >> 16) & 0xFF; + $g = ($rgba >> 8) & 0xFF; + $b = $rgba & 0xFF; + + $left = floor(($r + $g + $b) / 3); + for ($x = 1; $x < $width; $x++) { + // Get the pixel value for each pixel starting from position 1. + $rgba = imagecolorat($gd, $x, $y); + $r = ($rgba >> 16) & 0xFF; + $g = ($rgba >> 8) & 0xFF; + $b = $rgba & 0xFF; + $right = floor(($r + $g + $b) / 3); + // Each hash bit is set based on whether the left pixel is brighter than the right pixel. + if ($left > $right) { + $hash .= '1'; + } else { + $hash .= '0'; + } + // Prepare the next loop. + $left = $right; + } + } + $editor->free( $image ); + return $hash; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Grafika.php b/source/vendor/kosinix/grafika/src/Grafika/Grafika.php new file mode 100644 index 0000000..5489d1b --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Grafika.php @@ -0,0 +1,394 @@ +isAvailable()) { + return $editorName; + } + } + + throw new \Exception('No supported editor.'); + } + + /** + * Creates the first available editor. + * + * @param array $editorList Array of editor list names. Use this to change the order of evaluation for editors. Default order of evaluation is Imagick then GD. + * + * @return EditorInterface + * @throws \Exception + */ + public static function createEditor($editorList = array('Imagick', 'Gd')) + { + $editorName = self::detectAvailableEditor($editorList); + if ('Imagick' === $editorName) { + return new ImagickEditor(); + } else { + return new GdEditor(); + } + } + + /** + * Create an image. + * @param string $imageFile Path to image file. + * + * @return ImageInterface + * @throws \Exception + */ + public static function createImage($imageFile) + { + $editorName = self::detectAvailableEditor(); + if ('Imagick' === $editorName) { + return ImagickImage::createFromFile($imageFile); + } else { + return GdImage::createFromFile($imageFile); + } + } + + + /** + * Create a blank image. + * + * @param int $width Width of image in pixels. + * @param int $height Height of image in pixels. + * + * @return ImageInterface + * @throws \Exception + */ + public static function createBlankImage($width = 1, $height = 1) + { + $editorName = self::detectAvailableEditor(); + if ('Imagick' === $editorName) { + return ImagickImage::createBlank($width, $height); + } else { + return GdImage::createBlank($width, $height); + } + } + + + /** + * Create a filter. Detects available editor to use. + * + * @param string $filterName The name of the filter. + * + * @return FilterInterface + * @throws \Exception + */ + public static function createFilter($filterName) + { + $editorName = self::detectAvailableEditor(); + $p = func_get_args(); + if ('Imagick' === $editorName) { + switch ($filterName){ + case 'Blur': + return new ImagickBlur( + (array_key_exists(1,$p) ? $p[1] : 1) + ); + case 'Brightness': + return new ImagickBrightness( + $p[1] + ); + case 'Colorize': + return new ImagickColorize( + $p[1], $p[2], $p[3] + ); + case 'Contrast': + return new ImagickContrast( + $p[1] + ); + case 'Dither': + return new ImagickDither( + $p[1] + ); + case 'Gamma': + return new ImagickGamma( + $p[1] + ); + case 'Grayscale': + return new ImagickGrayscale(); + case 'Invert': + return new ImagickInvert(); + case 'Pixelate': + return new ImagickPixelate( + $p[1] + ); + case 'Sharpen': + return new ImagickSharpen( + $p[1] + ); + case 'Sobel': + return new ImagickSobel(); + } + throw new \Exception('Invalid filter name.'); + } else { + switch ($filterName){ + case 'Blur': + return new GdBlur( + (array_key_exists(1,$p) ? $p[1] : 1) + ); + case 'Brightness': + return new GdBrightness( + $p[1] + ); + case 'Colorize': + return new GdColorize( + $p[1], $p[2], $p[3] + ); + case 'Contrast': + return new GdContrast( + $p[1] + ); + case 'Dither': + return new GdDither( + $p[1] + ); + case 'Gamma': + return new GdGamma( + $p[1] + ); + case 'Grayscale': + return new GdGrayscale(); + case 'Invert': + return new GdInvert(); + case 'Pixelate': + return new GdPixelate( + $p[1] + ); + case 'Sharpen': + return new GdSharpen( + $p[1] + ); + case 'Sobel': + return new GdSobel(); + } + throw new \Exception('Invalid filter name.'); + } + } + + /** + * Draws an object. Detects available editor to use. + * + * @param string $drawingObjectName The name of the DrawingObject. + * + * @return DrawingObjectInterface + * @throws \Exception + * + * We use array_key_exist() instead of isset() to be able to detect a parameter with a NULL value. + */ + public static function createDrawingObject($drawingObjectName) + { + $editorName = self::detectAvailableEditor(); + $p = func_get_args(); + if ('Imagick' === $editorName) { + switch ($drawingObjectName){ + case 'CubicBezier': + return new ImagickCubicBezier( + $p[1], + $p[2], + $p[3], + $p[4], + (array_key_exists(5,$p) ? $p[5] : '#000000') + ); + case 'Ellipse': + return new ImagickEllipse( + $p[1], + $p[2], + (array_key_exists(3,$p) ? $p[3] : array(0,0)), + (array_key_exists(4,$p) ? $p[4] : 1), + (array_key_exists(5,$p) ? $p[5] : '#000000'), + (array_key_exists(6,$p) ? $p[6] : '#FFFFFF') + ); + case 'Line': + return new ImagickLine( + $p[1], + $p[2], + (array_key_exists(3,$p) ? $p[3] : 1), + (array_key_exists(4,$p) ? $p[4] : '#000000') + ); + case 'Polygon': + return new ImagickPolygon( + $p[1], + (array_key_exists(2,$p) ? $p[2] : 1), + (array_key_exists(3,$p) ? $p[3] : '#000000'), + (array_key_exists(4,$p) ? $p[4] : '#FFFFFF') + ); + case 'Rectangle': + return new ImagickRectangle( + $p[1], + $p[2], + (array_key_exists(3,$p) ? $p[3] : array(0,0)), + (array_key_exists(4,$p) ? $p[4] : 1), + (array_key_exists(5,$p) ? $p[5] : '#000000'), + (array_key_exists(6,$p) ? $p[6] : '#FFFFFF') + ); + case 'QuadraticBezier': + return new ImagickQuadraticBezier( + $p[1], + $p[2], + $p[3], + (array_key_exists(4,$p) ? $p[4] : '#000000') + ); + + } + throw new \Exception('Invalid drawing object name.'); + } else { + switch ($drawingObjectName) { + case 'CubicBezier': + return new GdCubicBezier( + $p[1], + $p[2], + $p[3], + $p[4], + (array_key_exists(5,$p) ? $p[5] : '#000000') + ); + case 'Ellipse': + return new GdEllipse( + $p[1], + $p[2], + (array_key_exists(3,$p) ? $p[3] : array(0,0)), + (array_key_exists(4,$p) ? $p[4] : 1), + (array_key_exists(5,$p) ? $p[5] : '#000000'), + (array_key_exists(6,$p) ? $p[6] : '#FFFFFF') + ); + case 'Line': + return new GdLine( + $p[1], + $p[2], + (array_key_exists(3,$p) ? $p[3] : 1), + (array_key_exists(4,$p) ? $p[4] : '#000000') + ); + case 'Polygon': + return new GdPolygon( + $p[1], + (array_key_exists(2,$p) ? $p[2] : 1), + (array_key_exists(3,$p) ? $p[3] : '#000000'), + (array_key_exists(4,$p) ? $p[4] : '#FFFFFF') + ); + case 'Rectangle': + return new GdRectangle( + $p[1], + $p[2], + (array_key_exists(3,$p) ? $p[3] : array(0,0)), + (array_key_exists(4,$p) ? $p[4] : 1), + (array_key_exists(5,$p) ? $p[5] : '#000000'), + (array_key_exists(6,$p) ? $p[6] : '#FFFFFF') + ); + case 'QuadraticBezier': + return new GdQuadraticBezier( + $p[1], + $p[2], + $p[3], + (array_key_exists(4,$p) ? $p[4] : '#000000') + ); + } + throw new \Exception('Invalid drawing object name.'); + } + } + + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/ImageInterface.php b/source/vendor/kosinix/grafika/src/Grafika/ImageInterface.php new file mode 100644 index 0000000..4ad1874 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/ImageInterface.php @@ -0,0 +1,80 @@ +getWidth(); + $height = $image->getHeight(); + $imagick = $image->getCore(); + + $draw = new \ImagickDraw(); + + $strokeColor = new \ImagickPixel($this->getColor()->getHexString()); + $fillColor = new \ImagickPixel('rgba(0,0,0,0)'); + + $draw->setStrokeOpacity(1); + $draw->setStrokeColor($strokeColor); + $draw->setFillColor($fillColor); + + $points = array( + array('x'=> $this->point1[0], 'y'=> $this->point1[1]), + array('x'=> $this->control1[0], 'y'=> $this->control1[1]), + array('x'=> $this->control2[0], 'y'=> $this->control2[1]), + array('x'=> $this->point2[0], 'y'=> $this->point2[1]), + ); + $draw->bezier($points); + + // Render the draw commands in the ImagickDraw object + $imagick->drawImage($draw); + + $type = $image->getType(); + $file = $image->getImageFile(); + return new Image($imagick, $file, $width, $height, $type); // Create new image with updated core + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Ellipse.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Ellipse.php new file mode 100644 index 0000000..d444dfb --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Ellipse.php @@ -0,0 +1,40 @@ +getBorderColor()->getHexString()); + $fillColor = new \ImagickPixel($this->getFillColor()->getHexString()); + + $draw = new \ImagickDraw(); + $draw->setStrokeColor($strokeColor); + $draw->setFillColor($fillColor); + + $draw->setStrokeWidth($this->borderSize); + + list($x, $y) = $this->pos; + $left = $x + $this->width / 2; + $top = $y + $this->height / 2; + $draw->ellipse($left, $top, $this->width/2, $this->height/2, 0, 360); + + $image->getCore()->drawImage($draw); + + return $image; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Line.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Line.php new file mode 100644 index 0000000..96cad7f --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Line.php @@ -0,0 +1,41 @@ +getColor()->getHexString()); + + $draw = new \ImagickDraw(); + + $draw->setStrokeColor($strokeColor); + + $draw->setStrokeWidth($this->thickness); + + list($x1, $y1) = $this->point1; + list($x2, $y2) = $this->point2; + $draw->line($x1, $y1, $x2, $y2); + + $image->getCore()->drawImage($draw); + + return $image; + } + + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Polygon.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Polygon.php new file mode 100644 index 0000000..a2e3e43 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Polygon.php @@ -0,0 +1,52 @@ +setStrokeWidth($this->borderSize); + + if(null !== $this->fillColor) { + $fillColor = new \ImagickPixel( $this->fillColor->getHexString() ); + $draw->setFillColor($fillColor); + } else { + $draw->setFillOpacity(0); + } + + if(null !== $this->borderColor) { + $borderColor = new \ImagickPixel( $this->borderColor->getHexString() ); + $draw->setStrokeColor($borderColor); + } else { + $draw->setStrokeOpacity(0); + } + + $draw->polygon($this->points()); + + $image->getCore()->drawImage($draw); + + return $image; + } + + protected function points(){ + $points = array(); + foreach($this->points as $i=>$pos){ + $points[$i] = array( + 'x' => $pos[0], + 'y' => $pos[1] + ); + } + if( count($points) < 3 ){ + throw new \Exception('Polygon needs at least 3 points.'); + } + return $points; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/QuadraticBezier.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/QuadraticBezier.php new file mode 100644 index 0000000..87bbe57 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/QuadraticBezier.php @@ -0,0 +1,55 @@ +getWidth(); + $height = $image->getHeight(); + $imagick = $image->getCore(); + + $draw = new \ImagickDraw(); + + $strokeColor = new \ImagickPixel($this->getColor()->getHexString()); + $fillColor = new \ImagickPixel('rgba(0,0,0,0)'); + + $draw->setStrokeOpacity(1); + $draw->setStrokeColor($strokeColor); + $draw->setFillColor($fillColor); + + + + list($x1, $y1) = $this->point1; + list($x2, $y2) = $this->control; + list($x3, $y3) = $this->point2; + $draw->pathStart(); + $draw->pathMoveToAbsolute($x1, $y1); + $draw->pathCurveToQuadraticBezierAbsolute( + $x2, $y2, + $x3, $y3 + ); + $draw->pathFinish(); + + // Render the draw commands in the ImagickDraw object + $imagick->drawImage($draw); + + $type = $image->getType(); + $file = $image->getImageFile(); + return new Image($imagick, $file, $width, $height, $type); // Create new image with updated core + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Rectangle.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Rectangle.php new file mode 100644 index 0000000..c480c66 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/DrawingObject/Rectangle.php @@ -0,0 +1,46 @@ +setStrokeWidth($this->borderSize); + + if(null !== $this->fillColor) { + $fillColor = new \ImagickPixel( $this->fillColor->getHexString() ); + $draw->setFillColor($fillColor); + } else { + $draw->setFillOpacity(0); + } + + if(null !== $this->borderColor) { + $borderColor = new \ImagickPixel( $this->borderColor->getHexString() ); + $draw->setStrokeColor($borderColor); + } else { + $draw->setStrokeOpacity(0); + } + + + + $x1 = $this->pos[0]; + $x2 = $x1 + $this->getWidth(); + $y1 = $this->pos[1]; + $y2 = $y1 + $this->getHeight(); + + $draw->rectangle( $x1, $y1, $x2, $y2 ); + + $image->getCore()->drawImage($draw); + + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Editor.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Editor.php new file mode 100644 index 0000000..36d698f --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Editor.php @@ -0,0 +1,827 @@ +isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $image = $filter->apply($image); + + return $this; + } + + /** + * Blend two images together with the first image as the base and the second image on top. Supports several blend modes. + * + * @param Image $image1 The base image. + * @param Image $image2 The image placed on top of the base image. + * @param string $type The blend mode. Can be: normal, multiply, overlay or screen. + * @param float $opacity The opacity of $image2. Possible values 0.0 to 1.0 where 0.0 is fully transparent and 1.0 is fully opaque. Defaults to 1.0. + * @param string $position The position of $image2 on $image1. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to top-left. + * @param int $offsetX Number of pixels to add to the X position of $image2. + * @param int $offsetY Number of pixels to add to the Y position of $image2. + * + * @return Editor + * @throws \Exception When added image is outside of canvas or invalid blend type + */ + public function blend(&$image1, $image2, $type='normal', $opacity = 1.0, $position = 'top-left', $offsetX = 0, $offsetY = 0 ){ + + // Turn into position object + $position = new Position($position, $offsetX, $offsetY); + + // Position is for $image2. $image1 is canvas. + list($offsetX, $offsetY) = $position->getXY($image1->getWidth(), $image1->getHeight(), $image2->getWidth(), $image2->getHeight()); + + // Check if it overlaps + if( ($offsetX >= $image1->getWidth() ) or + ($offsetX + $image2->getWidth() <= 0) or + ($offsetY >= $image1->getHeight() ) or + ($offsetY + $image2->getHeight() <= 0)){ + + throw new \Exception('Invalid blending. Image 2 is outside the canvas.'); + } + + // Loop start X + $loopStartX = 0; + $canvasStartX = $offsetX; + if($canvasStartX < 0){ + $diff = 0 - $canvasStartX; + $loopStartX += $diff; + } + + // Loop start Y + $loopStartY = 0; + $canvasStartY = $offsetY; + if($canvasStartY < 0){ + $diff = 0 - $canvasStartY; + $loopStartY += $diff; + } + + if ( $opacity !== 1 ) { + $this->opacity($image2, $opacity); + } + + $type = strtolower( $type ); + if($type==='normal') { + $image1->getCore()->compositeImage($image2->getCore(), \Imagick::COMPOSITE_OVER, $loopStartX + $offsetX, $loopStartY + $offsetY); + } else if($type==='multiply'){ + $image1->getCore()->compositeImage($image2->getCore(), \Imagick::COMPOSITE_MULTIPLY, $loopStartX + $offsetX, $loopStartY + $offsetY); + } else if($type==='overlay'){ + $image1->getCore()->compositeImage($image2->getCore(), \Imagick::COMPOSITE_OVERLAY, $loopStartX + $offsetX, $loopStartY + $offsetY); + } else if($type==='screen'){ + $image1->getCore()->compositeImage($image2->getCore(), \Imagick::COMPOSITE_SCREEN, $loopStartX + $offsetX, $loopStartY + $offsetY); + } else { + throw new \Exception(sprintf('Invalid blend type "%s".', $type)); + } + + return $this; + } + + /** + * Compare two images and returns a hamming distance. A value of 0 indicates a likely similar picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is likely a different image. + * + * @param ImageInterface|string $image1 + * @param ImageInterface|string $image2 + * + * @return int Hamming distance. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor. + * @throws \Exception + */ + public function compare($image1, $image2) + { + + if (is_string($image1)) { // If string passed, turn it into a Image object + $image1 = Image::createFromFile($image1); + $this->flatten( $image1 ); + } + + if (is_string($image2)) { // If string passed, turn it into a Image object + $image2 = Image::createFromFile($image2); + $this->flatten( $image2 ); + } + + $hash = new DifferenceHash(); + + $bin1 = $hash->hash($image1, $this); + $bin2 = $hash->hash($image2, $this); + $str1 = str_split($bin1); + $str2 = str_split($bin2); + $distance = 0; + foreach ($str1 as $i => $char) { + if ($char !== $str2[$i]) { + $distance++; + } + } + + return $distance; + + } + + /** + * Crop the image to the given dimension and position. + * + * @param Image $image + * @param int $cropWidth Crop width in pixels. + * @param int $cropHeight Crop Height in pixels. + * @param string $position The crop position. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to center. + * @param int $offsetX Number of pixels to add to the X position of the crop. + * @param int $offsetY Number of pixels to add to the Y position of the crop. + * + * @return Editor + * @throws \Exception + */ + public function crop(&$image, $cropWidth, $cropHeight, $position = 'center', $offsetX = 0, $offsetY = 0) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + if ( 'smart' === $position ) { // Smart crop + list( $x, $y ) = $this->_smartCrop( $image, $cropWidth, $cropHeight ); + } else { + // Turn into an instance of Position + $position = new Position( $position, $offsetX, $offsetY ); + + // Crop position as x,y coordinates + list( $x, $y ) = $position->getXY( $image->getWidth(), $image->getHeight(), $cropWidth, $cropHeight ); + + } + + $image->getCore()->cropImage($cropWidth, $cropHeight, $x, $y); + + return $this; + } + + /** + * Draw a DrawingObject on the image. See Drawing Objects section. + * + * @param Image $image + * @param DrawingObjectInterface $drawingObject + * + * @return $this + */ + public function draw(&$image, $drawingObject) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $image = $drawingObject->draw($image); + + return $this; + } + + /** + * Compare if two images are equal. It will compare if the two images are of the same width and height. If the dimensions differ, it will return false. If the dimensions are equal, it will loop through each pixels. If one of the pixel don't match, it will return false. The pixels are compared using their RGB (Red, Green, Blue) values. + * + * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image. + * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image. + * + * @return bool True if equals false if not. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor. + * @throws \Exception + */ + public function equal($image1, $image2) + { + + if (is_string($image1)) { // If string passed, turn it into a Image object + $image1 = Image::createFromFile($image1); + $this->flatten( $image1 ); + } + + if (is_string($image2)) { // If string passed, turn it into a Image object + $image2 = Image::createFromFile($image2); + $this->flatten( $image2 ); + } + + // Check if image dimensions are equal + if ($image1->getWidth() !== $image2->getWidth() or $image1->getHeight() !== $image2->getHeight()) { + + return false; + + } else { + + // Loop using image1 + $pixelIterator = $image1->getCore()->getPixelIterator(); + foreach ($pixelIterator as $row => $pixels) { /* Loop through pixel rows */ + foreach ($pixels as $column => $pixel) { /* Loop through the pixels in the row (columns) */ + /** + * Get image1 pixel + * @var $pixel \ImagickPixel + */ + $rgba1 = $pixel->getColor(); + + // Get image2 pixel + $rgba2 = $image2->getCore()->getImagePixelColor($column, $row)->getColor(); + + // Compare pixel value + if ( + $rgba1['r'] !== $rgba2['r'] or + $rgba1['g'] !== $rgba2['g'] or + $rgba1['b'] !== $rgba2['b'] + ) { + return false; + } + } + $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */ + } + } + + return true; + } + + /** + * Fill entire image with color. + * + * @param Image $image + * @param Color $color Color object + * @param int $x X-coordinate of start point + * @param int $y Y-coordinate of start point + * + * @return self + */ + public function fill(&$image, $color, $x = 0, $y = 0) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $target = $image->getCore()->getImagePixelColor($x, $y); + $image->getCore()->floodfillPaintImage($color->getHexString(), 1, $target, $x, $y, false); + + return $this; + } + + /** + * Flatten if animated GIF. Do nothing otherwise. + * + * @param Image $image + * + * @return self + */ + public function flatten(&$image){ + if($image->isAnimated()){ + $imagick = $image->getCore()->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN); + $image = new Image( + $imagick, + $image->getImageFile(), + $image->getWidth(), + $image->getHeight(), + $image->getType(), + '', // blocks + false // animated + ); + } + return $this; + } + + /** + * Flip or mirrors the image. + * + * @param Image $image + * @param string $mode The type of flip: 'h' for horizontal flip or 'v' for vertical. + * + * @return Editor + * @throws \Exception + */ + public function flip(&$image, $mode){ + if ($mode === 'h') { + $image->getCore()->flopImage(); + } else if ($mode === 'v') { + $image->getCore()->flipImage(); + } else { + throw new \Exception(sprintf('Unsupported mode "%s"', $mode)); + } + return $this; + } + + /** + * Free the image clearing resources associated with it. + * + * @param Image $image + * + * @return Editor + */ + public function free( &$image ) + { + $image->getCore()->clear(); + return $this; + } + + /** + * Checks if the editor is available on the current PHP install. + * + * @return bool True if available false if not. + */ + public function isAvailable() + { + // First, test Imagick's extension and classes. + if (false === extension_loaded('imagick') || + false === class_exists('Imagick') || + false === class_exists('ImagickDraw') || + false === class_exists('ImagickPixel') || + false === class_exists('ImagickPixelIterator') + ) { + return false; + } + + return true; + } + + /** + * Sets the image to the specified opacity level where 1.0 is fully opaque and 0.0 is fully transparent. + * + * @param Image $image + * @param float $opacity + * + * @return self + * @throws \Exception + */ + public function opacity(&$image, $opacity) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + // Bounds checks + $opacity = ($opacity > 1) ? 1 : $opacity; + $opacity = ($opacity < 0) ? 0 : $opacity; + + $image->getCore()->setImageOpacity($opacity); + + return $this; + } + + /** + * Open an image file and assign Image to first parameter. + * + * @param Image $image + * @param string $imageFile + * + * @return Editor + */ + public function open(&$image, $imageFile){ + $image = Image::createFromFile( $imageFile ); + return $this; + } + + /** + * Wrapper function for the resizeXXX family of functions. Resize image given width, height and mode. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * @param int $newHeight Height in pixels. + * @param string $mode Resize mode. Possible values: "exact", "exactHeight", "exactWidth", "fill", "fit". + * + * @return Editor + * @throws \Exception + */ + public function resize(&$image, $newWidth, $newHeight, $mode = 'fit') + { + /* + * Resize formula: + * ratio = w / h + * h = w / ratio + * w = h * ratio + */ + switch ($mode) { + case 'exact': + $this->resizeExact($image, $newWidth, $newHeight); + break; + case 'fill': + $this->resizeFill($image, $newWidth, $newHeight); + break; + case 'exactWidth': + $this->resizeExactWidth($image, $newWidth); + break; + case 'exactHeight': + $this->resizeExactHeight($image, $newHeight); + break; + case 'fit': + $this->resizeFit($image, $newWidth, $newHeight); + break; + default: + throw new \Exception(sprintf('Invalid resize mode "%s".', $mode)); + } + + return $this; + } + + /** + * Resize image to exact dimensions ignoring aspect ratio. Useful if you want to force exact width and height. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * @param int $newHeight Height in pixels. + * + * @return self + */ + public function resizeExact(&$image, $newWidth, $newHeight) + { + + $this->_resize($image, $newWidth, $newHeight); + + return $this; + } + + /** + * Resize image to exact height. Width is auto calculated. Useful for creating row of images with the same height. + * + * @param Image $image + * @param int $newHeight Height in pixels. + * + * @return self + */ + public function resizeExactHeight(&$image, $newHeight) + { + + $width = $image->getWidth(); + $height = $image->getHeight(); + $ratio = $width / $height; + + $resizeHeight = $newHeight; + $resizeWidth = $newHeight * $ratio; + + $this->_resize($image, $resizeWidth, $resizeHeight); + + return $this; + } + + /** + * Resize image to exact width. Height is auto calculated. Useful for creating column of images with the same width. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * + * @return self + */ + public function resizeExactWidth(&$image, $newWidth) + { + + $width = $image->getWidth(); + $height = $image->getHeight(); + $ratio = $width / $height; + + $resizeWidth = $newWidth; + $resizeHeight = round($newWidth / $ratio); + + $this->_resize($image, $resizeWidth, $resizeHeight); + + return $this; + } + + /** + * Resize image to fill all the space in the given dimension. Excess parts are cropped. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * @param int $newHeight Height in pixels. + * + * @return self + */ + public function resizeFill(&$image, $newWidth, $newHeight) + { + $width = $image->getWidth(); + $height = $image->getHeight(); + $ratio = $width / $height; + + // Base optimum size on new width + $optimumWidth = $newWidth; + $optimumHeight = round($newWidth / $ratio); + + if (($optimumWidth < $newWidth) or ($optimumHeight < $newHeight)) { // Oops, where trying to fill and there are blank areas + // So base optimum size on height instead + $optimumWidth = $newHeight * $ratio; + $optimumHeight = $newHeight; + } + + $this->_resize($image, $optimumWidth, $optimumHeight); + $this->crop($image, $newWidth, $newHeight); // Trim excess parts + + return $this; + } + + /** + * Resize image to fit inside the given dimension. No part of the image is lost. + * + * @param Image $image + * @param int $newWidth Width in pixels. + * @param int $newHeight Height in pixels. + * + * @return self + */ + public function resizeFit(&$image, $newWidth, $newHeight) + { + + $width = $image->getWidth(); + $height = $image->getHeight(); + $ratio = $width / $height; + + // Try basing it on width first + $resizeWidth = $newWidth; + $resizeHeight = round($newWidth / $ratio); + + if (($resizeWidth > $newWidth) or ($resizeHeight > $newHeight)) { // Oops, either with or height does not fit + // So base on height instead + $resizeHeight = $newHeight; + $resizeWidth = $newHeight * $ratio; + } + + $this->_resize($image, $resizeWidth, $resizeHeight); + + return $this; + } + + /** + * Rotate an image counter-clockwise. + * + * @param Image $image + * @param int $angle The angle in degrees. + * @param Color|null $color The Color object containing the background color. + * + * @return EditorInterface An instance of image editor. + */ + public function rotate(&$image, $angle, $color = null) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $color = ($color !== null) ? $color : new Color('#000000'); + list($r, $g, $b, $alpha) = $color->getRgba(); + + $image->getCore()->rotateImage(new \ImagickPixel("rgba($r, $g, $b, $alpha)"), $angle * -1); + + return $this; + } + + /** + * Save the image to an image format. + * + * @param Image $image + * @param string $file File path where to save the image. + * @param null|string $type Type of image. Can be null, "GIF", "PNG", or "JPEG". + * @param null|string $quality Quality of image. Applies to JPEG only. Accepts number 0 - 100 where 0 is lowest and 100 is the highest quality. Or null for default. + * @param bool|false $interlace Set to true for progressive JPEG. Applies to JPEG only. + * @param int $permission Default permission when creating non-existing target directory. + * + * @return Editor + * @throws \Exception + */ + public function save( $image, $file, $type = null, $quality = null, $interlace = false, $permission = 0755) + { + + if (null === $type) { + + $type = $this->_getImageTypeFromFileName($file); // Null given, guess type from file extension + if (ImageType::UNKNOWN === $type) { + $type = $image->getType(); // 0 result, use original image type + } + } + + $targetDir = dirname($file); // $file's directory + if (false === is_dir($targetDir)) { // Check if $file's directory exist + // Create and set default perms to 0755 + if ( ! mkdir($targetDir, $permission, true)) { + throw new \Exception(sprintf('Cannot create %s', $targetDir)); + } + } + + switch (strtoupper($type)) { + case ImageType::GIF : + $image->getCore()->writeImages($file, true); // Support animated image. Eg. GIF + break; + + case ImageType::PNG : + // PNG is lossless and does not need compression. Although GD allow values 0-9 (0 = no compression), we leave it alone. + $image->getCore()->setImageFormat($type); + $image->getCore()->writeImage($file); + break; + + default: // Defaults to jpeg + $quality = ($quality === null) ? 75 : $quality; // Default to 75 (GDs default) if null. + $quality = ($quality > 100) ? 100 : $quality; + $quality = ($quality <= 0) ? 1 : $quality; // Note: If 0 change it to 1. The lowest quality in Imagick is 1 whereas in GD its 0. + + if ($interlace) { + $image->getCore()->setImageInterlaceScheme(\Imagick::INTERLACE_JPEG); + } + $image->getCore()->setImageFormat($type); + $image->getCore()->setImageCompression(\Imagick::COMPRESSION_JPEG); + $image->getCore()->setImageCompressionQuality($quality); + $image->getCore()->writeImage($file); // Single frame image. Eg. JPEG + } + + return $this; + } + + /** + * Write text to image. + * + * @param Image $image + * @param string $text The text to be written. + * @param int $size The font size. Defaults to 12. + * @param int $x The distance from the left edge of the image to the left of the text. Defaults to 0. + * @param int $y The distance from the top edge of the image to the top of the text. Defaults to 12 (equal to font size) so that the text is placed within the image. + * @param Color $color The Color object. Default text color is black. + * @param string $font Full path to font file. If blank, will default to Liberation Sans font. + * @param int $angle Angle of text from 0 - 359. Defaults to 0. + * + * @return EditorInterface + * @throws \Exception + */ + public function text(&$image, $text, $size = 12, $x = 0, $y = 0, $color = null, $font = '', $angle = 0) + { + + if ($image->isAnimated()) { // Ignore animated GIF for now + return $this; + } + + $y += $size; + + $color = ($color !== null) ? $color : new Color('#000000'); + $font = ($font !== '') ? $font : Grafika::fontsDir() . DIRECTORY_SEPARATOR . 'LiberationSans-Regular.ttf'; + + list($r, $g, $b, $alpha) = $color->getRgba(); + + // Set up draw properties + $draw = new \ImagickDraw(); + // Text color + $draw->setFillColor(new \ImagickPixel("rgba($r, $g, $b, $alpha)")); + // Font properties + $draw->setFont($font); + $draw->setFontSize($size); + + // Write text + $image->getCore()->annotateImage( + $draw, + $x, + $y, + $angle, + $text + ); + + return $this; + } + + /** + * Calculate entropy based on histogram. + * + * @param array $hist Histogram returned by Image->histogram + * + * @return float|int + */ + private function _entropy($hist){ + $entropy = 0; + $hist_size = array_sum($hist['r']) + array_sum($hist['g']) + array_sum($hist['b']); + foreach($hist['r'] as $p){ + $p = $p / $hist_size; + $entropy += $p * log($p, 2); + } + foreach($hist['g'] as $p){ + $p = $p / $hist_size; + $entropy += $p * log($p, 2); + } + foreach($hist['b'] as $p){ + $p = $p / $hist_size; + $entropy += $p * log($p, 2); + } + return $entropy * -1; + } + + /** + * Crop based on entropy. + * + * @param Image $oldImage + * @param $cropW + * @param $cropH + * + * @return array + */ + private function _smartCrop($oldImage, $cropW, $cropH){ + $image = clone $oldImage; + + $this->resizeFit($image, 30, 30); + + $origW = $oldImage->getWidth(); + $origH = $oldImage->getHeight(); + $resizeW = $image->getWidth(); + $resizeH = $image->getHeight(); + + $smallCropW = round(($resizeW / $origW) * $cropW); + $smallCropH = round(($resizeH / $origH) * $cropH); + + $step = 1; + + for($y = 0; $y < $resizeH-$smallCropH; $y+=$step){ + for($x = 0; $x < $resizeW-$smallCropW; $x+=$step){ + $hist[$x.'-'.$y] = $this->_entropy($image->histogram(array(array($x, $y), array($smallCropW, $smallCropH)))); + } + if($resizeW-$smallCropW <= 0){ + $hist['0-'.$y] = $this->_entropy($image->histogram(array(array(0, 0), array($smallCropW, $smallCropH)))); + } + } + if($resizeH-$smallCropH <= 0){ + $hist['0-0'] = $this->_entropy($image->histogram(array(array(0, 0), array($smallCropW, $smallCropH)))); + } + + asort($hist); + end($hist); + $pos = key($hist); // last key + list($x, $y) = explode('-', $pos); + $x = round($x*($origW / $resizeW)); + $y = round($y*($origH / $resizeH)); + + return array($x,$y); + } + + /** + * Resize helper function. + * + * @param Image $image + * @param int $newWidth + * @param int $newHeight + * + * @return self + * @throws \Exception + */ + private function _resize(&$image, $newWidth, $newHeight) + { + + if ('GIF' == $image->getType()) { // Animated image. Eg. GIF + + $imagick = $image->getCore()->coalesceImages(); + + foreach ($imagick as $frame) { + $frame->resizeImage($newWidth, $newHeight, \Imagick::FILTER_BOX, 1, false); + $frame->setImagePage($newWidth, $newHeight, 0, 0); + } + + // Assign new image with frames + $image = new Image($imagick->deconstructImages(), $image->getImageFile(), $newWidth, $newHeight, + $image->getType()); + } else { // Single frame image. Eg. JPEG, PNG + + $image->getCore()->resizeImage($newWidth, $newHeight, \Imagick::FILTER_LANCZOS, 1, false); + // Assign new image + $image = new Image($image->getCore(), $image->getImageFile(), $newWidth, $newHeight, + $image->getType()); + } + + } + + /** + * Get image type base on file extension. + * + * @param int $imageFile File path to image. + * + * @return ImageType string Type of image. + */ + private function _getImageTypeFromFileName($imageFile) + { + $ext = strtolower((string)pathinfo($imageFile, PATHINFO_EXTENSION)); + + if ('jpg' == $ext or 'jpeg' == $ext) { + return ImageType::JPEG; + } else if ('gif' == $ext) { + return ImageType::GIF; + } else if ('png' == $ext) { + return ImageType::PNG; + } else { + return ImageType::UNKNOWN; + } + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Blur.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Blur.php new file mode 100644 index 0000000..0801044 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Blur.php @@ -0,0 +1,37 @@ +amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + $image->getCore()->blurImage(1 * $this->amount, 0.5 * $this->amount); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Brightness.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Brightness.php new file mode 100644 index 0000000..a98d89d --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Brightness.php @@ -0,0 +1,39 @@ += 0 >= 100 + + /** + * Brightness constructor. + * @param int $amount The amount of brightness to apply. >= -100 and <= -1 to darken. 0 for no change. >= 1 and <= 100 to brighten. + */ + public function __construct($amount) + { + $this->amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + $image->getCore()->modulateImage(100 + $this->amount, 100, 100); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Colorize.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Colorize.php new file mode 100644 index 0000000..5d4d11c --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Colorize.php @@ -0,0 +1,67 @@ += 0 >= 100 + /** + * @var int + */ + protected $green; // -100 >= 0 >= 100 + /** + * @var int + */ + protected $blue; // -100 >= 0 >= 100 + + /** + * Colorize constructor. + * @param int $red The amount of red colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add. + * @param int $green The amount of green colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add. + * @param int $blue The amount of blue colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add. + */ + public function __construct($red, $green, $blue) + { + $this->red = intval($red); + $this->green = intval($green); + $this->blue = intval($blue); + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + + // normalize colorize levels + $red = $this->normalizeLevel($this->red); + $green = $this->normalizeLevel($this->green); + $blue = $this->normalizeLevel($this->blue); + $qrange = $image->getCore()->getQuantumRange(); + + $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 $image; + } + + private function normalizeLevel($level) + { + if ($level > 0) { + return $level/5; + } else { + return ($level+100)/100; + } + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Contrast.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Contrast.php new file mode 100644 index 0000000..149c4a1 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Contrast.php @@ -0,0 +1,38 @@ += 0 >= 100 + + /** + * Contrast constructor. + * @param int $amount The amount of contrast to apply. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to increase. + */ + public function __construct($amount) + { + $this->amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + + $image->getCore()->sigmoidalContrastImage($this->amount > 0, $this->amount / 4, 0); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Dither.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Dither.php new file mode 100644 index 0000000..da3952a --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Dither.php @@ -0,0 +1,168 @@ +type = $type; + } + + /** + * Apply filter. + * + * @param Image $image + * + * @return Image + * @throws \Exception + */ + public function apply( $image ) { + if ( $this->type === 'ordered' ) { + return $this->ordered( $image ); + } else if ( $this->type === 'diffusion' ) { + return $this->diffusion( $image ); + } + throw new \Exception( sprintf( 'Invalid dither type "%s".', $this->type ) ); + } + + /** + * Dither using error diffusion. + * + * @param Image $image + * + * @return Image + */ + private function diffusion( $image ){ + $pixels = array(); + + // Localize vars + $width = $image->getWidth(); + $height = $image->getHeight(); + + // Loop using image1 + $pixelIterator = $image->getCore()->getPixelIterator(); + foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */ + foreach ( $rows as $x => $px ) { /* Loop through the pixels in the row (columns) */ + /** + * @var $px \ImagickPixel */ + $rgba = $px->getColor(); + + $gray = round($rgba['r'] * 0.3 + $rgba['g'] * 0.59 + $rgba['b'] * 0.11); + + if(isset($pixels[$x][$y])){ // Add errors to color if there are + $gray += $pixels[$x][$y]; + } + + if ( $gray <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess val due to adding the error + $blackOrWhite = 0; + } else { + $blackOrWhite = 255; + } + + $oldPixel = $gray; + $newPixel = $blackOrWhite; + + // Current pixel + $px->setColor("rgb($newPixel,$newPixel,$newPixel)"); + + $qError = $oldPixel - $newPixel; // Quantization error + + // Propagate error on neighbor pixels + if ( $x + 1 < $width ) { + $pixels[$x+1][$y] = (isset($pixels[$x+1][$y]) ? $pixels[$x+1][$y] : 0) + ($qError * (7 / 16)); + } + + if ( $x - 1 > 0 and $y + 1 < $height ) { + $pixels[$x-1][$y+1] = (isset($pixels[$x-1][$y+1]) ? $pixels[$x-1][$y+1] : 0) + ($qError * (3 / 16)); + } + + if ( $y + 1 < $height ) { + $pixels[$x][$y+1] = (isset($pixels[$x][$y+1]) ? $pixels[$x][$y+1] : 0) + ($qError * (5 / 16)); + } + + if ( $x + 1 < $width and $y + 1 < $height ) { + $pixels[$x+1][$y+1] = (isset($pixels[$x+1][$y+1]) ? $pixels[$x+1][$y+1] : 0) + ($qError * (1 / 16)); + } + + } + $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */ + } + + $type = $image->getType(); + $file = $image->getImageFile(); + $image = $image->getCore(); + + return new Image( $image, $file, $width, $height, $type ); // Create new image with updated core + + } + + /** + * Dither by applying a threshold map. + * + * @param Image $image + * + * @return Image + */ + private function ordered( $image ) { + + // Localize vars + $width = $image->getWidth(); + $height = $image->getHeight(); + + $thresholdMap = array( + array( 15, 135, 45, 165 ), + array( 195, 75, 225, 105 ), + array( 60, 180, 30, 150 ), + array( 240, 120, 210, 90 ) + ); + + // Loop using image1 + $pixelIterator = $image->getCore()->getPixelIterator(); + foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */ + foreach ( $rows as $x => $px ) { /* Loop through the pixels in the row (columns) */ + /** + * @var $px \ImagickPixel */ + $rgba = $px->getColor(); + + $gray = round($rgba['r'] * 0.3 + $rgba['g'] * 0.59 + $rgba['b'] * 0.11); + + $threshold = $thresholdMap[ $x % 4 ][ $y % 4 ]; + $oldPixel = ( $gray + $threshold ) / 2; + if ( $oldPixel <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess value + $newPixel = 0; + } else { + $newPixel = 255; + } + + // Current pixel + $px->setColor("rgb($newPixel,$newPixel,$newPixel)"); + + } + $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */ + } + + $type = $image->getType(); + $file = $image->getImageFile(); + $image = $image->getCore(); + + return new Image( $image, $file, $width, $height, $type ); // Create new image with updated core + + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Gamma.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Gamma.php new file mode 100644 index 0000000..bbdbae5 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Gamma.php @@ -0,0 +1,38 @@ += 1.0 + + /** + * Gamma constructor. + * @param float $amount The amount of gamma correction to apply. >= 1.0 + */ + public function __construct($amount) + { + $this->amount = (float) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + + $image->getCore()->gammaImage($this->amount); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Grayscale.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Grayscale.php new file mode 100644 index 0000000..5e1e2f4 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Grayscale.php @@ -0,0 +1,23 @@ +getCore()->modulateImage(100, 0, 100); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Invert.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Invert.php new file mode 100644 index 0000000..7e9607f --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Invert.php @@ -0,0 +1,24 @@ +getCore()->negateImage(false); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Pixelate.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Pixelate.php new file mode 100644 index 0000000..0a74614 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Pixelate.php @@ -0,0 +1,42 @@ += 1 + */ + protected $amount; + + /** + * Pixelate constructor. + * @param int $amount The size of pixelation. >= 1 + */ + public function __construct($amount) + { + $this->amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + + $size = $this->amount; + $width = $image->getWidth(); + $height = $image->getHeight(); + $image->getCore()->scaleImage(max(1, ($width / $size)), max(1, ($height / $size))); + $image->getCore()->scaleImage($width, $height); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Sharpen.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Sharpen.php new file mode 100644 index 0000000..df6ee12 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Sharpen.php @@ -0,0 +1,37 @@ += 1 to <= 100 + */ + public function __construct($amount) + { + $this->amount = (int) $amount; + } + + /** + * @param Image $image + * + * @return Image + */ + public function apply( $image ) { + $image->getCore()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0); + return $image; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Sobel.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Sobel.php new file mode 100644 index 0000000..877bc45 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Filter/Sobel.php @@ -0,0 +1,142 @@ +getWidth(); + $height = $image->getHeight(); + + // Loop + $pixelIterator = $image->getCore()->getPixelIterator(); + foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */ + foreach ( $rows as $x => $px ) { /* Loop through the pixels in the row (columns) */ + + // row 0 + if ($x > 0 and $y > 0) { + $matrix[0][0] = $this->getColor($px, $pixels, $x - 1, $y - 1); + } else { + $matrix[0][0] = $this->getColor($px, $pixels, $x, $y); + } + + if ($y > 0) { + $matrix[1][0] = $this->getColor($px, $pixels, $x, $y - 1); + } else { + $matrix[1][0] = $this->getColor($px, $pixels, $x, $y); + } + + if ($x + 1 < $width and $y > 0) { + $matrix[2][0] = $this->getColor($px, $pixels, $x + 1, $y - 1); + } else { + $matrix[2][0] = $this->getColor($px, $pixels, $x, $y); + } + + // row 1 + if ($x > 0) { + $matrix[0][1] = $this->getColor($px, $pixels, $x - 1, $y); + } else { + $matrix[0][1] = $this->getColor($px, $pixels, $x, $y); + } + + if ($x + 1 < $width) { + $matrix[2][1] = $this->getColor($px, $pixels, $x + 1, $y); + } else { + $matrix[2][1] = $this->getColor($px, $pixels, $x, $y); + } + + // row 1 + if ($x > 0 and $y + 1 < $height) { + $matrix[0][2] = $this->getColor($px, $pixels, $x - 1, $y + 1); + } else { + $matrix[0][2] = $this->getColor($px, $pixels, $x, $y); + } + + if ($y + 1 < $height) { + $matrix[1][2] = $this->getColor($px, $pixels, $x, $y + 1); + } else { + $matrix[1][2] = $this->getColor($px, $pixels, $x, $y); + } + + if ($x + 1 < $width and $y + 1 < $height) { + $matrix[2][2] = $this->getColor($px, $pixels, $x + 1, $y + 1); + } else { + $matrix[2][2] = $this->getColor($px, $pixels, $x, $y); + } + + $edge = $this->convolve($matrix); + $edge = intval($edge / 2); + if ($edge > 255) { + $edge = 255; + } + + /** + * @var \ImagickPixel $px Current pixel. + */ + $finalPx[] = $edge; // R + $finalPx[] = $edge; // G + $finalPx[] = $edge; // B + + } + $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */ + } + + $new = new \Imagick(); + $new->newImage($width, $height, new \ImagickPixel('black')); + /* Import the pixels into image. + width * height * strlen("RGB") must match count($pixels) */ + $new->importImagePixels(0, 0, $width, $height, "RGB", \Imagick::PIXEL_CHAR, $finalPx); + + $type = $image->getType(); + $file = $image->getImageFile(); + + return new Image( $new, $file, $width, $height, $type ); // Create new image with updated core + + } + + private function convolve($matrix) + { + $gx = $matrix[0][0] + ($matrix[2][0] * -1) + + ($matrix[0][1] * 2) + ($matrix[2][1] * -2) + + $matrix[0][2] + ($matrix[2][2] * -1); + + $gy = $matrix[0][0] + ($matrix[1][0] * 2) + $matrix[2][0] + + ($matrix[0][2] * -1) + ($matrix[1][2] * -2) + ($matrix[2][2] * -1); + + return sqrt(($gx * $gx) + ($gy * $gy)); + } + + /** + * @param \ImagickPixel $px + * @param array $pixels + * @param int $x + * @param int $y + * + * @return float + */ + private function getColor($px, &$pixels, $x, $y) + { + if (isset($pixels[$x][$y])) { + return $pixels[$x][$y]; + } + $rgba = $px->getColor(); + return $pixels[$x][$y] = round($rgba['r'] * 0.3 + $rgba['g'] * 0.59 + $rgba['b'] * 0.11); // gray + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/Image.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Image.php new file mode 100644 index 0000000..8055f7b --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/Image.php @@ -0,0 +1,269 @@ +imagick = $imagick; + $this->imageFile = $imageFile; + $this->width = $width; + $this->height = $height; + $this->type = $type; + $this->animated = $animated; + } + + public function __clone() + { + $copy = clone $this->imagick; + + $this->imagick = $copy; + } + + /** + * Output a binary raw dump of an image in a specified format. + * + * @param string|ImageType $type Image format of the dump. + * + * @throws \Exception When unsupported type. + */ + public function blob( $type = 'PNG' ) { + $this->imagick->setImageFormat($type); + echo $this->imagick->getImageBlob(); + } + + /** + * @param $imageFile + * + * @return Image + * @throws \Exception + */ + public static function createFromFile( $imageFile ){ + $imageFile = realpath( $imageFile ); + + if ( ! file_exists( $imageFile ) ) { + throw new \Exception( sprintf('Could not open image file "%s"', $imageFile) ); + } + + $imagick = new \Imagick( realpath($imageFile) ); + $animated = false; + if ($imagick->getImageIterations() > 0) { + $animated = true; + } + + return new self( + $imagick, + $imageFile, + $imagick->getImageWidth(), + $imagick->getImageHeight(), + $imagick->getImageFormat(), + $animated + ); + } + + /** + * Create an Image from an instance of Imagick. + * + * @param \Imagick $imagick Instance of Imagick. + * + * @return Image + */ + public static function createFromCore( $imagick ) { + return new self( $imagick, '', $imagick->getImageWidth(), $imagick->getImageHeight(), $imagick->getImageFormat() ); + } + + /** + * Create a blank image. + * + * @param int $width Width in pixels. + * @param int $height Height in pixels. + * + * @return self + */ + public static function createBlank($width = 1, $height = 1){ + $imagick = new \Imagick(); + $imagick->newImage($width, $height, new \ImagickPixel('black')); + $imagick->setImageFormat('png'); // Default to PNG. + + return new self( $imagick, '', $imagick->getImageWidth(), $imagick->getImageHeight(), $imagick->getImageFormat()); + + } + + /** + * Get Imagick instance + * + * @return \Imagick + */ + public function getCore() { + return $this->imagick; + } + + /** + * Get image file path. + * + * @return string File path to image. + */ + public function getImageFile() { + return $this->imageFile; + } + + /** + * Get image width in pixels. + * + * @return int + */ + public function getWidth() { + return $this->width; + } + + /** + * Get image height in pixels. + * + * @return int + */ + public function getHeight() { + return $this->height; + } + + /** + * Get image type. + * + * @return string + */ + public function getType() { + return $this->type; + } + + /** + * Get histogram from an entire image or its sub-region. + * + * @param array|null $slice Array of slice information. array( array( 0,0), array(100,50)) means x,y is 0,0 and width,height is 100,50 + * + * @return array Returns array containing RGBA bins array('r'=>array(), 'g'=>array(), 'b'=>array(), 'a'=>array()) + */ + public function histogram($slice = null) + { + + if(null === $slice){ + $sliceX = 0; + $sliceY = 0; + $sliceW = $this->getWidth(); + $sliceH = $this->getHeight(); + } else { + $sliceX = $slice[0][0]; + $sliceY = $slice[0][1]; + $sliceW = $slice[1][0]; + $sliceH = $slice[1][1]; + } + + $rBin = array(); + $gBin = array(); + $bBin = array(); + $aBin = array(); + + // Loop using image + $pixelIterator = $this->getCore()->getPixelIterator(); + foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */ + if($y >= $sliceY and $y < $sliceY+$sliceH) { + foreach ($rows as $x => $px) { /* Loop through the pixels in the row (columns) */ + if($x >= $sliceX and $x < $sliceX+$sliceW) { + /** + * @var $px \ImagickPixel */ + $pixel = $px->getColor(); + $r = $pixel['r']; + $g = $pixel['g']; + $b = $pixel['b']; + $a = $pixel['a']; + + if ( ! isset($rBin[$r])) { + $rBin[$r] = 1; + } else { + $rBin[$r]++; + } + + if ( ! isset($gBin[$g])) { + $gBin[$g] = 1; + } else { + $gBin[$g]++; + } + + if ( ! isset($bBin[$b])) { + $bBin[$b] = 1; + } else { + $bBin[$b]++; + } + + if ( ! isset($aBin[$a])) { + $aBin[$a] = 1; + } else { + $aBin[$a]++; + } + } + } + } + } + return array( + 'r' => $rBin, + 'g' => $gBin, + 'b' => $bBin, + 'a' => $aBin + ); + } + + /** + * Returns animated flag. + * + * @return bool True if animated GIF. + */ + public function isAnimated() { + return $this->animated; + } + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Imagick/ImageHash/AverageHash.php b/source/vendor/kosinix/grafika/src/Grafika/Imagick/ImageHash/AverageHash.php new file mode 100644 index 0000000..8a490cd --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Imagick/ImageHash/AverageHash.php @@ -0,0 +1,37 @@ +resizeExact($image, $width, $height); // Resize to exactly 9x8 + $imagick = $image->getCore(); + + // Build hash + $hash = ''; + for ($y = 0; $y < $height; $y++) { + // Get the pixel value for the leftmost pixel. + $rgba = $imagick->getImagePixelColor(0, $y)->getColor(); + + $left = floor(($rgba['r'] + $rgba['g'] + $rgba['b']) / 3); + for ($x = 1; $x < $width; $x++) { + // Get the pixel value for each pixel starting from position 1. + $rgba = $imagick->getImagePixelColor($x, $y)->getColor(); + $right = floor(($rgba['r'] + $rgba['g'] + $rgba['b']) / 3); + // Each hash bit is set based on whether the left pixel is brighter than the right pixel. + if ($left > $right) { + $hash .= '1'; + } else { + $hash .= '0'; + } + // Prepare the next loop. + $left = $right; + } + } + $editor->free( $image ); + return $hash; + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/Grafika/Position.php b/source/vendor/kosinix/grafika/src/Grafika/Position.php new file mode 100644 index 0000000..bd09f40 --- /dev/null +++ b/source/vendor/kosinix/grafika/src/Grafika/Position.php @@ -0,0 +1,146 @@ +position = $position; + $this->offsetX = $offsetX; + $this->offsetY = $offsetY; + } + + /** + * Translate the textual position + offsets into x,y values. + * + * @param int $canvasWidth Width of canvas. + * @param int $canvasHeight Height of canvas. + * @param int $imageWidth Width of image/object added. + * @param int $imageHeight Height of image/object added. + * + * @return array Array of X and Y coordinates: array($x, $y). + * @throws \Exception When invalid position. + */ + public function getXY($canvasWidth, $canvasHeight, $imageWidth, $imageHeight){ + if ( self::TOP_LEFT === $this->position) { + $x = 0; + $y = 0; + } else if ( self::TOP_CENTER === $this->position) { + $x = (int)round(($canvasWidth / 2) - ($imageWidth / 2)); + $y = 0; + } else if ( self::TOP_RIGHT === $this->position) { + $x = $canvasWidth - $imageWidth; + $y = 0; + } else if ( self::CENTER_LEFT === $this->position) { + $x = 0; + $y = (int)round(($canvasHeight / 2) - ($imageHeight / 2)); + } else if ( self::CENTER_RIGHT === $this->position) { + $x = $canvasWidth - $imageWidth; + $y = (int)round(($canvasHeight / 2) - ($imageHeight / 2)); + } else if ( self::BOTTOM_LEFT === $this->position) { + $x = 0; + $y = $canvasHeight - $imageHeight; + } else if ( self::BOTTOM_CENTER === $this->position) { + $x = (int)round(($canvasWidth / 2) - ($imageWidth / 2)); + $y = $canvasHeight - $imageHeight; + } else if ( self::BOTTOM_RIGHT === $this->position) { + $x = $canvasWidth - $imageWidth; + $y = $canvasHeight - $imageHeight; + } else if ( self::CENTER === $this->position) { + $x = (int)round(($canvasWidth / 2) - ($imageWidth / 2)); + $y = (int)round(($canvasHeight / 2) - ($imageHeight / 2)); + } else { + throw new \Exception( sprintf( 'Invalid position "%s".', $this->position ) ); + } + + return array( + $x + $this->offsetX, + $y + $this->offsetY + ); + } + + /** + * @return string + */ + public function getText() { + return $this->position; + } + + /** + * @return int + */ + public function getOffsetY() { + return $this->offsetY; + } + + /** + * @return int + */ + public function getOffsetX() { + return $this->offsetX; + } + + +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/src/autoloader.php b/source/vendor/kosinix/grafika/src/autoloader.php new file mode 100644 index 0000000..4d46a7d --- /dev/null +++ b/source/vendor/kosinix/grafika/src/autoloader.php @@ -0,0 +1,10 @@ +assertTrue($editor instanceof EditorInterface); + + return $editor; + } + + public function testCreateEditorStatic() + { + + Grafika::setEditorList(array('Gd')); // Use GD only + + $editor = Grafika::createEditor(); + + $this->assertTrue($editor instanceof EditorInterface); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenFail(EditorInterface $editor) + { + if (version_compare(PHP_VERSION, '5.6', '>=')) { + $this->expectException('\Exception'); + + $input = 'unreachable.jpg'; // Non existent file + + Grafika::createImage($input); + } + + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testUnknownTypeFail($editor) + { + if (version_compare(PHP_VERSION, '5.6', '>=')) { + $this->expectException('\Exception'); + + $input = DIR_TEST_IMG . '/unsupported.bmp'; + + Grafika::createImage($input); + } + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenJpeg($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $image = Grafika::createImage($input); + + $this->assertTrue($image instanceof Image); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenPng($editor) + { + + $input = DIR_TEST_IMG . '/sample.png'; + $image = Grafika::createImage($input); + + $this->assertTrue($image instanceof Image); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenGif($editor) + { + + $input = DIR_TEST_IMG . '/sample.gif'; + $image = Grafika::createImage($input); + + $this->assertTrue($image instanceof Image); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenWbmp($editor) + { + + $input = DIR_TEST_IMG . '/sample.wbm'; + $image = Grafika::createImage($input); + + $this->assertTrue($image instanceof Image); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + * @return EditorInterface + */ + public function testEqual($editor) + { + $input1 = $this->dirAssert . '/testEqual.jpg'; + + $this->assertTrue($editor->equal($input1, $input1)); + + return $editor; + } + + /** + * @depends testEqual + * @param EditorInterface $editor + * @return EditorInterface + */ + public function testEqualFalse($editor) + { + $input1 = $this->dirAssert . '/testEqual.jpg'; + $input2 = $this->dirAssert . '/testEqualFalse.png'; + + $this->assertFalse($editor->equal($input1, $input2)); + + return $editor; + } + + /** + * Test similarity + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testCompare($editor) + { + + $input = DIR_TEST_IMG . '/lena.png'; + $input2 = DIR_TEST_IMG . '/lena-gray.png'; + + $ham = $editor->compare($input,$input2); + + $this->assertLessThan(10,$ham); // hamming distance: 0 is equal, 1-10 is similar, 11+ is different image + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testSave($editor){ + $input = DIR_TEST_IMG . '/sample.png'; + $output1 = DIR_TMP . '/' . __FUNCTION__ . '1.jpg'; + $output2 = DIR_TMP . '/' . __FUNCTION__ . '2.jpg'; + $output3 = DIR_TMP . '/' . __FUNCTION__ . '3.png'; + + $image = Grafika::createImage($input); + $editor->save($image, $output1, 'jpg', 100); + $this->assertEquals(0, $editor->compare($input, $output1)); + + $editor->save($image, $output2, 'jpg', 0); + $this->assertGreaterThan(0, $editor->compare($input, $output2)); // Not exactly similar due to compression + + $editor->save($image, $output3, 'png', null); + $this->assertEquals(0, $editor->compare($input, $output3)); + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testAddTextOnBlankImage($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $blank = Grafika::createBlankImage( 400, 100 ); + $editor->fill( $blank, new Color( '#ffffff' ) ); + $editor->text( $blank, 'Lorem ipsum - Liberation Sans'); + $editor->save( $blank, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testAddTextOnJpeg($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFit($image, 300, 300); + $editor->text($image, 'Lorem ipsum - Liberation Sans'); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * Test enlarging an image to a dimension larger than its original size. + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeFitEnlarge($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFit($image, $image->getWidth() + 100, $image->getHeight() + 100); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + // Animated gif + $input = DIR_TEST_IMG . '/sample.gif'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.gif'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.gif'; + + $image = Grafika::createImage($input); + $editor->resizeFit($image, $image->getWidth() + 100, $image->getHeight() + 100); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeFitPortrait($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFit($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExact($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExact($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + // Animated gif + $input = DIR_TEST_IMG . '/sample.gif'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.gif'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.gif'; + + $image = Grafika::createImage($input); + $editor->resizeExact($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactPortrait($editor) + { + + $input = DIR_TEST_IMG . '/portrait.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExact($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeFill($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFill($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeFillPortrait($editor) + { + + $input = DIR_TEST_IMG . '/portrait.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFill($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactWidth($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExactWidth($image, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactWidthPortrait($editor) + { + + $input = DIR_TEST_IMG . '/portrait.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExactWidth($image, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactHeight($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExactHeight($image, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactHeightPortrait($editor) + { + + $input = DIR_TEST_IMG . '/portrait.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExactHeight($image, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testRotate($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->rotate($image, 45); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testCubicBezier($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createBlankImage( 277, 277 ); + $editor->fill( $image, new Color( '#FFFFFF' ) ); + + $obj = Grafika::createDrawingObject('CubicBezier', array(42, 230), array(230, 237), array(42, 45), array(230, 43), new Color('#000000')); + $editor->draw($image, $obj); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testEllipse($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.png'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.png'; + + $image = Grafika::createBlankImage( 200, 200 ); + $editor->fill( $image, new Color( '#FFFFFF' ) ); + + $obj = Grafika::createDrawingObject( 'Ellipse', 100, 50, array( 50, 75 ), 1, new Color( '#000000' ), new Color( '#FF0000' ) ); + $editor->draw($image, $obj); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testLines($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createBlankImage( 200, 200 ); + $editor->fill( $image, new Color( '#FFFFFF' ) ); + + $editor->draw( $image, Grafika::createDrawingObject('Line', array(0, 0), array(200, 200), 1, new Color('#FF0000'))); + $editor->draw( $image, Grafika::createDrawingObject('Line', array(0, 200), array(200, 0), 1, new Color('#00FF00'))); + $editor->draw( $image, Grafika::createDrawingObject('Line', array(0, 0), array(200, 100), 1, new Color('#0000FF'))); + $editor->draw( $image, Grafika::createDrawingObject('Line', array(0, 100), array(200, 100))); + $editor->draw( $image, Grafika::createDrawingObject('Line', array(100, 0), array(100, 200))); + + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testQuadraticBezier($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createBlankImage( 277, 277 ); + $editor->fill( $image, new Color( '#EEEEEE' ) ); + + $obj = Grafika::createDrawingObject('QuadraticBezier', array(70, 250), array(20, 110), array(220, 60), new Color('#FF0000')); + $editor->draw( $image, $obj); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testRectangle($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createBlankImage( 200, 200 ); + $editor->fill( $image, new Color( '#CCCCCC' ) ); + + $editor->draw( $image, Grafika::createDrawingObject('Rectangle', 85, 50)); // A 85x50 no filled rectangle with a black 1px border on location 0,0. + $editor->draw( $image, Grafika::createDrawingObject('Rectangle', 85, 50, array(105, 10), 0, null, new Color('#FF0000'))); // A 85x50 red rectangle with no border. + $editor->draw( $image, Grafika::createDrawingObject('Rectangle', 85, 50, array(105, 70), 0, null, new Color('#00FF00'))); // A 85x50 green rectangle with no border. + $editor->draw( $image, Grafika::createDrawingObject('Rectangle', 85, 50, array(0, 60), 1, '#000000', null)); // No fill rectangle + + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testPolygon($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.png'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.png'; + + $image = Grafika::createBlankImage( 200, 200 ); + $editor->fill( $image, new Color( '#FFFFFF' ) ); + + $editor->draw( $image, Grafika::createDrawingObject('Polygon', array(array(0,0), array(50,0), array(0,50)), 1)); + $editor->draw( $image, Grafika::createDrawingObject('Polygon', array(array(200-1,0), array(150-1,0), array(200-1,50)), 1)); + $editor->draw( $image, Grafika::createDrawingObject('Polygon', array(array(100,0), array(140,50), array(100,100), array(60,50)), 1, null, new Color('#FF0000'))); + + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testDither($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . 'Diffusion.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . 'Diffusion.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Dither', 'diffusion') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . 'Ordered.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . 'Ordered.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Dither', 'ordered') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testSobel($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Sobel') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testBlur($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Blur', 10) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testBrightness($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, new Brightness(50) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testColorize($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, new Colorize(-50, -50, -50) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testContrast($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Contrast', 50) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testGamma($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Gamma', 2.0) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testGrayscale($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Grayscale') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + // Test on animated GIF + $input = DIR_TEST_IMG . '/sample.gif'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.gif'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Grayscale') ); + $editor->save( $image, $output); + + $this->assertTrue($image->isAnimated()); // It should still be animated GIF + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testInvert($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Invert') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testPixelate($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Pixelate',10) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testSharpen($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Sharpen', 50) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testCrop($editor) + { + $input = DIR_TEST_IMG . '/crop-test.jpg'; + + $output1 = DIR_TMP . '/' . __FUNCTION__ . '1.jpg'; + $correct1 = $this->dirAssert . '/' . __FUNCTION__ . '1.jpg'; + + $output2 = DIR_TMP . '/' . __FUNCTION__ . '2.jpg'; + $correct2 = $this->dirAssert . '/' . __FUNCTION__ . '2.jpg'; + + $output3 = DIR_TMP . '/' . __FUNCTION__ . '3.jpg'; + $correct3 = $this->dirAssert . '/' . __FUNCTION__ . '3.jpg'; + + $output4 = DIR_TMP . '/' . __FUNCTION__ . '4.jpg'; + $correct4 = $this->dirAssert . '/' . __FUNCTION__ . '4.jpg'; + + $output5 = DIR_TMP . '/' . __FUNCTION__ . '5.jpg'; + $correct5 = $this->dirAssert . '/' . __FUNCTION__ . '5.jpg'; + + $output6 = DIR_TMP . '/' . __FUNCTION__ . '6.jpg'; + $correct6 = $this->dirAssert . '/' . __FUNCTION__ . '6.jpg'; + + $output7 = DIR_TMP . '/' . __FUNCTION__ . '7.jpg'; + $correct7 = $this->dirAssert . '/' . __FUNCTION__ . '7.jpg'; + + $output8 = DIR_TMP . '/' . __FUNCTION__ . '8.jpg'; + $correct8 = $this->dirAssert . '/' . __FUNCTION__ . '8.jpg'; + + $output9 = DIR_TMP . '/' . __FUNCTION__ . '9.jpg'; + $correct9 = $this->dirAssert . '/' . __FUNCTION__ . '9.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'top-left' ); + $editor->save( $image, $output1); + + $this->assertLessThanOrEqual(5, $editor->compare($output1, $correct1)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'top-center' ); + $editor->save( $image, $output2); + $this->assertLessThanOrEqual(5, $editor->compare($output2, $correct2)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'top-right' ); + $editor->save( $image, $output3); + $this->assertLessThanOrEqual(5, $editor->compare($output3, $correct3)); + + // + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'center-left' ); + $editor->save( $image, $output4); + $this->assertLessThanOrEqual(5, $editor->compare($output4, $correct4)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'center' ); + $editor->save( $image, $output5); + $this->assertLessThanOrEqual(5, $editor->compare($output5, $correct5)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'center-right' ); + $editor->save( $image, $output6); + $this->assertLessThanOrEqual(5, $editor->compare($output6, $correct6)); + // + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'bottom-left' ); + $editor->save( $image, $output7); + $this->assertLessThanOrEqual(5, $editor->compare($output7, $correct7)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'bottom-center' ); + $editor->save( $image, $output8); + $this->assertLessThanOrEqual(5, $editor->compare($output8, $correct8)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'bottom-right' ); + $editor->save( $image, $output9); + $this->assertLessThanOrEqual(5, $editor->compare($output9, $correct9)); + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testSmartCrop($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '1.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '1.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 250, 250, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + + $input = DIR_TEST_IMG . '/tower.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '2.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '2.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 400, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + + $input = DIR_TEST_IMG . '/portal-companion-cube.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '3.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '3.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 200, 200, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '4.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '4.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 200, 200, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + + $input = DIR_TEST_IMG . '/sample.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '5.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '5.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 200, 200, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testFlattenAnimatedGif($editor) + { + + // Animated gif + $input = DIR_TEST_IMG . '/sample.gif'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.gif'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.gif'; + + $image = Grafika::createImage( $input ); + $editor->flatten( $image ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testFlip($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . 'H.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . 'H.jpg'; + + $image = Grafika::createImage( $input ); + $editor->flip( $image, 'h' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + $output = DIR_TMP . '/' . __FUNCTION__ . 'V.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . 'V.jpg'; + + $image = Grafika::createImage( $input ); + $editor->flip( $image, 'v' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testBlend($editor) + { + $input1 = DIR_TEST_IMG . '/lena.png'; + $input2 = DIR_TEST_IMG . '/blend.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image1 = Grafika::createImage( $input1 ); + $image2 = Grafika::createImage( $input2 ); + $editor->blend( $image1, $image2, 'overlay', 0.5, 'center' ); // overlay blend, opacity 50%, center position + $editor->save( $image1, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + // On before every test + protected function setUp() + { + $editor = new Editor(); + if (false === $editor->isAvailable()) { + $this->markTestSkipped( + 'PHP GD is not available.' + ); + } + } + + // After every test + protected function tearDown() + { + if (CLEAN_DUMP) { + deleteTmpDirectory(); // Delete images created by a test + } + } + + + public static function setUpBeforeClass() + { + + } + + public static function tearDownAfterClass() + { + + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/tests/ImagickEditorTest.php b/source/vendor/kosinix/grafika/tests/ImagickEditorTest.php new file mode 100644 index 0000000..bbe5d7f --- /dev/null +++ b/source/vendor/kosinix/grafika/tests/ImagickEditorTest.php @@ -0,0 +1,1027 @@ +assertTrue($editor instanceof EditorInterface); + + return $editor; + } + + public function testCreateEditorStatic() + { + + Grafika::setEditorList(array('Imagick')); // Use Imagick only + + $editor = Grafika::createEditor(); + + $this->assertTrue($editor instanceof EditorInterface); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenFail(EditorInterface $editor) + { + if (version_compare(PHP_VERSION, '5.6', '>=')) { + $this->expectException('\Exception'); + + $input = 'unreachable.jpg'; // Non existent file + + Grafika::createImage($input); + } + + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testUnknownTypeFail($editor) + { + if (version_compare(PHP_VERSION, '5.6', '>=')) { + $this->expectException('\Exception'); + + $input = DIR_TEST_IMG . '/sample.wbm'; + + Grafika::createImage($input); + } + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenJpeg($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $image = Grafika::createImage($input); + + $this->assertTrue($image instanceof Image); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenPng($editor) + { + + $input = DIR_TEST_IMG . '/sample.png'; + $image = Grafika::createImage($input); + + $this->assertTrue($image instanceof Image); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testOpenGif($editor) + { + + $input = DIR_TEST_IMG . '/sample.gif'; + $image = Grafika::createImage($input); + + $this->assertTrue($image instanceof Image); + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + * @return EditorInterface + */ + public function testEqual($editor) + { + $input1 = $this->dirAssert . '/testEqual.jpg'; + + $this->assertTrue($editor->equal($input1, $input1)); + + return $editor; + } + + /** + * @depends testEqual + * @param EditorInterface $editor + * @return EditorInterface + */ + public function testEqualFalse($editor) + { + $input1 = $this->dirAssert . '/testEqual.jpg'; + $input2 = $this->dirAssert . '/testEqualFalse.png'; + + $this->assertFalse($editor->equal($input1, $input2)); + + return $editor; + } + + /** + * Test similarity + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testCompare($editor) + { + + $input = DIR_TEST_IMG . '/lena.png'; + $input2 = DIR_TEST_IMG . '/lena-gray.png'; + + $ham = $editor->compare($input,$input2); + + $this->assertLessThan(10,$ham); // hamming distance: 0 is equal, 1-10 is similar, 11+ is different image + } + + /** + * @depends testCreateEditor + * @param EditorInterface $editor + */ + public function testSave($editor){ + $input = DIR_TEST_IMG . '/sample.png'; + $output1 = DIR_TMP . '/' . __FUNCTION__ . '1.jpg'; + $output2 = DIR_TMP . '/' . __FUNCTION__ . '2.jpg'; + $output3 = DIR_TMP . '/' . __FUNCTION__ . '3.png'; + + $image = Grafika::createImage($input); + $editor->save($image, $output1, 'jpg', 100); + $this->assertEquals(0, $editor->compare($input, $output1)); + + $editor->save($image, $output2, 'jpg', 0); + $this->assertGreaterThan(0, $editor->compare($input, $output2)); // Not exactly similar due to compression + + $editor->save($image, $output3, 'png', null); + $this->assertEquals(0, $editor->compare($input, $output3)); + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testAddTextOnBlankImage($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $blank = Grafika::createBlankImage( 400, 100 ); + $editor->fill( $blank, new Color( '#ffffff' ) ); + $editor->text( $blank, 'Lorem ipsum - Liberation Sans'); + $editor->save( $blank, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testAddTextOnJpeg($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFit($image, 300, 300); + $editor->text($image, 'Lorem ipsum - Liberation Sans'); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * Test enlarging an image to a dimension larger than its original size. + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeFitEnlarge($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFit($image, $image->getWidth() + 100, $image->getHeight() + 100); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + // Animated gif + $input = DIR_TEST_IMG . '/sample.gif'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.gif'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.gif'; + + $image = Grafika::createImage($input); + $editor->resizeFit($image, $image->getWidth() + 100, $image->getHeight() + 100); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeFitPortrait($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFit($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExact($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExact($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + // Animated gif + $input = DIR_TEST_IMG . '/sample.gif'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.gif'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.gif'; + + $image = Grafika::createImage($input); + $editor->resizeExact($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactPortrait($editor) + { + + $input = DIR_TEST_IMG . '/portrait.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExact($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeFill($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFill($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeFillPortrait($editor) + { + + $input = DIR_TEST_IMG . '/portrait.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeFill($image, 200, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactWidth($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExactWidth($image, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactWidthPortrait($editor) + { + + $input = DIR_TEST_IMG . '/portrait.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExactWidth($image, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactHeight($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExactHeight($image, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testResizeExactHeightPortrait($editor) + { + + $input = DIR_TEST_IMG . '/portrait.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->resizeExactHeight($image, 200); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testRotate($editor) + { + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage($input); + $editor->rotate($image, 45); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testCubicBezier($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createBlankImage( 277, 277 ); + $editor->fill( $image, new Color( '#FFFFFF' ) ); + + $obj = Grafika::createDrawingObject('CubicBezier', array(42, 230), array(230, 237), array(42, 45), array(230, 43), new Color('#000000')); + $editor->draw($image, $obj); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testEllipse($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.png'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.png'; + + $image = Grafika::createBlankImage( 200, 200 ); + $editor->fill( $image, new Color( '#FFFFFF' ) ); + + $obj = Grafika::createDrawingObject( 'Ellipse', 100, 50, array( 50, 75 ), 1, new Color( '#000000' ), new Color( '#FF0000' ) ); + $editor->draw($image, $obj); + $editor->save($image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testLines($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createBlankImage( 200, 200 ); + $editor->fill( $image, new Color( '#FFFFFF' ) ); + + $editor->draw( $image, Grafika::createDrawingObject('Line', array(0, 0), array(200, 200), 1, new Color('#FF0000'))); + $editor->draw( $image, Grafika::createDrawingObject('Line', array(0, 200), array(200, 0), 1, new Color('#00FF00'))); + $editor->draw( $image, Grafika::createDrawingObject('Line', array(0, 0), array(200, 100), 1, new Color('#0000FF'))); + $editor->draw( $image, Grafika::createDrawingObject('Line', array(0, 100), array(200, 100))); + $editor->draw( $image, Grafika::createDrawingObject('Line', array(100, 0), array(100, 200))); + + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testQuadraticBezier($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createBlankImage( 277, 277 ); + $editor->fill( $image, new Color( '#EEEEEE' ) ); + + $obj = Grafika::createDrawingObject('QuadraticBezier', array(70, 250), array(20, 110), array(220, 60), new Color('#FF0000')); + $editor->draw( $image, $obj); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testRectangle($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createBlankImage( 200, 200 ); + $editor->fill( $image, new Color( '#CCCCCC' ) ); + + $editor->draw( $image, Grafika::createDrawingObject('Rectangle', 85, 50)); // A 85x50 no filled rectangle with a black 1px border on location 0,0. + $editor->draw( $image, Grafika::createDrawingObject('Rectangle', 85, 50, array(105, 10), 0, null, new Color('#FF0000'))); // A 85x50 red rectangle with no border. + $editor->draw( $image, Grafika::createDrawingObject('Rectangle', 85, 50, array(105, 70), 0, null, new Color('#00FF00'))); // A 85x50 green rectangle with no border. + $editor->draw( $image, Grafika::createDrawingObject('Rectangle', 85, 50, array(0, 60), 1, '#000000', null)); // No fill rectangle + + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testPolygon($editor) + { + + $output = DIR_TMP . '/' . __FUNCTION__ . '.png'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.png'; + + $image = Grafika::createBlankImage( 200, 200 ); + $editor->fill( $image, new Color( '#FFFFFF' ) ); + + $editor->draw( $image, Grafika::createDrawingObject('Polygon', array(array(0,0), array(50,0), array(0,50)), 1)); + $editor->draw( $image, Grafika::createDrawingObject('Polygon', array(array(200-1,0), array(150-1,0), array(200-1,50)), 1)); + $editor->draw( $image, Grafika::createDrawingObject('Polygon', array(array(100,0), array(140,50), array(100,100), array(60,50)), 1, null, new Color('#FF0000'))); + + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testDither($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . 'Diffusion.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . 'Diffusion.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Dither', 'diffusion') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . 'Ordered.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . 'Ordered.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Dither', 'ordered') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + * TODO: Check why this is failing on travis + */ + public function testSobel($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Sobel') ); + $editor->save( $image, $output); + + //$this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testBlur($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Blur', 10) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testBrightness($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, new Brightness(50) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testColorize($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, new Colorize(-50, -50, -50) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testContrast($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Contrast', 50) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testGamma($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Gamma', 2.0) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testGrayscale($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Grayscale') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + // Test on animated GIF + $input = DIR_TEST_IMG . '/sample.gif'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.gif'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Grayscale') ); + $editor->save( $image, $output); + + $this->assertTrue($image->isAnimated()); // It should still be animated GIF + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testInvert($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Invert') ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testPixelate($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Pixelate',10) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testSharpen($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image = Grafika::createImage( $input ); + $editor->apply( $image, Grafika::createFilter('Sharpen', 50) ); + $editor->save( $image, $output); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testCrop($editor) + { + $input = DIR_TEST_IMG . '/crop-test.jpg'; + + $output1 = DIR_TMP . '/' . __FUNCTION__ . '1.jpg'; + $correct1 = $this->dirAssert . '/' . __FUNCTION__ . '1.jpg'; + + $output2 = DIR_TMP . '/' . __FUNCTION__ . '2.jpg'; + $correct2 = $this->dirAssert . '/' . __FUNCTION__ . '2.jpg'; + + $output3 = DIR_TMP . '/' . __FUNCTION__ . '3.jpg'; + $correct3 = $this->dirAssert . '/' . __FUNCTION__ . '3.jpg'; + + $output4 = DIR_TMP . '/' . __FUNCTION__ . '4.jpg'; + $correct4 = $this->dirAssert . '/' . __FUNCTION__ . '4.jpg'; + + $output5 = DIR_TMP . '/' . __FUNCTION__ . '5.jpg'; + $correct5 = $this->dirAssert . '/' . __FUNCTION__ . '5.jpg'; + + $output6 = DIR_TMP . '/' . __FUNCTION__ . '6.jpg'; + $correct6 = $this->dirAssert . '/' . __FUNCTION__ . '6.jpg'; + + $output7 = DIR_TMP . '/' . __FUNCTION__ . '7.jpg'; + $correct7 = $this->dirAssert . '/' . __FUNCTION__ . '7.jpg'; + + $output8 = DIR_TMP . '/' . __FUNCTION__ . '8.jpg'; + $correct8 = $this->dirAssert . '/' . __FUNCTION__ . '8.jpg'; + + $output9 = DIR_TMP . '/' . __FUNCTION__ . '9.jpg'; + $correct9 = $this->dirAssert . '/' . __FUNCTION__ . '9.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'top-left' ); + $editor->save( $image, $output1); + + $this->assertLessThanOrEqual(5, $editor->compare($output1, $correct1)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'top-center' ); + $editor->save( $image, $output2); + $this->assertLessThanOrEqual(5, $editor->compare($output2, $correct2)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'top-right' ); + $editor->save( $image, $output3); + $this->assertLessThanOrEqual(5, $editor->compare($output3, $correct3)); + + // + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'center-left' ); + $editor->save( $image, $output4); + $this->assertLessThanOrEqual(5, $editor->compare($output4, $correct4)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'center' ); + $editor->save( $image, $output5); + $this->assertLessThanOrEqual(5, $editor->compare($output5, $correct5)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'center-right' ); + $editor->save( $image, $output6); + $this->assertLessThanOrEqual(5, $editor->compare($output6, $correct6)); + // + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'bottom-left' ); + $editor->save( $image, $output7); + $this->assertLessThanOrEqual(5, $editor->compare($output7, $correct7)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'bottom-center' ); + $editor->save( $image, $output8); + $this->assertLessThanOrEqual(5, $editor->compare($output8, $correct8)); + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 150, 'bottom-right' ); + $editor->save( $image, $output9); + $this->assertLessThanOrEqual(5, $editor->compare($output9, $correct9)); + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testSmartCrop($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '1.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '1.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 250, 250, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + + $input = DIR_TEST_IMG . '/tower.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '2.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '2.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 260, 400, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + + $input = DIR_TEST_IMG . '/portal-companion-cube.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '3.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '3.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 200, 200, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + + $input = DIR_TEST_IMG . '/sample.jpg'; + $output = DIR_TMP . '/' . __FUNCTION__ . '4.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '4.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 200, 200, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + + $input = DIR_TEST_IMG . '/sample.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '5.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '5.jpg'; + + $image = Grafika::createImage( $input ); + $editor->crop( $image, 200, 200, 'smart' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($correct, $output)); // Account for minor variations due to different GD versions (GD image that gen. asserts is different on the testing site) + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testFlattenAnimatedGif($editor) + { + + // Animated gif + $input = DIR_TEST_IMG . '/sample.gif'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.gif'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.gif'; + + $image = Grafika::createImage( $input ); + $editor->flatten( $image ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testFlip($editor) + { + $input = DIR_TEST_IMG . '/lena.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . 'H.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . 'H.jpg'; + + $image = Grafika::createImage( $input ); + $editor->flip( $image, 'h' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + $output = DIR_TMP . '/' . __FUNCTION__ . 'V.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . 'V.jpg'; + + $image = Grafika::createImage( $input ); + $editor->flip( $image, 'v' ); + $editor->save( $image, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + /** + * @depends testEqualFalse + * @param EditorInterface $editor + */ + public function testBlend($editor) + { + $input1 = DIR_TEST_IMG . '/lena.png'; + $input2 = DIR_TEST_IMG . '/blend.png'; + $output = DIR_TMP . '/' . __FUNCTION__ . '.jpg'; + $correct = $this->dirAssert . '/' . __FUNCTION__ . '.jpg'; + + $image1 = Grafika::createImage( $input1 ); + $image2 = Grafika::createImage( $input2 ); + $editor->blend( $image1, $image2, 'overlay', 0.5, 'center' ); // overlay blend, opacity 50%, center position + $editor->save( $image1, $output ); + + $this->assertLessThanOrEqual(5, $editor->compare($output, $correct)); // Account for windows and linux generating different text sizes given the same font size. + + } + + // On before every test + protected function setUp() + { + $editor = new Editor(); + if (false === $editor->isAvailable()) { + $this->markTestSkipped( + 'PHP Imagick is not available.' + ); + } + } + + // After every test + protected function tearDown() + { + if (CLEAN_DUMP) { + deleteTmpDirectory(); // Delete images created by a test + } + } + + + public static function setUpBeforeClass() + { + + } + + public static function tearDownAfterClass() + { + + } +} \ No newline at end of file diff --git a/source/vendor/kosinix/grafika/tests/bootstrap.php b/source/vendor/kosinix/grafika/tests/bootstrap.php new file mode 100644 index 0000000..c6f4380 --- /dev/null +++ b/source/vendor/kosinix/grafika/tests/bootstrap.php @@ -0,0 +1,40 @@ += 5.4.0**. + +### Usage +Encode a coordinate: + + use Lvht\GeoHash; + echo GeoHash::encode(117.031689,36.65396); + +The result is wwe0x0euu12. + +The default precision is 0.00001 which can be changed by the third parameter +of encode method. + +Find the neighbors for a given geohash: + + use Lvht\GeoHash; + var_dump(GeoHash::expand('wwe0x0')); + +and the result is: + + array(8) { + [0] => + string(11) "wwe0wc7zzzz" + [1] => + string(11) "wwe0x17zzzz" + [2] => + string(11) "wwe0x37zzzz" + [3] => + string(11) "wwe0wb7zzzz" + [4] => + string(11) "wwe0x27zzzz" + [5] => + string(11) "wwe0qz7zzzz" + [6] => + string(11) "wwe0rp7zzzz" + [7] => + string(11) "wwe0rr7zzzz" + } + +Decode a geohash string: + + Use Lvht\GeoHash; + var_dump(GeoHash::decode('wwe0x0')); + +and the result is: + + array(4) { + [0] => + double(117.0263671875) # min longitude + [1] => + double(117.03735351562) # max longitude + [2] => + double(36.650390625) # min latitude + [3] => + double(36.655883789062) # max latitude + } diff --git a/source/vendor/lvht/geohash/composer.json b/source/vendor/lvht/geohash/composer.json new file mode 100644 index 0000000..a5fbe39 --- /dev/null +++ b/source/vendor/lvht/geohash/composer.json @@ -0,0 +1,23 @@ +{ + "name": "lvht/geohash", + "type": "library", + "description": "geohash like python-geohash", + "keywords": ["geohash"], + "homepage": "http://github.com/lvht/geohash", + "license": "MIT", + "authors": [ + { + "name": "吕海涛", + "email": "git@lvht.net", + "homepage": "https://github.com/lvht" + } + ], + "require": { + "php": ">=5.4.0" + }, + "autoload": { + "psr-4": { + "Lvht\\": "src" + } + } +} diff --git a/source/vendor/lvht/geohash/src/GeoHash.php b/source/vendor/lvht/geohash/src/GeoHash.php new file mode 100644 index 0000000..ba32f2f --- /dev/null +++ b/source/vendor/lvht/geohash/src/GeoHash.php @@ -0,0 +1,159 @@ += $prec) { + if ($isEven) { + $next = ($minlng + $maxlng) / 2; + if ($lng > $next) { + $chr |= self::$bits[$b]; + $minlng = $next; + } else { + $maxlng = $next; + } + } else { + $next = ($minlat + $maxlat) / 2; + if ($lat > $next) { + $chr |= self::$bits[$b]; + $minlat = $next; + } else { + $maxlat = $next; + } + } + $isEven = !$isEven; + + if ($b < 4) { + $b++; + } else { + $hash[] = self::$table[$chr]; + $error = max($maxlng - $minlng, $maxlat - $minlat); + $b = 0; + $chr = 0b00000; + } + } + + return join('', $hash); + } + + public static function expand($hash, $prec = 0.00001) + { + list($minlng, $maxlng, $minlat, $maxlat) = self::decode($hash); + $dlng = ($maxlng - $minlng) / 2; + $dlat = ($maxlat - $minlat) / 2; + + return array( + self::encode($minlng - $dlng, $maxlat + $dlat, $prec), + self::encode($minlng + $dlng, $maxlat + $dlat, $prec), + self::encode($maxlng + $dlng, $maxlat + $dlat, $prec), + self::encode($minlng - $dlng, $maxlat - $dlat, $prec), + self::encode($maxlng + $dlng, $maxlat - $dlat, $prec), + self::encode($minlng - $dlng, $minlat - $dlat, $prec), + self::encode($minlng + $dlng, $minlat - $dlat, $prec), + self::encode($maxlng + $dlng, $minlat - $dlat, $prec), + ); + } + + public static function getRect($hash) + { + list($minlng, $maxlng, $minlat, $maxlat) = self::decode($hash); + + return array( + array($minlng, $minlat), + array($minlng, $maxlat), + array($maxlng, $maxlat), + array($maxlng, $minlat), + ); + } + + /** + * decode a geohash string to a geographical area + * + * @var $hash string geohash + * @return array array($minlng, $maxlng, $minlat, $maxlat); + */ + public static function decode($hash) + { + $minlng = -180; + $maxlng = 180; + $minlat = -90; + $maxlat = 90; + + for ($i=0,$c=strlen($hash); $i<$c; $i++) { + $v = strpos(self::$table, $hash[$i]); + if (1&$i) { + if (16&$v) { + $minlat = ($minlat + $maxlat) / 2; + } else { + $maxlat = ($minlat + $maxlat) / 2; + } + if (8&$v) { + $minlng = ($minlng + $maxlng) / 2; + } else { + $maxlng = ($minlng + $maxlng) / 2; + } + if (4&$v) { + $minlat = ($minlat + $maxlat) / 2; + } else { + $maxlat = ($minlat + $maxlat) / 2; + } + if (2&$v) { + $minlng = ($minlng + $maxlng) / 2; + } else { + $maxlng = ($minlng + $maxlng) / 2; + } + if (1&$v) { + $minlat = ($minlat + $maxlat) / 2; + } else { + $maxlat = ($minlat + $maxlat) / 2; + } + } else { + if (16&$v) { + $minlng = ($minlng + $maxlng) / 2; + } else { + $maxlng = ($minlng + $maxlng) / 2; + } + if (8&$v) { + $minlat = ($minlat + $maxlat) / 2; + } else { + $maxlat = ($minlat + $maxlat) / 2; + } + if (4&$v) { + $minlng = ($minlng + $maxlng) / 2; + } else { + $maxlng = ($minlng + $maxlng) / 2; + } + if (2&$v) { + $minlat = ($minlat + $maxlat) / 2; + } else { + $maxlat = ($minlat + $maxlat) / 2; + } + if (1&$v) { + $minlng = ($minlng + $maxlng) / 2; + } else { + $maxlng = ($minlng + $maxlng) / 2; + } + } + } + + return array($minlng, $maxlng, $minlat, $maxlat); + } +} diff --git a/source/vendor/myclabs/php-enum/.gitattributes b/source/vendor/myclabs/php-enum/.gitattributes new file mode 100644 index 0000000..50b8b8a --- /dev/null +++ b/source/vendor/myclabs/php-enum/.gitattributes @@ -0,0 +1,6 @@ +# Auto detect text files and perform LF normalization +* text=auto + +tests/ export-ignore +phpunit.xml export-ignore +.travis.yml export-ignore diff --git a/source/vendor/myclabs/php-enum/.gitignore b/source/vendor/myclabs/php-enum/.gitignore new file mode 100644 index 0000000..ad5190c --- /dev/null +++ b/source/vendor/myclabs/php-enum/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +nbproject/* +.idea/* +vendor/* +composer.phar +composer.lock diff --git a/source/vendor/myclabs/php-enum/LICENSE b/source/vendor/myclabs/php-enum/LICENSE new file mode 100644 index 0000000..accd5a0 --- /dev/null +++ b/source/vendor/myclabs/php-enum/LICENSE @@ -0,0 +1,18 @@ +php-enum - PHP Enum implementation http://github.com/myclabs/php-enum + +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/source/vendor/myclabs/php-enum/README.md b/source/vendor/myclabs/php-enum/README.md new file mode 100644 index 0000000..6a08e0a --- /dev/null +++ b/source/vendor/myclabs/php-enum/README.md @@ -0,0 +1,128 @@ +# 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) + +## Why? + +First, and mainly, `SplEnum` is not integrated to PHP, you have to install it separately. + +Using an enum instead of class constants provides the following advantages: + +- You can type-hint: `function setAction(Action $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 + */ +class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +Note the `private` keyword requires PHP > 7.1, you can omit it on PHP 7.0. + +## Usage + +```php +$action = new Action(Action::VIEW); + +// or +$action = Action::VIEW(); +``` + +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 type-hint enum values: + +```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: + +- `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 +- `search()` Return key for searched value + +### Static methods + +```php +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 +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() + */ +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 2/3 ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) diff --git a/source/vendor/myclabs/php-enum/composer.json b/source/vendor/myclabs/php-enum/composer.json new file mode 100644 index 0000000..1395658 --- /dev/null +++ b/source/vendor/myclabs/php-enum/composer.json @@ -0,0 +1,31 @@ +{ + "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": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "squizlabs/php_codesniffer": "1.*" + } +} diff --git a/source/vendor/myclabs/php-enum/src/Enum.php b/source/vendor/myclabs/php-enum/src/Enum.php new file mode 100644 index 0000000..d1bc516 --- /dev/null +++ b/source/vendor/myclabs/php-enum/src/Enum.php @@ -0,0 +1,198 @@ + + * @author Daniel Costa + * @author Mirosław Filip + */ +abstract class Enum implements \JsonSerializable +{ + /** + * Enum value + * + * @var mixed + */ + protected $value; + + /** + * Store existing constants in a static cache per object. + * + * @var array + */ + protected static $cache = []; + + /** + * Creates a new value of some type + * + * @param mixed $value + * + * @throws \UnexpectedValueException if incompatible type is given. + */ + public function __construct($value) + { + if (!$this->isValid($value)) { + throw new \UnexpectedValueException("Value '$value' is not part of the enum " . \get_called_class()); + } + + $this->value = $value; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the enum key (i.e. the constant name). + * + * @return mixed + */ + public function getKey() + { + return static::search($this->value); + } + + /** + * @return string + */ + public function __toString() + { + return (string)$this->value; + } + + /** + * Compares one Enum with another. + * + * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 + * + * @return bool True if Enums are equal, false if not equal + */ + final public function equals(Enum $enum = null) + { + return $enum !== null && $this->getValue() === $enum->getValue() && \get_called_class() === \get_class($enum); + } + + /** + * Returns the names (keys) of all constants in the Enum class + * + * @return array + */ + public static function keys() + { + return \array_keys(static::toArray()); + } + + /** + * Returns instances of the Enum class of all Enum constants + * + * @return static[] Constant name in key, Enum instance in value + */ + public static function values() + { + $values = array(); + + foreach (static::toArray() as $key => $value) { + $values[$key] = new static($value); + } + + return $values; + } + + /** + * Returns all possible values as an array + * + * @return array Constant name in key, constant value in value + */ + public static function toArray() + { + $class = \get_called_class(); + if (!isset(static::$cache[$class])) { + $reflection = new \ReflectionClass($class); + static::$cache[$class] = $reflection->getConstants(); + } + + return static::$cache[$class]; + } + + /** + * Check if is valid enum value + * + * @param $value + * + * @return bool + */ + public static function isValid($value) + { + return \in_array($value, static::toArray(), true); + } + + /** + * Check if is valid enum key + * + * @param $key + * + * @return bool + */ + public static function isValidKey($key) + { + $array = static::toArray(); + + return isset($array[$key]) || \array_key_exists($key, $array); + } + + /** + * Return key for value + * + * @param $value + * + * @return mixed + */ + 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 + */ + public static function __callStatic($name, $arguments) + { + $array = static::toArray(); + if (isset($array[$name]) || \array_key_exists($name, $array)) { + return new static($array[$name]); + } + + throw new \BadMethodCallException("No static method or enum constant '$name' in class " . \get_called_class()); + } + + /** + * 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 + */ + public function jsonSerialize() + { + return $this->getValue(); + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/.gitignore b/source/vendor/qcloud/cos-sdk-v5/.gitignore new file mode 100644 index 0000000..1c0b8d4 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/.gitignore @@ -0,0 +1,2 @@ +.vscode/ +.idea/ \ No newline at end of file diff --git a/source/vendor/qcloud/cos-sdk-v5/.travis.yml b/source/vendor/qcloud/cos-sdk-v5/.travis.yml new file mode 100644 index 0000000..0392843 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/.travis.yml @@ -0,0 +1,6 @@ +language: php +php: + - 5.4 +script: + - composer install + - phpunit -v diff --git a/source/vendor/qcloud/cos-sdk-v5/LICENSE b/source/vendor/qcloud/cos-sdk-v5/LICENSE new file mode 100644 index 0000000..2c948c8 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 腾讯云 + +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/source/vendor/qcloud/cos-sdk-v5/README.md b/source/vendor/qcloud/cos-sdk-v5/README.md new file mode 100644 index 0000000..2a404c5 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/README.md @@ -0,0 +1,257 @@ +# COS-PHP-SDK-V5 +腾讯云COS-PHP-SDK-V5([XML API](https://cloud.tencent.com/document/product/436/7751)) + +[![Total Downloads](https://img.shields.io/packagist/dt/qcloud/cos-sdk-v5.svg?style=flat)](https://packagist.org/packages/qcloud/cos-sdk-v5) +[![Build Status](https://travis-ci.org/tencentyun/cos-php-sdk-v5.svg?branch=master)](https://travis-ci.org/tencentyun/cos-php-sdk-v5) + +## 环境准备 +* PHP 5.3+ + 您可以通过`php -v`命令查看当前的PHP版本。 + +* cURL 扩展 + 您可以通过`php -m`命令查看cURL扩展是否已经安装好。 + +> **说明:** +> +> * Ubuntu系统中,您可以使用apt-get包管理器安装PHP的cURL扩展 `sudo apt-get install php-curl`。 +> * CentOS系统中,您可以使用yum包管理器安装PHP的cURL扩展 `sudo yum install php-curl`。 + +### SDK 安装 +有三种方式安装SDK: +* Composer方式 +* Phar方式 +* 源码方式 +#### 1、Composer方式 +推荐用户使用 Composer 安装 cos-php-sdk-v5,Composer是PHP的依赖管理工具,允许您声明项目所需的依赖,然后自动将它们安装到您的项目中。 + +> **提示**:您可以在 [getcomposer.org](getcomposer.org) 上找到更多关于如何安装Composer,配置自动加载以及用于定义依赖项的其他最佳实践。 + +**使用 Composer 安装 COS-PHP-SDK-V5** +1. 打开终端 +2. 下载 Composer +``` +curl -sS https://getcomposer.org/installer | php +``` +3. 创建一个名为`composer.json`的文件,内容为 +``` +{ + "require": { + "qcloud/cos-sdk-v5": "1.*" + } +} +``` +4. 使用 Composer 安装 +``` +php composer.phar install +``` +使用该命令后会在当前目录中创建一个vendor文件夹,里面包含 sdk 的依赖库和一个 autoload.php 脚本,方便用户在自己的项目中调用。 +5. 通过 autoloader 脚本调用cos-php-sdk-v5 +``` +require '/path/to/sdk/vendor/autoload.php'; +``` +现在您的项目已经可以使用COS的V5 SDK了。 + + +#### 2、Phar方式 +phar方式安装SDK的步骤如下: + +1. 在[github发布页面](https://github.com/tencentyun/cos-php-sdk-v5/releases)下载相应的phar文件 + +2. 在代码中引入phar文件: +``` +require '/path/to/cos-sdk-v5.phar'; +``` + +#### 3、源码方式 +源码方式安装SDK的步骤如下: + +1. 在[github发布页面](https://github.com/tencentyun/cos-php-sdk-v5/releases)下载相应的zip文件 + +2. 解压通过 autoload.php 脚本加载sdk +``` +require '/path/to/sdk/vendor/autoload.php'; +``` + + +## 快速入门 +可参照Demo程序,详见 [sample.php](https://github.com/tencentyun/cos-php-sdk-v5/blob/master/sample.php) +## 接口文档 +php sdk 接口文档,详见https://cloud.tencent.com/document/product/436/12267 +### 配置文件 +```php +$cosClient = new Qcloud\Cos\Client(array('region' => 'COS_REGION', + 'credentials'=> array( + 'secretId' => 'COS_KEY', + 'secretKey' => 'COS_SECRET'))); +``` +### 上传文件 +* 使用putObject接口上传文件(最大5G) +* 使用Upload接口分块上传文件 +```php +# 上传文件 +## putObject(上传接口,最大支持上传5G文件) +### 上传内存中的字符串 +//bucket 的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式 +try { + $result = $cosClient->putObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => 'Hello World!')); + print_r($result); +} catch (\Exception $e) { + echo "$e\n"; +} + +### 上传文件流 +try { + $result = $cosClient->putObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => fopen($local_path, 'rb'))); + print_r($result); +} catch (\Exception $e) { + echo "$e\n"; +} + +### 设置header和meta +try { + $result = $cosClient->putObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => fopen($local_path, 'rb'), + 'ACL' => 'string', + 'CacheControl' => 'string', + 'ContentDisposition' => 'string', + 'ContentEncoding' => 'string', + 'ContentLanguage' => 'string', + 'ContentLength' => integer, + 'ContentType' => 'string', + 'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime', + 'GrantFullControl' => 'string', + 'GrantRead' => 'string', + 'GrantWrite' => 'string', + 'Metadata' => array( + 'string' => 'string', + ), + 'StorageClass' => 'string')); + print_r($result); +} catch (\Exception $e) { + echo "$e\n"; +} + +## Upload(高级上传接口,默认使用分块上传最大支持50T) +### 上传内存中的字符串 +try { + $result = $cosClient->Upload( + $bucket = $bucket, + $key = $key, + $body = 'Hello World!'); + print_r($result); +} catch (\Exception $e) { + echo "$e\n"; +} + +### 上传文件流 +try { + $result = $cosClient->Upload( + $bucket = $bucket, + $key = $key, + $body = fopen($local_path, 'rb')); + print_r($result); +} catch (\Exception $e) { + echo "$e\n"; +} + +### 设置header和meta +try { + $result = $cosClient->Upload( + $bucket= $bucket, + $key = $key, + $body = fopen($local_path, 'rb'), + $options = array( + 'ACL' => 'string', + 'CacheControl' => 'string', + 'ContentDisposition' => 'string', + 'ContentEncoding' => 'string', + 'ContentLanguage' => 'string', + 'ContentLength' => integer, + 'ContentType' => 'string', + 'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime', + 'GrantFullControl' => 'string', + 'GrantRead' => 'string', + 'GrantWrite' => 'string', + 'Metadata' => array( + 'string' => 'string', + ), + 'StorageClass' => 'string')); + print_r($result); +} catch (\Exception $e) { + echo "$e\n"; +} +``` + +### 下载文件 +* 使用getObject接口下载文件 +* 使用getObjectUrl接口获取文件下载URL +```php +# 下载文件 +## getObject(下载文件) +### 下载到内存 +//bucket 的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式 +try { + $result = $cosClient->getObject(array( + 'Bucket' => $bucket, + 'Key' => $key)); + echo($result['Body']); +} catch (\Exception $e) { + echo "$e\n"; +} + +### 下载到本地 +try { + $result = $cosClient->getObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'SaveAs' => $local_path)); +} catch (\Exception $e) { + echo "$e\n"; +} + +### 指定下载范围 +/* + * Range 字段格式为 'bytes=a-b' + */ +try { + $result = $cosClient->getObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Range' => 'bytes=0-10', + 'SaveAs' => $local_path)); +} catch (\Exception $e) { + echo "$e\n"; +} + +### 设置返回header +try { + $result = $cosClient->getObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'ResponseCacheControl' => 'string', + 'ResponseContentDisposition' => 'string', + 'ResponseContentEncoding' => 'string', + 'ResponseContentLanguage' => 'string', + 'ResponseContentType' => 'string', + 'ResponseExpires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime', + 'SaveAs' => $local_path)); +} catch (\Exception $e) { + echo "$e\n"; +} + +## getObjectUrl(获取文件UrL) +try { + $signedUrl = $cosClient->getObjectUrl($bucket, $key, '+10 minutes'); + echo $signedUrl; +} catch (\Exception $e) { + print_r($e); +} +``` diff --git a/source/vendor/qcloud/cos-sdk-v5/composer.json b/source/vendor/qcloud/cos-sdk-v5/composer.json new file mode 100644 index 0000000..bdeff53 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/composer.json @@ -0,0 +1,27 @@ +{ + "name": "qcloud/cos-sdk-v5", + "description": "PHP SDK for QCloud COS", + "keywords": [ + "qcloud", "cos", "php" + ], + "license": "MIT", + "authors": [ + { + "name": "yaozongyou", + "email": "yaozongyou@vip.qq.com" + }, + { + "name": "lewzylu", + "email": "327874225@qq.com" + } + ], + "autoload": { + "psr-0": { + "Qcloud\\Cos\\": "src/" + } + }, + "require": { + "php": ">=5.3.0", + "guzzle/guzzle": "~3.7" + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/phpunit.xml b/source/vendor/qcloud/cos-sdk-v5/phpunit.xml new file mode 100644 index 0000000..3c7a4c1 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/phpunit.xml @@ -0,0 +1,7 @@ + + + + src/Qcloud/Cos/Tests + + + diff --git a/source/vendor/qcloud/cos-sdk-v5/sample.php b/source/vendor/qcloud/cos-sdk-v5/sample.php new file mode 100644 index 0000000..4c98c86 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/sample.php @@ -0,0 +1,824 @@ + 'COS_REGION', #地域,如ap-guangzhou,ap-beijing-1 + 'credentials' => array( + 'secretId' => 'COS_KEY', + 'secretKey' => 'COS_SECRET', + ), +)); + +// 若初始化 Client 时未填写 appId,则 bucket 的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式 +$bucket = 'test2-1252448703'; +$key = 'a.txt'; +$local_path = "E:/a.txt"; + +# 上传文件 +## putObject(上传接口,最大支持上传5G文件) +### 上传内存中的字符串 +try { + $result = $cosClient->putObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => 'Hello World!' + )); + print_r($result); + # 可以直接通过$result读出返回结果 + echo ($result['ETag']); +} catch (\Exception $e) { + echo($e); +} + +### 上传文件流 +try { + $result = $cosClient->putObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => fopen($local_path, 'rb') + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +### 设置header和meta +try { + $result = $cosClient->putObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => fopen($local_path, 'rb'), + 'ACL' => 'string', + 'CacheControl' => 'string', + 'ContentDisposition' => 'string', + 'ContentEncoding' => 'string', + 'ContentLanguage' => 'string', + 'ContentLength' => integer, + 'cONTENTType' => 'string', + 'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime', + 'GrantFullControl' => 'string', + 'GrantRead' => 'string', + 'GrantWrite' => 'string', + 'Metadata' => array( + 'string' => 'string', + ), + 'StorageClass' => 'string' + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## Upload(高级上传接口,默认使用分块上传最大支持50T) +### 上传内存中的字符串 +try { + $result = $cosClient->upload( + $bucket = $bucket, + $key = $key, + $body = 'Hello World!' + ); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +### 上传文件流 +try { + $result = $cosClient->upload( + $bucket = $bucket, + $key = $key, + $body = fopen($local_path, 'rb') + ); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +### 设置header和meta +try { + $result = $cosClient->upload( + $bucket = $bucket, + $key = $key, + $body = fopen($local_path, 'rb'), + $options = array( + 'ACL' => 'string', + 'CacheControl' => 'string', + 'ContentDisposition' => 'string', + 'ContentEncoding' => 'string', + 'ContentLanguage' => 'string', + 'ContentLength' => integer, + 'ContentType' => 'string', + 'Expires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime', + 'GrantFullControl' => 'string', + 'GrantRead' => 'string', + 'GrantWrite' => 'string', + 'Metadata' => array( + 'string' => 'string', + ), + 'StorageClass' => 'string' + ) + ); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## 预签名上传createPresignedUrl +## 获取带有签名的url +### 简单上传预签名 +try { + #此处可以替换为其他上传接口 + $command = $cosClient->getCommand('putObject', array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => '', //Body可以任意 + )); + $signedUrl = $command->createPresignedUrl('+10 minutes'); + echo ($signedUrl); +} catch (\Exception $e) { + echo($e); +} + +### 分块上传预签名 +try { + #此处可以替换为其他上传接口 + $command = $cosClient->getCommand('uploadPart', array( + 'Bucket' => $bucket, + 'Key' => $key, + 'UploadId' => '', + 'PartNumber' => '1', + 'Body' => '', //Body可以任意 + )); + $signedUrl = $command->createPresignedUrl('+10 minutes'); + echo ($signedUrl); +} catch (\Exception $e) { + echo($e); +} + +### 获取签名 +try { + #此处可以替换为其他上传接口 + $command = $cosClient->getCommand('putObject', array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => '', //Body可以任意 + )); + $signedUrl = $command->createAuthorization('+10 minutes'); + echo ($signedUrl); +} catch (\Exception $e) { + echo($e); +} + + +# 下载文件 +## getObject(下载文件) +### 下载到内存 +try { + $result = $cosClient->getObject(array( + 'Bucket' => $bucket, + 'Key' => $key + )); + echo $result['Body']; +} catch (\Exception $e) { + echo($e); +} + +### 下载到本地 +try { + $result = $cosClient->getObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'SaveAs' => $local_path + )); +} catch (\Exception $e) { + echo($e); +} + +### 指定下载范围 +/* + * Range 字段格式为 'bytes=a-b' + */ +try { + $result = $cosClient->getObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Range' => 'bytes=0-10', + 'SaveAs' => $local_path + )); +} catch (\Exception $e) { + echo($e); +} + +### 设置返回header +try { + $result = $cosClient->getObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'ResponseCacheControl' => 'string', + 'ResponseContentDisposition' => 'string', + 'ResponseContentEncoding' => 'string', + 'ResponseContentLanguage' => 'string', + 'ResponseContentType' => 'string', + 'ResponseExpires' => 'mixed type: string (date format)|int (unix timestamp)|\DateTime', + 'SaveAs' => $local_path + )); +} catch (\Exception $e) { + echo($e); +} + +## getObjectUrl(获取文件UrL) +try { + $signedUrl = $cosClient->getObjectUrl($bucket, $key, '+10 minutes'); + echo $signedUrl; +} catch (\Exception $e) { + echo($e); +} + +# 删除object +## deleteObject +try { + $result = $cosClient->deleteObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'VersionId' => 'string' + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 删除多个object +## deleteObjects +try { + $result = $cosClient->deleteObjects(array( + 'Bucket' => 'string', + 'Objects' => array( + array( + 'Key' => $key, + 'VersionId' => 'string', + ), + // ... repeated + ), + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 获取object信息 +## headObject +/* + * 可代替isObjectExist接口,查询object是否存在 + */ +try { + $result = $cosClient->headObject(array( + 'Bucket' => $bucket, + 'Key' => '11', + 'VersionId' => '111', + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 获取bucket列表 +## listBuckets +try { + $result = $cosClient->listBuckets(); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 创建bucket +## createBucket +try { + $result = $cosClient->createBucket(array('Bucket' => $bucket)); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 删除bucket +## deleteBucket +try { + $result = $cosClient->deleteBucket(array( + 'Bucket' => $bucket + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 获取bucket信息 +## headBucket +/* + * 可代替isBucketExist接口,查询bucket是否存在 + */ +try { + $result = $cosClient->headBucket(array( + 'Bucket' => $bucket + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 列出bucket下的object +## listObjects +### 列出所有object +/* + * 该接口一次最多列出1000个,需要列出所有请参考其他服务中的清空并删除bucket接口 + */ +try { + $result = $cosClient->listObjects(array( + 'Bucket' => $bucket + )); + foreach ($result['Contents'] as $rt) { + print_r($rt); + } +} catch (\Exception $e) { + echo($e); +} + +### 列出带有前缀的object +try { + $result = $cosClient->listObjects(array( + 'Bucket' => $bucket, + 'Prefix' => 'string' + )); + foreach ($result['Contents'] as $rt) { + print_r($rt); + } +} catch (\Exception $e) { + echo($e); +} + +# 获取bucket地域 +## getBucketLocation +try { + $result = $cosClient->getBucketLocation(array( + 'Bucket' => 'lewzylu02', + )); +} catch (\Exception $e) { + echo($e); +}; + +# 多版本相关 +## putBucketVersioning(开启关闭某个bucket的多版本) +try { + $result = $cosClient->putBucketVersioning(array( + 'Bucket' => $bucket, + 'Status' => 'Enabled' + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## ListObjectVersions(列出多版本object) +/* + * 同名文件会出现多个版本 + */ +try { + $result = $cosClient->listObjectVersions(array( + 'Bucket' => $bucket, + 'Prefix' => 'string' + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## getBucketVersioning(获取某个bucket多版本属性) +try { + $result = $cosClient->getBucketVersioning( + array('Bucket' => $bucket)); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# ACL相关 +## PutBucketAcl(设置bucketACL) +try { + $result = $cosClient->putBucketAcl(array( + 'Bucket' => $bucket, + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::uin/327874225:uin/327874225', + 'ID' => 'qcs::cam::uin/327874225:uin/327874225', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/3210232098:uin/3210232098', + 'ID' => 'qcs::cam::uin/3210232098:uin/3210232098', + ))); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## getBucketAcl(获取bucketACL) +try { + $result = $cosClient->getBucketAcl(array( + 'Bucket' => $bucket)); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## putObjectAcl(设置objectACL) +try { + $result = $cosClient->putObjectAcl(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::uin/327874225:uin/327874225', + 'ID' => 'qcs::cam::uin/327874225:uin/327874225', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/3210232098:uin/3210232098', + 'ID' => 'qcs::cam::uin/3210232098:uin/3210232098', + ))); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## GetObjectAcl(获取objectACL) +try { + $result = $cosClient->getObjectAcl(array( + 'Bucket' => $bucket, + 'Key' => $key)); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 生命周期相关 +## putBucketLifecycle(设置bucket生命周期) +try { + $result = $cosClient->putBucketLifecycle(array( + 'Bucket' => $bucket, + 'Rules' => array( + array( + 'Expiration' => array( + 'Days' => 1000, + ), + 'ID' => 'id1', + 'Filter' => array( + 'Prefix' => 'documents/', + ), + 'Status' => 'Enabled', + 'Transitions' => array( + array( + 'Days' => 200, + 'StorageClass' => 'NEARLINE'), + ), + ), + ))); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## getBucketLifecycle(获取bucket生命周期) +try { + $result = $cosClient->getBucketLifecycle(array( + 'Bucket' => $bucket, + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## deleteBucketLifecycle(删除bucket生命周期) +try { + $result = $cosClient->deleteBucketLifecycle(array( + 'Bucket' => $bucket, + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 跨域相关 +## putBucketCors(设置bucket跨域) +try { + $result = $cosClient->putBucketCors(array( + 'Bucket' => $bucket, + 'CORSRules' => array( + array( + 'ID' => '1234', + 'AllowedHeaders' => array('*'), + 'AllowedMethods' => array('PUT'), + 'AllowedOrigins' => array('http://www.qq.com'), + ), + ), + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## getBucketCors(获取bucket跨域信息) +try { + $result = $cosClient->getBucketCors(array()); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## deleteBucketCors(删除bucket跨域) +try { + $result = $cosClient->deleteBucketCors(array( + // Bucket is required + 'Bucket' => $bucket, + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 跨区域复制相关 +## PutBucketReplication(设置bucket跨区域复制) +### 注意:目标bucket和源bucket都需要开启多版本 +try { + $result = $cosClient->putBucketReplication(array( + 'Bucket' => $bucket, + 'Role' => 'qcs::cam::uin/327874225:uin/327874225', + 'Rules'=>array( + array( + 'Status' => 'Enabled', + 'ID' => 'string', + 'Prefix' => 'string', + 'Destination' => array( + 'Bucket' => 'qcs::cos:ap-guangzhou::lewzylu01-1252448703', + 'StorageClass' => 'standard', + ), + // ...repeated + ), + ), + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## GetBucketReplication(获取bucket跨区域复制信息) +try { + $result = $cosClient->getBucketReplication(array( + 'Bucket' => $bucket + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## DeleteBucketReplication(删除bucket跨区域复制信息) +try { + $result = $cosClient->deleteBucketReplication(array( + 'Bucket' => $bucket + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 回调相关 +## PutBucketNotification +try { + $result = $cosClient->putBucketNotification(array( + "Bucket" => $bucket, + "CloudFunctionConfigurations"=> array( + array( + "Id" => "test-1", + "Filter" => array( + "Key" => array( + "FilterRules" => array( + array( + "Name" => "Prefix", + "Value" => "111" + ), + array( + "Name" => "Suffix", + "Value" => "111" + ), + ), + ) + ), + "CloudFunction" => "qcs:0:video:sh:appid/1253125191:video/10010", + "Events" => array( + 'Event' => "cos:ObjectCreated:*" + ) + ), + array( + "Id" => "test-2", + "Filter" => array( + "Key" => array( + "FilterRules" => array( + array( + "Name" => "Prefix", + "Value" => "111" + ), + array( + "Name" => "Suffix", + "Value" => "111" + ), + ), + ) + ), + "CloudFunction" => "qcs:0:video:sh:appid/1253125191:video/10010", + "Events" => array( + 'Event' => "cos:ObjectRemove:*" + ) + ), + )) + ); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## GetBucketNotification +try { + $result = $cosClient->getBucketNotification(array( + 'Bucket' => $bucket + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 复制 +## copyobject(简单复制) +/* + * 将{bucket},{region},{cos_path},{versionId}替换成复制源的真实信息 + */ +try { + $result = $cosClient->copyObject(array( + 'Bucket' => $bucket, + 'CopySource' => '{bucket}.cos.{region}.myqcloud.com/{cos_path}?versionId={versionId}', + 'Key' => 'string', + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## Copy(分块并发复制) +/* + * 将{bucket},{region},{cos_path},{versionId}替换成复制源的真实信息 + */ +try { + $result = $cosClient->copy( + $bucket = $bucket, + $key = $key, + $copysource = '{bucket}.cos.{region}.myqcloud.com/{cos_path}', + $options = array('VersionId' => '{versionId}' + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 恢复归档文件 +## restoreObject +try { + $result = $cosClient->restoreObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Days' => 7, + 'CASJobParameters' => array( + 'Tier' => 'Bulk', + ), + )); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +# 其他服务 +## 列出某bucket下所有的object +try { + $prefix = ''; + $marker = ''; + while (true) { + $result = $cosClient->listObjects(array( + 'Bucket' => $bucket, + 'Marker' => $marker, + 'MaxKeys' => 1000 + )); + foreach ($result['Contents'] as $rt) { + print_r($rt['Key'] . " "); + /* + * 使用下面的代码可以删除全部object + */ + // try { + // $result = $cosClient->deleteobjects(array( + // 'Bucket' => $bucket, + // 'Key' => $rt['Key'])); + // print_r($result); + // } catch (\Exception $e) { + // echo($e); + // } + } + $marker = $result['NextMarker']; + if (!$result['IsTruncated']) { + break; + } + } +} catch (\Exception $e) { + echo($e); +} + +## 删除所有因上传失败而产生的分块 +/* + * 可以清理掉因分块上传失败 + */ +try { + while (true) { + $result = $cosClient->listMultipartUploads( + array('Bucket' => $bucket, + 'Prefix' => '')); + if (count($result['Uploads']) == 0) { + break; + } + foreach ($result['Uploads'] as $upload) { + try { + $rt = $cosClient->abortMultipartUpload(array( + 'Bucket' => $bucket, + 'Key' => $upload['Key'], + 'UploadId' => $upload['UploadId'] + )); + print_r($rt); + } catch (\Exception $e) { + echo($e); + } + } + } +} catch (\Exception $e) { + echo($e); +} + +## 分块上传断点重传 +/* + * 仅适用于分块上传失败的情况 + * 需要填写上传失败的uploadId + */ +try { + $result = $cosClient->resumeUpload( + $bucket = $bucket, + $key = $key, + $body = fopen("E:/test.txt", 'rb'), + $uploadId = '152448808231afdf221eb558ab15d1e455d2afd025c5663936142fdf5614ebf6d1668e2eda' + ); + print_r($result); +} catch (\Exception $e) { + echo($e); +} + +## 删除某些前缀的空bucket +function startsWith($haystack, $needle) +{ + $length = strlen($needle); + return (substr($haystack, 0, $length) === $needle); +} + +try { + $result = $cosClient->listBuckets(); + foreach ($result['Buckets'] as $bucket) { + $region = $bucket['Location']; + $name = $bucket['Name']; + if (startsWith($name, 'lewzylu')) { + try { + $cosClient2 = new Qcloud\Cos\Client(array( + 'region' => $region, + 'credentials' => array( + //getenv为获取本地环境变量,请替换为真实密钥 + 'secretId' => getenv('COS_KEY'), + 'secretKey' => getenv('COS_SECRET')) + )); + $rt = $cosClient2->deleteBucket(array('Bucket' => $name)); + print_r($rt); + } catch (\Exception $e) { + } + } + } +} catch (\Exception $e) { + echo($e); +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/BucketStyleListener.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/BucketStyleListener.php new file mode 100644 index 0000000..a035d05 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/BucketStyleListener.php @@ -0,0 +1,74 @@ +appId = $appId; + } + + public static function getSubscribedEvents() { + return array('command.after_prepare' => array('onCommandAfterPrepare', -230)); + } + + /** + * Change from path style to host style. + * @param Event $event Event emitted. + */ + public function onCommandAfterPrepare(Event $event) { + $command = $event['command']; + $bucket = $command['Bucket']; + $request = $command->getRequest(); + + if ($command->getName() == 'ListBuckets') + { + $request->setHost('service.cos.myqcloud.com'); + return ; + } + if ($key = $command['Key']) { + // Modify the command Key to account for the {/Key*} explosion into an array + if (is_array($key)) { + $command['Key'] = $key = implode('/', $key); + } + } + $request->setHeader('Date', gmdate('D, d M Y H:i:s T')); + $request->setPath(preg_replace("#^/{$bucket}#", '', $request->getPath())); + + if ($this->appId != null && endWith($bucket,'-'.$this->appId) == False) + { + $bucket = $bucket.'-'.$this->appId; + } + // Set the key and bucket on the request + $request->getParams()->set('bucket', $bucket)->set('key', $key); + + //$request->setPath(urldecode($request->getPath())); + // Switch to virtual hosted bucket + $request->setHost($bucket. '.' . $request->getHost()); + if (!$bucket) { + $request->getParams()->set('cos.resource', '/'); + } else { + // Bucket style needs a trailing slash + $request->getParams()->set( + 'cos.resource', + '/' . rawurlencode($bucket) . ($key ? ('/' . Client::encodeKey($key)) : '/') + ); + } + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Client.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Client.php new file mode 100644 index 0000000..e6dd11f --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Client.php @@ -0,0 +1,270 @@ +region = $config['region']; + $regionmap = array('cn-east'=>'ap-shanghai', + 'cn-sorth'=>'ap-guangzhou', + 'cn-north'=>'ap-beijing-1', + 'cn-south-2'=>'ap-guangzhou-2', + 'cn-southwest'=>'ap-chengdu', + 'sg'=>'ap-singapore', + 'tj'=>'ap-beijing-1', + 'bj'=>'ap-beijing', + 'sh'=>'ap-shanghai', + 'gz'=>'ap-guangzhou', + 'cd'=>'ap-chengdu', + 'sgp'=>'ap-singapore',); + $this->region = isset($regionmap[$this->region]) ? $regionmap[$this->region] : $this->region; + $this->credentials = $config['credentials']; + $this->appId = isset($config['credentials']['appId']) ? $config['credentials']['appId'] : null; + $this->secretId = $config['credentials']['secretId']; + $this->secretKey = $config['credentials']['secretKey']; + $this->token = isset($config['credentials']['token']) ? $config['credentials']['token'] : null; + $this->timeout = isset($config['timeout']) ? $config['timeout'] : 3600; + $this->connect_timeout = isset($config['connect_timeout']) ? $config['connect_timeout'] : 3600; + $this->signature = new signature($this->secretId, $this->secretKey); + parent::__construct( + 'http://cos.' . $this->region . '.myqcloud.com/', // base url + array('request.options' => array('timeout' => $this->timeout, 'connect_timeout' => $this->connect_timeout), + )); // show curl verbose or not + + $desc = ServiceDescription::factory(Service::getService()); + $this->setDescription($desc); + $this->setUserAgent('cos-php-sdk-v5.' . Client::VERSION, true); + + $this->addSubscriber(new ExceptionListener()); + $this->addSubscriber(new Md5Listener($this->signature)); + $this->addSubscriber(new TokenListener($this->token)); + $this->addSubscriber(new SignatureListener($this->secretId, $this->secretKey)); + $this->addSubscriber(new BucketStyleListener($this->appId)); + + // Allow for specifying bodies with file paths and file handles + $this->addSubscriber(new UploadBodyListener(array('PutObject', 'UploadPart'))); + } + public function set_config($config) { + $this->region = $config['region']; + $regionmap = array('cn-east'=>'ap-shanghai', + 'cn-sorth'=>'ap-guangzhou', + 'cn-north'=>'ap-beijing-1', + 'cn-south-2'=>'ap-guangzhou-2', + 'cn-southwest'=>'ap-chengdu', + 'sg'=>'ap-singapore', + 'tj'=>'ap-beijing-1', + 'bj'=>'ap-beijing', + 'sh'=>'ap-shanghai', + 'gz'=>'ap-guangzhou', + 'cd'=>'ap-chengdu', + 'sgp'=>'ap-singapore',); + $this->region = isset($regionmap[$this->region]) ? $regionmap[$this->region] : $this->region; + $this->credentials = $config['credentials']; + $this->appId = isset($config['credentials']['appId']) ? $config['credentials']['appId'] : null; + $this->secretId = $config['credentials']['secretId']; + $this->secretKey = $config['credentials']['secretKey']; + $this->token = isset($config['credentials']['token']) ? $config['credentials']['token'] : null; + $this->timeout = isset($config['timeout']) ? $config['timeout'] : 3600; + $this->connect_timeout = isset($config['connect_timeout']) ? $config['connect_timeout'] : 3600; + $this->signature = new signature($this->secretId, $this->secretKey); + parent::__construct( + 'http://cos.' . $this->region . '.myqcloud.com/', // base url + array('request.options' => array('timeout' => $this->timeout, 'connect_timeout' => $this->connect_timeout), + )); // show curl verbose or not + } + public function __destruct() { + } + + public function __call($method, $args) { + return parent::__call(ucfirst($method), $args); + } + public function createAuthorization(RequestInterface $request, $expires) + { + if ($request->getClient() !== $this) { + throw new InvalidArgumentException('The request object must be associated with the client. Use the ' + . '$client->get(), $client->head(), $client->post(), $client->put(), etc. methods when passing in a ' + . 'request object'); + } + return $this->signature->createAuthorization($request, $expires); + } + public function createPresignedUrl(RequestInterface $request, $expires) + { + if ($request->getClient() !== $this) { + throw new InvalidArgumentException('The request object must be associated with the client. Use the ' + . '$client->get(), $client->head(), $client->post(), $client->put(), etc. methods when passing in a ' + . 'request object'); + } + return $this->signature->createPresignedUrl($request, $expires); + } + public function getObjectUrl($bucket, $key, $expires = null, array $args = array()) + { + $command = $this->getCommand('GetObject', $args + array('Bucket' => $bucket, 'Key' => $key)); + + if ($command->hasKey('Scheme')) { + $scheme = $command['Scheme']; + $request = $command->remove('Scheme')->prepare()->setScheme($scheme)->setPort(null); + } else { + $request = $command->prepare(); + } + + return $expires ? $this->createPresignedUrl($request, $expires) : $request->getUrl(); + } + public function Upload($bucket, $key, $body, $options = array()) { + $body = EntityBody::factory($body); + $options = Collection::fromConfig(array_change_key_case($options), array( + 'min_part_size' => MultipartUpload::MIN_PART_SIZE, + 'params' => $options)); + if ($body->getSize() < $options['min_part_size']) { + // Perform a simple PutObject operation + $rt = $this->putObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => $body, + ) + $options['params']); + + $rt['Location'] = $rt['ObjectURL']; + unset($rt['ObjectURL']); + } + else { + $multipartUpload = new MultipartUpload($this, $body, $options['min_part_size'], array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => $body, + ) + $options['params']); + + $rt = $multipartUpload->performUploading(); + } + return $rt; + } + + public function resumeUpload($bucket, $key, $body, $uploadId, $options = array()) { + $body = EntityBody::factory($body); + $options = Collection::fromConfig(array_change_key_case($options), array( + 'min_part_size' => MultipartUpload::MIN_PART_SIZE, + 'params' => $options)); + $multipartUpload = new MultipartUpload($this, $body, $options['min_part_size'], array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => $body, + 'UploadId' => $uploadId, + ) + $options['params']); + + $rt = $multipartUpload->resumeUploading(); + return $rt; + } + + public function Copy($bucket, $key, $copysource, $options = array()) { + + $options = Collection::fromConfig(array_change_key_case($options), array( + 'min_part_size' => Copy::MIN_PART_SIZE, + 'params' => $options)); + $sourcelistdot = explode('.',$copysource); + $sourcelistline = explode('-',$sourcelistdot[0]); + $sourceappid = array_pop($sourcelistline); + $sourcebucket = implode('-', $sourcelistline); + $sourceregion = $sourcelistdot[2]; + $sourcekey = substr(strstr($copysource,'/'),1); + $sourceversion = ""; + $cosClient = new Client(array('region' => $sourceregion, + 'credentials'=> array( + 'appId' => $sourceappid, + 'secretId' => $this->secretId, + 'secretKey' => $this->secretKey))); + if (!key_exists('VersionId',$options['params'])) { + $sourceversion = ""; + } + else{ + $sourceversion = $options['params']['VersionId']; + } + $rt = $cosClient->headObject(array('Bucket'=>$sourcebucket, + 'Key'=>$sourcekey, + 'VersionId'=>$sourceversion)); + $contentlength =$rt['ContentLength']; + + if ($contentlength < $options['min_part_size']) { + return $this->copyObject(array( + 'Bucket' => $bucket, + 'Key' => $key, + 'CopySource' => $copysource."?versionId=".$sourceversion, + ) + $options['params']); + } + $copy = new Copy($this, $contentlength, $copysource."?versionId=".$sourceversion, $options['min_part_size'], array( + 'Bucket' => $bucket, + 'Key' => $key + ) + $options['params']); + + return $copy->copy(); + } + + /** + * Determines whether or not a bucket exists by name + * + * @param string $bucket The name of the bucket + * @param bool $accept403 Set to true if 403s are acceptable + * @param array $options Additional options to add to the executed command + * + * @return bool + */ + public function doesBucketExist($bucket, $accept403 = true, array $options = array()) + { + try { + $this->HeadBucket(array( + 'Bucket' => $bucket)); + return True; + }catch (\Exception $e){ + return False; + } + } + + /** + * Determines whether or not an object exists by name + * + * @param string $bucket The name of the bucket + * @param string $key The key of the object + * @param array $options Additional options to add to the executed command + * + * @return bool + */ + public function doesObjectExist($bucket, $key, array $options = array()) + { + try { + $this->HeadObject(array( + 'Bucket' => $bucket, + 'Key' => $key)); + return True; + }catch (\Exception $e){ + return False; + } + } + public static function encodeKey($key) { + return $key; + return str_replace('%2F', '/', rawurlencode($key)); + } + + public static function explodeKey($key) { + // Remove a leading slash if one is found + //return explode('/', $key && $key[0] == '/' ? substr($key, 1) : $key); + return $key; + return ltrim($key, "/"); + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Command.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Command.php new file mode 100644 index 0000000..647bb52 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Command.php @@ -0,0 +1,39 @@ +client->createPresignedUrl($this->prepare(), $expires); + } + public function createAuthorization($expires) + { + return $this->client->createAuthorization($this->prepare(), $expires); + } + + protected function process() { + parent::process(); + // Set the GetObject URL if using the PutObject operation + if ($this->result instanceof Model && $this->getName() == 'PutObject') { + $request = $this->getRequest();; + $this->result->set('ObjectURL', $request->getUrl()); + } + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Copy.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Copy.php new file mode 100644 index 0000000..06d464a --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Copy.php @@ -0,0 +1,140 @@ +client = $client; + $this->source = $source; + $this->options = $options; + $this->size = $contentlength; + $this->partSize = $this->calculatePartSize($minPartSize); + $this->concurrency = isset($options['concurrency']) ? $options['concurrency'] : 10; + $this->retry = isset($options['retry']) ? $options['retry'] : 5; + } + public function copy() { + $uploadId= $this->initiateMultipartUpload(); + for ($i = 0; $i < 5; $i += 1) { + $rt = $this->uploadParts($uploadId); + if ($rt == 0) { + break; + } + sleep(1 << $i); + } + return $this->client->completeMultipartUpload(array( + 'Bucket' => $this->options['Bucket'], + 'Key' => $this->options['Key'], + 'UploadId' => $uploadId, + 'Parts' => $this->parts)); + + } + public function uploadParts($uploadId) { + $commands = array(); + $offset = 0; + $partNumber = 1; + $partSize = $this->partSize; + $finishedNum = 0; + $this->parts = array(); + for (;;) { + + if ($offset + $partSize >= $this->size) + { + $partSize = $this->size - $offset; + } + $params = array( + 'Bucket' => $this->options['Bucket'], + 'Key' => $this->options['Key'], + 'UploadId' => $uploadId, + 'PartNumber' => $partNumber, + 'CopySource'=> $this->source, + 'CopySourceRange' => 'bytes='.((string)$offset).'-'.(string)($offset+$partSize - 1), + ); + if(!isset($parts[$partNumber])) { + $commands[] = $this->client->getCommand('UploadPartCopy', $params); + } + if ($partNumber % $this->concurrency == 0) { + $this->client->execute($commands); + $commands = array(); + } + ++$partNumber; + $offset += $partSize; + if ($this->size == $offset) + { + break; + } + } + if (!empty($commands)) { + $this->client->execute($commands); + } + try { + $marker = 0; + $finishedNum = 1; + while (true) { + $rt = $this->client->listParts(array( + 'Bucket' => $this->options['Bucket'], + 'Key' => $this->options['Key'], + 'PartNumberMarker' => $marker, + 'MaxParts' => 1000, + 'UploadId' => $uploadId)); + if (!empty($rt['Parts'])) { + foreach ($rt['Parts'] as $part) { + $part = array('PartNumber' => $finishedNum, 'ETag' => $part['ETag']); + $this->parts[$finishedNum] = $part; + $finishedNum++; + } + } + $marker = $rt['NextPartNumberMarker']; + if (!$rt['IsTruncated']) { + break; + } + } + } catch (\Exception $e) { + echo($e); + } + if ($finishedNum == $partNumber) { + return 0; + } else { + return -1; + } + + } + + + private function calculatePartSize($minPartSize) + { + $partSize = intval(ceil(($this->size / self::MAX_PARTS))); + $partSize = max($minPartSize, $partSize); + $partSize = min($partSize, self::MAX_PART_SIZE); + $partSize = max($partSize, self::MIN_PART_SIZE); + + return $partSize; + } + + private function initiateMultipartUpload() { + $result = $this->client->createMultipartUpload($this->options); + return $result['UploadId']; + } + +} +function partUploadCopy($client, $params) { + $rt = $client->uploadPartCopy($params); +// $part = array('PartNumber' => $params['PartNumber'], 'ETag' => $rt['ETag']); + $rt['PartNumber'] = $params['PartNumber']; + return $rt; +} \ No newline at end of file diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketAlreadyExistsException.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketAlreadyExistsException.php new file mode 100644 index 0000000..08de5ac --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Exception/BucketAlreadyExistsException.php @@ -0,0 +1,5 @@ +exceptionCode = $code; + } + + /** + * Get the exception code + * + * @return string|null + */ + public function getExceptionCode() { + return $this->exceptionCode; + } + + /** + * Set the exception type + * + * @param string $type Exception type + */ + public function setExceptionType($type) { + $this->exceptionType = $type; + } + + /** + * Get the exception type (one of client or server) + * + * @return string|null + */ + public function getExceptionType() { + return $this->exceptionType; + } + + /** + * Set the request ID + * + * @param string $id Request ID + */ + public function setRequestId($id) { + $this->requestId = $id; + } + + /** + * Get the Request ID + * + * @return string|null + */ + public function getRequestId() { + return $this->requestId; + } + + /** + * Set the associated response + * + * @param Response $response Response + */ + public function setResponse(Response $response) { + $this->response = $response; + } + + /** + * Get the associated response object + * + * @return Response|null + */ + public function getResponse() { + return $this->response; + } + + /** + * Set the associated request + * + * @param RequestInterface $request + */ + public function setRequest(RequestInterface $request) { + $this->request = $request; + } + + /** + * Get the associated request object + * + * @return RequestInterface|null + */ + public function getRequest() { + return $this->request; + } + + /** + * Get the status code of the response + * + * @return int|null + */ + public function getStatusCode() { + return $this->response ? $this->response->getStatusCode() : null; + } + + /** + * Cast to a string + * + * @return string + */ + public function __toString() { + $message = get_class($this) . ': ' + . 'Cos Error Code: ' . $this->getExceptionCode() . ', ' + . 'Status Code: ' . $this->getStatusCode() . ', ' + . 'Cos Request ID: ' . $this->getRequestId() . ', ' + . 'Cos Error Type: ' . $this->getExceptionType() . ', ' + . 'Cos Error Message: ' . $this->getMessage(); + + // Add the User-Agent if available + if ($this->request) { + $message .= ', ' . 'User-Agent: ' . $this->request->getHeader('User-Agent'); + } + + return $message; + } + + /** + * Get the request ID of the error. This value is only present if a + * response was received, and is not present in the event of a networking + * error. + * + * Same as `getRequestId()` method, but matches the interface for SDKv3. + * + * @return string|null Returns null if no response was received + */ + public function getCosRequestId() { + return $this->requestId; + } + + /** + * Get the Cos error type. + * + * Same as `getExceptionType()` method, but matches the interface for SDKv3. + * + * @return string|null Returns null if no response was received + */ + public function getCosErrorType() { + return $this->exceptionType; + } + + /** + * Get the Cos error code. + * + * Same as `getExceptionCode()` method, but matches the interface for SDKv3. + * + * @return string|null Returns null if no response was received + */ + public function getCosErrorCode() { + return $this->exceptionCode; + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionListener.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionListener.php new file mode 100644 index 0000000..a7c0b69 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionListener.php @@ -0,0 +1,69 @@ +parser = new ExceptionParser(); + $this->defaultException = 'Qcloud\Cos\Exception\ServiceResponseException'; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return array('request.error' => array('onRequestError', -1)); + } + + /** + * Throws a more meaningful request exception if available + * + * @param Event $event Event emitted + */ + public function onRequestError(Event $event) { + $e = $this->fromResponse($event['request'], $event['response']); + $event->stopPropagation(); + throw $e; + } + + public function fromResponse(RequestInterface $request, Response $response) { + $parts = $this->parser->parse($request, $response); + + $className = 'Qcloud\\Cos\\Exception\\' . $parts['code']; + if (substr($className, -9) !== 'Exception') { + $className .= 'Exception'; + } + + $className = class_exists($className) ? $className : $this->defaultException; + + return $this->createException($className, $request, $response, $parts); + } + + protected function createException($className, RequestInterface $request, Response $response, array $parts) { + $class = new $className($parts['message']); + + if ($class instanceof ServiceResponseException) { + $class->setExceptionCode($parts['code']); + $class->setExceptionType($parts['type']); + $class->setResponse($response); + $class->setRequest($request); + $class->setRequestId($parts['request_id']); + } + + return $class; + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionParser.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionParser.php new file mode 100644 index 0000000..ab1cd9b --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/ExceptionParser.php @@ -0,0 +1,112 @@ + null, + 'message' => null, + 'type' => $response->isClientError() ? 'client' : 'server', + 'request_id' => null, + 'parsed' => null + ); + + $body = $response->getBody(true); + + if (!$body) { + $this->parseHeaders($request, $response, $data); + return $data; + } + + try { + $xml = new \SimpleXMLElement($body); + $this->parseBody($xml, $data); + return $data; + } catch (\Exception $e) { + // Gracefully handle parse errors. This could happen when the + // server responds with a non-XML response (e.g., private beta + // services). + $data['code'] = 'PhpInternalXmlParseError'; + $data['message'] = 'A non-XML response was received'; + return $data; + } + } + + /** + * Parses additional exception information from the response headers + * + * @param RequestInterface $request Request that was issued + * @param Response $response The response from the request + * @param array $data The current set of exception data + */ + protected function parseHeaders(RequestInterface $request, Response $response, array &$data) { + $data['message'] = $response->getStatusCode() . ' ' . $response->getReasonPhrase(); + if ($requestId = $response->getHeader('x-cos-request-id')) { + $data['request_id'] = $requestId; + $data['message'] .= " (Request-ID: $requestId)"; + } + + // Get the request + $status = $response->getStatusCode(); + $method = $request->getMethod(); + + // Attempt to determine code for 403s and 404s + if ($status === 403) { + $data['code'] = 'AccessDenied'; + } elseif ($method === 'HEAD' && $status === 404) { + $path = explode('/', trim($request->getPath(), '/')); + $host = explode('.', $request->getHost()); + $bucket = (count($host) >= 4) ? $host[0] : array_shift($path); + $object = array_shift($path); + + if ($bucket && $object) { + $data['code'] = 'NoSuchKey'; + } elseif ($bucket) { + $data['code'] = 'NoSuchBucket'; + } + } + } + + /** + * Parses additional exception information from the response body + * + * @param \SimpleXMLElement $body The response body as XML + * @param array $data The current set of exception data + */ + protected function parseBody(\SimpleXMLElement $body, array &$data) { + $data['parsed'] = $body; + + $namespaces = $body->getDocNamespaces(); + if (isset($namespaces[''])) { + // Account for the default namespace being defined and PHP not being able to handle it :( + $body->registerXPathNamespace('ns', $namespaces['']); + $prefix = 'ns:'; + } else { + $prefix = ''; + } + + if ($tempXml = $body->xpath("//{$prefix}Code[1]")) { + $data['code'] = (string) $tempXml[0]; + } + + if ($tempXml = $body->xpath("//{$prefix}Message[1]")) { + $data['message'] = (string) $tempXml[0]; + } + + $tempXml = $body->xpath("//{$prefix}RequestId[1]"); + if (empty($tempXml)) { + $tempXml = $body->xpath("//{$prefix}RequestID[1]"); + } + if (isset($tempXml[0])) { + $data['request_id'] = (string) $tempXml[0]; + } + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Md5Listener.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Md5Listener.php new file mode 100644 index 0000000..9595600 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Md5Listener.php @@ -0,0 +1,57 @@ + 'onCommandAfterPrepare'); + } + + public function __construct(Signature $signature) + { + $this->signature = $signature; + } + + public function onCommandAfterPrepare(Event $event) + { + $command = $event['command']; + $operation = $command->getOperation(); + + if ($operation->getData('contentMd5')) { + // Add the MD5 if it is required for all signers + $this->addMd5($command); + } elseif ($operation->hasParam('ContentMD5')) { + $value = $command['ContentMD5']; + // Add a computed MD5 if the parameter is set to true or if + // not using Signature V4 and the value is not set (null). + if ($value === true || + ($value === null && !($this->signature instanceof SignatureV4)) + ) { + $this->addMd5($command); + } + } + } + + private function addMd5(CommandInterface $command) + { + $request = $command->getRequest(); + $body = $request->getBody(); + if ($body && $body->getSize() > 0) { + if (false !== ($md5 = $body->getContentMd5(true, true))) { + $request->setHeader('Content-MD5', $md5); + } + } + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/MultipartUpload.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/MultipartUpload.php new file mode 100644 index 0000000..30a942d --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/MultipartUpload.php @@ -0,0 +1,132 @@ +client = $client; + $this->source = $source; + $this->options = $options; + $this->partSize = $this->calculatePartSize($minPartSize); + } + + public function performUploading() { + $uploadId = $this->initiateMultipartUpload(); + + $partNumber = 1; + $parts = array(); + for (;;) { + if ($this->source->isConsumed()) { + break; + } + + $body = new ReadLimitEntityBody($this->source, $this->partSize, $this->source->ftell()); + if ($body->getContentLength() == 0) { + break; + } + $result = $this->client->uploadPart(array( + 'Bucket' => $this->options['Bucket'], + 'Key' => $this->options['Key'], + 'Body' => $body, + 'UploadId' => $uploadId, + 'PartNumber' => $partNumber)); + if (md5($body) != substr($result['ETag'], 1, -1)){ + throw new CosException("ETag check inconsistency"); + } + $part = array('PartNumber' => $partNumber, 'ETag' => $result['ETag']); + array_push($parts, $part); + ++$partNumber; + } + try { + $rt = $this->client->completeMultipartUpload(array( + 'Bucket' => $this->options['Bucket'], + 'Key' => $this->options['Key'], + 'UploadId' => $uploadId, + 'Parts' => $parts)); + } catch(\Exception $e){ + throw $e; + } + return $rt; + } + + public function resumeUploading() { + $uploadId = $this->options['UploadId']; + $rt = $this->client->ListParts( + array('UploadId' => $uploadId, + 'Bucket'=>$this->options['Bucket'], + 'Key'=>$this->options['Key'])); + $parts = array(); + $offset = $this->partSize; + if (count($rt['Parts']) > 0) { + foreach ($rt['Parts'] as $part) { + $parts[$part['PartNumber'] - 1] = array('PartNumber' => $part['PartNumber'], 'ETag' => $part['ETag']); + } + } + for ($partNumber = 1;;++$partNumber,$offset+=$body->getContentLength()) { + if ($this->source->isConsumed()) { + break; + } + + $body = new ReadLimitEntityBody($this->source, $this->partSize, $this->source->ftell()); + if ($body->getContentLength() == 0) { + break; + } + + + if (array_key_exists($partNumber-1,$parts)){ + + if (md5($body) != substr($parts[$partNumber-1]['ETag'], 1, -1)){ + throw new CosException("ETag check inconsistency"); + } + $body->setOffset($offset); + continue; + } + + $result = $this->client->uploadPart(array( + 'Bucket' => $this->options['Bucket'], + 'Key' => $this->options['Key'], + 'Body' => $body, + 'UploadId' => $uploadId, + 'PartNumber' => $partNumber)); + if (md5($body) != substr($result['ETag'], 1, -1)){ + throw new CosException("ETag check inconsistency"); + } + $parts[$partNumber-1] = array('PartNumber' => $partNumber, 'ETag' => $result['ETag']); + + } + $rt = $this->client->completeMultipartUpload(array( + 'Bucket' => $this->options['Bucket'], + 'Key' => $this->options['Key'], + 'UploadId' => $uploadId, + 'Parts' => $parts)); + return $rt; + } + + private function calculatePartSize($minPartSize) { + $partSize = intval(ceil(($this->source->getContentLength() / self::MAX_PARTS))); + $partSize = max($minPartSize, $partSize); + $partSize = min($partSize, self::MAX_PART_SIZE); + $partSize = max($partSize, self::MIN_PART_SIZE); + + return $partSize; + } + + private function initiateMultipartUpload() { + $result = $this->client->createMultipartUpload($this->options); + return $result['UploadId']; + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Service.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Service.php new file mode 100644 index 0000000..a2d0abb --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Service.php @@ -0,0 +1,4328 @@ + 'Cos Service', + 'apiVersion' => 'V5', + 'description' => 'Cos V5 API Service', + + 'operations' => array( + /** + 舍弃一个分块上传且删除已上传的分片块的方法. + + COS 支持舍弃一个分块上传且删除已上传的分片块. 注意,已上传但是未终止的分片块会占用存储空间进 而产生存储费用.因此,建议及时完成分块上传 或者舍弃分块上传. + + 关于分块上传的具体描述,请查看 https://cloud.tencent.com/document/product/436/14112. + + 关于舍弃一个分块上传且删除已上传的分片块接口的描述,请查看 https://cloud.tencent.com/document/product/436/7740. + + cos php SDK 中舍弃一个分块上传且删除已上传的分片块请求的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 AbortMultipfartUpload 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则操作成功。 + */ + 'AbortMultipartUpload' => array( + 'httpMethod' => 'DELETE', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'AbortMultipartUploadOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1), + 'UploadId' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'uploadId')), + 'errorResponses' => array( + array( + 'reason' => 'The specified multipart upload does not exist.', + 'class' => 'NoSuchUploadException'))), + /** + 创建存储桶(Bucket)的方法. + + 在开始使用 COS 时,需要在指定的账号下先创建一个 Bucket 以便于对象的使用和管理. 并指定 Bucket 所属的地域.创建 Bucket 的用户默认成为 Bucket 的持有者.若创建 Bucket 时没有指定访问权限,则默认 为私有读写(private)权限. + + 可用地域,可以查看https://cloud.tencent.com/document/product/436/6224. + + 关于创建 Bucket 描述,请查看 https://cloud.tencent.com/document/product/436/14106. + + 关于创建存储桶(Bucket)接口的具体 描述,请查看 https://cloud.tencent.com/document/product/436/7738. + + cos php SDK 中创建 Bucket的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 CreateBucket 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则创建成功。 + + 示例: + $result = $cosClient->createBucket(array('Bucket' => 'testbucket-1252448703')); + */ + 'CreateBucket' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'CreateBucketOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'CreateBucketConfiguration')), + 'parameters' => array( + 'ACL' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-acl'), + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri')), + 'errorResponses' => array( + array( + 'reason' => 'The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.', + 'class' => 'BucketAlreadyExistsException'))), + /** + 完成整个分块上传的方法. + + 当使用分块上传(uploadPart(UploadPartRequest))完对象的所有块以后,必须调用该 completeMultiUpload(CompleteMultiUploadRequest) 或者 completeMultiUploadAsync(CompleteMultiUploadRequest, CosXmlResultListener) 来完成整个文件的分块上传.且在该请求的 Body 中需要给出每一个块的 PartNumber 和 ETag,用来校验块的准 确性. + + 分块上传适合于在弱网络或高带宽环境下上传较大的对象.SDK 支持自行切分对象并分别调用uploadPart(UploadPartRequest)上传各 个分块. + + 关于分块上传的描述,请查看 https://cloud.tencent.com/document/product/436/14112. + + 关于完成整个分片上传接口的描述,请查看 https://cloud.tencent.com/document/product/436/7742. + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 CompleteMultipartUpload 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则操作成功。 + + */ + 'CompleteMultipartUpload' => array( + 'httpMethod' => 'POST', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'CompleteMultipartUploadOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'CompleteMultipartUpload')), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1), + 'Parts' => array( + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true), + 'items' => array( + 'name' => 'CompletedPart', + 'type' => 'object', + 'sentAs' => 'Part', + 'properties' => array( + 'ETag' => array( + 'type' => 'string'), + 'PartNumber' => array( + 'type' => 'numeric')))), + 'UploadId' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'uploadId'), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml'))), + 'CreateMultipartUpload' => array( + 'httpMethod' => 'POST', + 'uri' => '/{Bucket}{/Key*}?uploads', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'CreateMultipartUploadOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'CreateMultipartUploadRequest')), + 'parameters' => array( + 'ACL' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-acl', + ), + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'CacheControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Cache-Control', + ), + 'ContentDisposition' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Disposition', + ), + 'ContentEncoding' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Encoding', + ), + 'ContentLanguage' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Language', + ), + 'ContentType' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Type', + ), + 'Expires' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time-http', + 'location' => 'header', + ), + 'GrantFullControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-full-control', + ), + 'GrantRead' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-read', + ), + 'GrantReadACP' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-read-acp', + ), + 'GrantWriteACP' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-write-acp', + ), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1, + ), + 'Metadata' => array( + 'type' => 'object', + 'location' => 'header', + 'sentAs' => 'x-cos-meta-', + 'additionalProperties' => array( + 'type' => 'string', + ), + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'StorageClass' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-storage-class', + ), + 'WebsiteRedirectLocation' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-website-redirect-location', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKey' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + 'ACP' => array( + 'type' => 'object', + 'additionalProperties' => true, + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ))), + 'CopyObject' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'CopyObjectOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'CopyObjectRequest', + ), + ), + 'parameters' => array( + 'ACL' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-acl', + ), + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'CacheControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Cache-Control', + ), + 'ContentDisposition' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Disposition', + ), + 'ContentEncoding' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Encoding', + ), + 'ContentLanguage' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Language', + ), + 'ContentType' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Type', + ), + 'CopySource' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source', + ), + 'CopySourceIfMatch' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-if-match', + ), + 'CopySourceIfModifiedSince' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time-http', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-if-modified-since', + ), + 'CopySourceIfNoneMatch' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-if-none-match', + ), + 'CopySourceIfUnmodifiedSince' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time-http', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-if-unmodified-since', + ), + 'Expires' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time-http', + 'location' => 'header', + ), + 'GrantFullControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-full-control', + ), + 'GrantRead' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-read', + ), + 'GrantReadACP' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-read-acp', + ), + 'GrantWriteACP' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-write-acp', + ), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1, + ), + 'Metadata' => array( + 'type' => 'object', + 'location' => 'header', + 'sentAs' => 'x-cos-meta-', + 'additionalProperties' => array( + 'type' => 'string', + ), + ), + 'MetadataDirective' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-metadata-directive', + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'StorageClass' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-storage-class', + ), + 'WebsiteRedirectLocation' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-website-redirect-location', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKey' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key', + ), + 'CopySourceSSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-algorithm', + ), + 'CopySourceSSECustomerKey' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-key', + ), + 'CopySourceSSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-key-MD5', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + 'ACP' => array( + 'type' => 'object', + 'additionalProperties' => true, + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + 'errorResponses' => array( + array( + 'reason' => 'The source object of the COPY operation is not in the active tier.', + 'class' => 'ObjectNotInActiveTierErrorException', + ), + ), + ), + /** + 删除存储桶 (Bucket)的方法. + + COS 目前仅支持删除已经清空的 Bucket,如果 Bucket 中仍有对象,将会删除失败. 因此,在执行删除 Bucket 前,需确保 Bucket 内已经没有对象. 删除 Bucket 时,还需要确保操作的身份已被授权该操作,并确认 传入了正确的存储桶名称和地域参数, 请参阅 putBucket(PutBucketRequest). + + 关于删除 Bucket 的描述,请查看 https://cloud.tencent.com/document/product/436/14105. + + 关于删除 Bucket 接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7732. + + cos php SDK 中删除 Bucket 的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 DeleteBucket 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,删除成功。 + + 示例: + $result = $cosClient->deleteBucket(array( + 'Bucket' => 'testbucket-1252448703')); + print_r($result); + */ + 'DeleteBucket' => array( + 'httpMethod' => 'DELETE', + 'uri' => '/{Bucket}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'DeleteBucketOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'))), + /** + 删除跨域访问配置信息的方法. + + 若是 Bucket 不需要支持跨域访问配置,可以调用此接口删除已配置的跨域访问信息. 跨域访问配置可以通过 putBucketCORS(PutBucketCORSRequest) 或者 putBucketCORSAsync(PutBucketCORSRequest, CosXmlResultListener) 方法来开启 Bucket 的跨域访问 支持. + + 关于删除跨域访问配置信息接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8283. + + cos php SDK 中删除跨域访问配置信息的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 DeleteBucketCORS 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,删除成功。 + + 示例: + $result = $cosClient->deleteBucketCors(array( + // Bucket is required + 'Bucket' => 'testbucket-1252448703', + )); + */ + 'DeleteBucketCors' => array( + 'httpMethod' => 'DELETE', + 'uri' => '/{Bucket}?cors', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'DeleteBucketCorsOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + ), + ), + /** + 删除 COS 上单个对象的方法. + + COS 支持直接删除一个或多个对象,当仅需要删除一个对象时,只需要提供对象的名称(即对象键)即可. + + 关于删除 COS 上单个对象的具体描述,请查看 https://cloud.tencent.com/document/product/436/14119. + + 关于删除 COS 上单个对象接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/7743. + + cos php SDK 中删除 COS 上单个对象请求的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 DeleteObject 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则删除成功。 + + 示例: + $result = $cosClient->deleteObject(array( + 'Bucket' => 'testbucket-1252448703', + 'Key' => '111.txt', + 'VersionId' => 'string')); + */ + 'DeleteObject' => array( + 'httpMethod' => 'DELETE', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'DeleteObjectOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1), + 'MFA' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-mfa', + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'versionId', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ),)), + /** + 批量删除 COS 对象的方法. + + COS 支持批量删除指定 Bucket 中 对象,单次请求最大支持批量删除 1000 个 对象. 请求中删除一个不存在的对象,仍然认为是成功的. 对于响应结果,COS提供 Verbose 和 Quiet 两种模式:Verbose 模式将返回每个对象的删除结果;Quiet 模式只返回删除报错的对象信息. 请求必须携带 Content-MD5 用来校验请求Body 的完整性. + + 关于批量删除 COS 对象接口的描述,请查看https://cloud.tencent.com/document/product/436/8289. + + cos php SDK 中批量删除 COS 对象的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 DeleteObjects 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则删除成功。 + + 示例: + $result = $cosClient->deleteObjects(array( + // Bucket is required + 'Bucket' => 'testbucket-1252448703', + // Objects is required + 'Objects' => array( + array( + // Key is required + 'Key' => 'string', + 'VersionId' => 'string', + ), + // ... repeated + ), + )); + */ + 'DeleteObjects' => array( + 'httpMethod' => 'POST', + 'uri' => '/{Bucket}?delete', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'DeleteObjectsOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'Delete', + ), + 'contentMd5' => true, + ), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'Objects' => array( + 'required' => true, + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'ObjectIdentifier', + 'type' => 'object', + 'sentAs' => 'Object', + 'properties' => array( + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'minLength' => 1, + ), + 'VersionId' => array( + 'type' => 'string', + ), + ), + ), + ), + 'Quiet' => array( + 'type' => 'boolean', + 'format' => 'boolean-string', + 'location' => 'xml', + ), + 'MFA' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-mfa', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + /** + 删除存储桶(Bucket) 的生命周期配置的方法. + + COS 支持删除已配置的 Bucket 的生命周期列表. COS 支持以生命周期配置的方式来管理 Bucket 中 对象的生命周期,生命周期配置包含一个或多个将 应用于一组对象规则的规则集 (其中每个规则为 COS 定义一个操作),请参阅 putBucketLifecycle(PutBucketLifecycleRequest). + + 关于删除 Bucket 的生命周期配置接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8284. + + cos php SDK 中删除 Bucket 的生命周期配置的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 DeleteBucketLifeCycle 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,删除成功。 + + 示例: + $result = $cosClient->deleteBucketLifecycle(array( + // Bucket is required + 'Bucket' =>'testbucket-1252448703', + )); + */ + 'DeleteBucketLifecycle' => array( + 'httpMethod' => 'DELETE', + 'uri' => '/{Bucket}?lifecycle', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'DeleteBucketLifecycleOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + ), + ), + /** + 删除跨区域复制配置的方法. + + 当不需要进行跨区域复制时,可以删除 Bucket 的跨区域复制配置. 跨区域复制,可以查阅putBucketReplication(PutBucketReplicationRequest) + + cos php SDK 中删除跨区域复制配置的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 DeleteBucketReplication 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,获取成功。 + + */ + 'DeleteBucketReplication' => array( + 'httpMethod' => 'DELETE', + 'uri' => '/{Bucket}?replication', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'DeleteBucketReplicationOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + ), + ), + 'GetObject' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetObjectOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'IfMatch' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'If-Match'), + 'IfModifiedSince' => array( + 'type' => array( + 'object', + 'string', + 'integer'), + 'format' => 'date-time-http', + 'location' => 'header', + 'sentAs' => 'If-Modified-Since'), + 'IfNoneMatch' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'If-None-Match'), + 'IfUnmodifiedSince' => array( + 'type' => array( + 'object', + 'string', + 'integer'), + 'format' => 'date-time-http', + 'location' => 'header', + 'sentAs' => 'If-Unmodified-Since'), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1), + 'Range' => array( + 'type' => 'string', + 'location' => 'header'), + 'ResponseCacheControl' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'response-cache-control'), + 'ResponseContentDisposition' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'response-content-disposition'), + 'ResponseContentEncoding' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'response-content-encoding'), + 'ResponseContentLanguage' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'response-content-language'), + 'ResponseContentType' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'response-content-type'), + 'ResponseExpires' => array( + 'type' => array( + 'object', + 'string', + 'integer'), + 'format' => 'date-time-http', + 'location' => 'query', + 'sentAs' => 'response-expires'), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'versionId', + ), + 'SaveAs' => array( + 'location' => 'response_body')), + 'errorResponses' => array( + array( + 'reason' => 'The specified key does not exist.', + 'class' => 'NoSuchKeyException'))), + /** + 获取 COS 对象的访问权限信息(Access Control List, ACL)的方法. + + Bucket 的持有者可获取该 Bucket 下的某个对象的 ACL 信息,如被授权者以及被授权的信息. ACL 权限包括读、写、读写权限. + + 关于获取 COS 对象的 ACL 接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7744. + + cos php SDK 中获取 COS 对象的 ACL 的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 GetObjectAcl 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。 + + 示例: + $result = $cosClient->getObjectAcl(array( + 'Bucket' => 'testbucket-1252448703', + 'Key' => '11')); + */ + 'GetObjectAcl' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}{/Key*}?acl', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetObjectAclOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1, + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'versionId', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + 'errorResponses' => array( + array( + 'reason' => 'The specified key does not exist.', + 'class' => 'NoSuchKeyException', + ), + ), + ), + /** + 获取存储桶(Bucket) 的访问权限信息(Access Control List, ACL)的方法. + + ACL 权限包括读、写、读写权限. COS 中 Bucket 是有访问权限控制的.可以通过获取 Bucket 的 ACL 表(putBucketACL(PutBucketACLRequest)),来查看那些用户拥有 Bucket 访 问权限. + + 关于获取 Bucket 的 ACL 接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/7733. + + cos php SDK 中获取 Bucket 的 ACL 的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 GetBucketACL 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。 + + + 示例: + $result = $cosClient->GetBucketAcl(array( + 'Bucket' => 'testbucket-1252448703',)); + */ + 'GetBucketAcl' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?acl', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetBucketAclOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml'))), + /** + 查询存储桶(Bucket) 跨域访问配置信息的方法. + + COS 支持查询当前 Bucket 跨域访问配置信息,以确定是否配置跨域信息.当跨域访问配置不存在时,请求 返回403 Forbidden. 跨域访问配置可以通过 putBucketCORS(PutBucketCORSRequest) 或者 putBucketCORSAsync(PutBucketCORSRequest, CosXmlResultListener) 方法来开启 Bucket 的跨域访问 支持. + + 关于查询 Bucket 跨域访问配置信息接口的具体描述, 请查看 https://cloud.tencent.com/document/product/436/8274. + + cos php SDK 中查询 Bucket 跨域访问配置信息的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 GetBucketCORS 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。 + + + 示例: + $result = $cosClient->getBucketCors(array( + // Bucket is required + 'Bucket' => 'testbucket-1252448703', + )); + */ + 'GetBucketCors' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?cors', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetBucketCorsOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + /** + 查询存储桶(Bucket) 的生命周期配置的方法. + + COS 支持以生命周期配置的方式来管理 Bucket 中对象的生命周期,生命周期配置包含一个或多个将 应用于一组对象规则的规则集 (其中每个规则为 COS 定义一个操作),请参阅 putBucketLifecycle(PutBucketLifecycleRequest). + + 关于查询 Bucket 的生命周期配置接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8278. + + cos php SDK 中查询 Bucket 的生命周期配置的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 GetBucketLifecycle 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。 + + + 示例: + $result = $cosClient->getBucketLifecycle(array( + 'Bucket' => 'testbucket-1252448703', + )); + */ + 'GetBucketLifecycle' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?lifecycle', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetBucketLifecycleOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + /** + 获取存储桶(Bucket)版本控制信息的方法. + + 通过查询版本控制信息,可以得知该 Bucket 的版本控制功能是处于禁用状态还是启用状态(Enabled 或者 Suspended), 开启版本控制功能,可参考putBucketVersioning(PutBucketVersioningRequest). + + cos php SDK 中获取 Bucket 版本控制信息的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 GetBucketVersioning 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,获取成功。 + + 示例: + $result = $cosClient->getBucketVersioning( + array('Bucket' => 'lewzylu02-1252448703')); + */ + 'GetBucketVersioning' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?versioning', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetBucketVersioningOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + /** + 获取跨区域复制配置信息的方法. + + 跨区域复制是支持不同区域 Bucket 自动复制对象, 请查阅putBucketReplication(PutBucketReplicationRequest). + + cos php SDK 中获取跨区域复制配置信息的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 GetBucketReplication 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,设置成功。 + + */ + 'GetBucketReplication' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?replication', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetBucketReplicationOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + /** + 获取存储桶(Bucket) 所在的地域信息的方法. + + 在创建 Bucket 时,需要指定所属该 Bucket 所属地域信息. + + COS 支持的地域信息,可查看https://cloud.tencent.com/document/product/436/6224. + + 关于获取 Bucket 所在的地域信息接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8275. + + cos php SDK 中获取 Bucket 所在的地域信息的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 GetBucketLocation 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则获取成功。 + + + 示例: + $result = $cosClient->getBucketLocation(array( + 'Bucket' => 'testbucket-1252448703', + )); + */ + 'GetBucketLocation' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?location', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetBucketLocationOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + ), + ), + 'GetBucketNotification' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?notification', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'GetBucketNotificationOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + 'UploadPart' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'UploadPartOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'UploadPartRequest')), + 'parameters' => array( + 'Body' => array( + 'type' => array( + 'string', + 'object'), + 'location' => 'body'), + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'ContentLength' => array( + 'type' => 'numeric', + 'location' => 'header', + 'sentAs' => 'Content-Length'), + 'ContentMD5' => array( + 'type' => array( + 'string', + 'boolean'), + 'location' => 'header', + 'sentAs' => 'Content-MD5'), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1), + 'PartNumber' => array( + 'required' => true, + 'type' => 'numeric', + 'location' => 'query', + 'sentAs' => 'partNumber'), + 'UploadId' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'uploadId'), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKey' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ))), + 'PutObject' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'PutObjectOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'PutObjectRequest')), + 'parameters' => array( + 'ACL' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-acl'), + 'Body' => array( + 'type' => array( + 'string', + 'object'), + 'location' => 'body'), + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'CacheControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Cache-Control'), + 'ContentDisposition' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Disposition'), + 'ContentEncoding' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Encoding'), + 'ContentLanguage' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Language'), + 'ContentLength' => array( + 'type' => 'numeric', + 'location' => 'header', + 'sentAs' => 'Content-Length'), + 'ContentMD5' => array( + 'type' => array( + 'string', + 'boolean'), + 'location' => 'header', + 'sentAs' => 'Content-MD5'), + 'ContentType' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Type'), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1), + 'Metadata' => array( + 'type' => 'object', + 'location' => 'header', + 'sentAs' => 'x-cos-meta-', + 'additionalProperties' => array( + 'type' => 'string') + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'StorageClass' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-storage-class', + ), + 'WebsiteRedirectLocation' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-website-redirect-location', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKey' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + 'ACP' => array( + 'type' => 'object', + 'additionalProperties' => true, + ))), + /** + 设置 COS 对象的访问权限信息(Access Control List, ACL)的方法. + + ACL权限包括读、写、读写权限. COS 对象的 ACL 可以通过 header头部:"x-cos-acl","x-cos-grant-read","x-cos-grant-write", "x-cos-grant-full-control" 传入 ACL 信息,或者通过 Body 以 XML 格式传入 ACL 信息.这两种方式只 能选择其中一种,否则引起冲突. 传入新的 ACL 将覆盖原有 ACL信息.ACL策略数上限1000,建议用户不要每个上传文件都设置 ACL. + + 关于设置 COS 对象的ACL接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7748. + + cos PHP SDK 中设置 COS 对象的 ACL 的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 PutObjectAcl 对象中的方法发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则设置成功。 + + 示例: + $cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '11', + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::uin/327874225:uin/327874225', + 'ID' => 'qcs::cam::uin/327874225:uin/327874225', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + ))); + */ + 'PutObjectAcl' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}{/Key*}?acl', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'PutObjectAclOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'AccessControlPolicy', + ), + ), + 'parameters' => array( + 'ACL' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-acl', + ), + 'Grants' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'AccessControlList', + 'items' => array( + 'name' => 'Grant', + 'type' => 'object', + 'properties' => array( + 'Grantee' => array( + 'type' => 'object', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string'), + 'ID' => array( + 'type' => 'string'), + 'Type' => array( + 'type' => 'string', + 'sentAs' => 'xsi:type', + 'data' => array( + 'xmlAttribute' => true, + 'xmlNamespace' => 'http://www.w3.org/2001/XMLSchema-instance')), + 'URI' => array( + 'type' => 'string') )), + 'Permission' => array( + 'type' => 'string', + ), + ), + ), + ), + 'Owner' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string', + ), + 'ID' => array( + 'type' => 'string', + ), + ), + ), + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'GrantFullControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-full-control', + ), + 'GrantRead' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-read', + ), + 'GrantReadACP' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-read-acp', + ), + 'GrantWrite' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-write', + ), + 'GrantWriteACP' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-write-acp', + ), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1, + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + 'ACP' => array( + 'type' => 'object', + 'additionalProperties' => true, + ), + ), + 'errorResponses' => array( + array( + 'reason' => 'The specified key does not exist.', + 'class' => 'NoSuchKeyException', + ), + ), + ), + /** + 设置存储桶(Bucket) 的访问权限(Access Control List, ACL)的方法. + + ACL 权限包括读、写、读写权限. 写入 Bucket 的 ACL 可以通过 header头部:"x-cos-acl","x-cos-grant-read","x-cos-grant-write", "x-cos-grant-full-control" 传入 ACL 信息,或者通过 Body 以 XML 格式传入 ACL 信息.这两种方式只 能选择其中一种,否则引起冲突. 传入新的 ACL 将覆盖原有 ACL信息. 私有 Bucket 可以下可以给某个文件夹设置成公有,那么该文件夹下的文件都是公有;但是把文件夹设置成私有后,在该文件夹下的文件设置 的公有属性,不会生效. + + 关于设置 Bucket 的ACL接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/7737. + + cos php SDK 中设置 Bucket 的ACL的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 PutObjectAcl 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,设置成功。 + + + 示例: + $result = $cosClient->PutObjectAcl(array( + 'Bucket' => 'testbucket-1252448703', + 'Key' => '111', + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::uin/327874225:uin/327874225', + 'ID' => 'qcs::cam::uin/327874225:uin/327874225', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/3210232098:uin/3210232098', + 'ID' => 'qcs::cam::uin/3210232098:uin/3210232098', + ),)); + */ + 'PutBucketAcl' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}?acl', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'PutBucketAclOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'AccessControlPolicy', + ), + ), + 'parameters' => array( + 'ACL' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-acl', + ), + 'Grants' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'AccessControlList', + 'items' => array( + 'name' => 'Grant', + 'type' => 'object', + 'properties' => array( + 'Grantee' => array( + 'type' => 'object', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string', + ), + 'EmailAddress' => array( + 'type' => 'string', + ), + 'ID' => array( + 'type' => 'string', + ), + 'Type' => array( + 'required' => true, + 'type' => 'string', + 'sentAs' => 'xsi:type', + 'data' => array( + 'xmlAttribute' => true, + 'xmlNamespace' => 'http://www.w3.org/2001/XMLSchema-instance', + ), + ), + 'URI' => array( + 'type' => 'string', + ), + ), + ), + 'Permission' => array( + 'type' => 'string', + ), + ), + ), + ), + 'Owner' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string', + ), + 'ID' => array( + 'type' => 'string', + ), + ), + ), + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'GrantFullControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-full-control', + ), + 'GrantRead' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-read', + ), + 'GrantReadACP' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-read-acp', + ), + 'GrantWrite' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-write', + ), + 'GrantWriteACP' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-grant-write-acp', + ), + 'ACP' => array( + 'type' => 'object', + 'additionalProperties' => true, + ), + ), + ), + /** + 设置存储桶(Bucket) 的跨域配置信息的方法. + + 跨域访问配置的预请求是指在发送跨域请求之前会发送一个 OPTIONS 请求并带上特定的来源域,HTTP 方 法和 header 信息等给 COS,以决定是否可以发送真正的跨域请求. 当跨域访问配置不存在时,请求返回403 Forbidden. + + 默认情况下,Bucket的持有者可以直接配置 Bucket的跨域信息 ,Bucket 持有者也可以将配置权限授予其他用户.新的配置是覆盖当前的所有配置信 息,而不是新增一条配置.可以通过传入 XML 格式的配置文件来实现配置,文件大小限制为64 KB. + + 关于设置 Bucket 的跨域配置信息接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/8279. + + cos php SDK 中设置 Bucket 的跨域配置信息的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 PutBucketCORS 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,设置成功。 + + 示例: + $result = $cosClient->putBucketCors(array( + // Bucket is required + 'Bucket' => 'testbucket-1252448703', + // CORSRules is required + 'CORSRules' => array( + array( + 'ID' => '1234', + 'AllowedHeaders' => array('*'), + // AllowedMethods is required + 'AllowedMethods' => array('PUT'), + // AllowedOrigins is required + 'AllowedOrigins' => array('http://www.qq.com', ), + ), + // ... repeated + ), + )); + */ + 'PutBucketCors' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}?cors', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'PutBucketCorsOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'CORSConfiguration', + ), + 'contentMd5' => true, + ), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'CORSRules' => array( + 'required' => true, + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'CORSRule', + 'type' => 'object', + 'sentAs' => 'CORSRule', + 'properties' => array( + 'ID' => array( + 'type' => 'string', + ), + 'AllowedHeaders' => array( + 'type' => 'array', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'AllowedHeader', + 'type' => 'string', + 'sentAs' => 'AllowedHeader', + ), + ), + 'AllowedMethods' => array( + 'required' => true, + 'type' => 'array', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'AllowedMethod', + 'type' => 'string', + 'sentAs' => 'AllowedMethod', + ), + ), + 'AllowedOrigins' => array( + 'required' => true, + 'type' => 'array', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'AllowedOrigin', + 'type' => 'string', + 'sentAs' => 'AllowedOrigin', + ), + ), + 'ExposeHeaders' => array( + 'type' => 'array', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'ExposeHeader', + 'type' => 'string', + 'sentAs' => 'ExposeHeader', + ), + ), + 'MaxAgeSeconds' => array( + 'type' => 'numeric', + ), + ), + ), + ), + ), + ), + /** + 设置存储桶(Bucket) 生命周期配置的方法. + + COS 支持以生命周期配置的方式来管理 Bucket 中对象的生命周期. 如果该 Bucket 已配置生命周期,新的配置的同时则会覆盖原有的配置. 生命周期配置包含一个或多个将应用于一组对象规则的规则集 (其中每个规则为 COS 定义一个操作)。这些操作分为以下两种:转换操作,过期操作. + + 转换操作,定义对象转换为另一个存储类的时间(例如,您可以选择在对象创建 30 天后将其转换为低频存储类别,同 时也支持将数据沉降到归档存储类别. + + 过期操作,指定 Object 的过期时间,COS 将会自动为用户删除过期的 Object. + + 关于Bucket 生命周期配置接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/8280 + + cos php SDK 中Bucket 生命周期配置的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 PutBucketLifecycle 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,设置成功。 + + 示例: + $result = $cosClient->putBucketLifecycle(array( + // Bucket is required + 'Bucket' => 'lewzylu06-1252448703', + // Rules is required + 'Rules' => array( + array( + 'Expiration' => array( + 'Days' => 1000, + ), + 'ID' => 'id1', + 'Filter' => array( + 'Prefix' => 'documents/' + ), + // Status is required + 'Status' => 'Enabled', + 'Transitions' => array( + array( + 'Days' => 200, + 'StorageClass' => 'NEARLINE'), + ), + // ... repeated + ), + ))); + */ + 'PutBucketLifecycle' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}?lifecycle', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'PutBucketLifecycleOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'LifecycleConfiguration', + ), + 'contentMd5' => true, + ), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'Rules' => array( + 'required' => true, + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'Rule', + 'type' => 'object', + 'sentAs' => 'Rule', + 'properties' => array( + 'Expiration' => array( + 'type' => 'object', + 'properties' => array( + 'Date' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time', + ), + 'Days' => array( + 'type' => 'numeric', + ), + ), + ), + 'ID' => array( + 'type' => 'string', + ), + 'Filter' => array( + 'type' => 'object', + 'require' => true, + 'properties' => array( + 'Prefix' => array( + 'type' => 'string', + 'require' => true, + ), + 'Tag' => array( + 'type' => 'object', + 'require' => true, + 'properties' => array( + 'Key' => array( + 'type' => 'string' + ), + 'Value' => array( + 'type' => 'string' + ), + ) + ) + ), + ), + 'Status' => array( + 'required' => true, + 'type' => 'string', + ), + 'Transitions' => array( + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'Transition', + 'type' => 'object', + 'sentAs' => 'Transition', + 'properties' => array( + 'Date' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time', + ), + 'Days' => array( + 'type' => 'numeric', + ), + 'StorageClass' => array( + 'type' => 'string', + )))), + 'NoncurrentVersionTransition' => array( + 'type' => 'object', + 'properties' => array( + 'NoncurrentDays' => array( + 'type' => 'numeric', + ), + 'StorageClass' => array( + 'type' => 'string', + ), + ), + ), + 'NoncurrentVersionExpiration' => array( + 'type' => 'object', + 'properties' => array( + 'NoncurrentDays' => array( + 'type' => 'numeric', + ), + ), + ), + ), + ), + ), + ), + ), + /** + 存储桶(Bucket)版本控制的方法. + + 版本管理功能一经打开,只能暂停,不能关闭. 通过版本控制,可以在一个 Bucket 中保留一个对象的多个版本. 版本控制可以防止意外覆盖和删除对象,以便检索早期版本的对象. 默认情况下,版本控制功能处于禁用状态,需要主动去启用或者暂停(Enabled 或者 Suspended). + + cos php SDK 中 Bucket 版本控制启用或者暂停的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 PutBucketVersioning 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,设置成功。 + + 示例: + $result = $cosClient->putBucketVersioning( + array('Bucket' => 'testbucket-1252448703', + 'Status' => 'Enabled')); + */ + 'PutBucketVersioning' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}?versioning', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'PutBucketVersioningOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'VersioningConfiguration', + ), + ), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'MFA' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-mfa', + ), + 'MFADelete' => array( + 'type' => 'string', + 'location' => 'xml', + 'sentAs' => 'MfaDelete', + ), + 'Status' => array( + 'type' => 'string', + 'location' => 'xml', + ), + ), + ), + /** + 配置跨区域复制的方法. + + 跨区域复制是支持不同区域 Bucket 自动异步复制对象.注意,不能是同区域的 Bucket, 且源 Bucket 和目 标 Bucket 必须已启用版本控制putBucketVersioning(PutBucketVersioningRequest). + + cos php SDK 中配置跨区域复制的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 PutBucketRelication 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,设置成功。 + + 示例: + + */ + 'PutBucketReplication' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}?replication', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'PutBucketReplicationOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'ReplicationConfiguration', + ), + 'contentMd5' => true, + ), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'Role' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'xml', + ), + 'Rules' => array( + 'required' => true, + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'ReplicationRule', + 'type' => 'object', + 'sentAs' => 'Rule', + 'properties' => array( + 'ID' => array( + 'type' => 'string', + ), + 'Prefix' => array( + 'required' => true, + 'type' => 'string', + ), + 'Status' => array( + 'required' => true, + 'type' => 'string', + ), + 'Destination' => array( + 'required' => true, + 'type' => 'object', + 'properties' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + ), + 'StorageClass' => array( + 'type' => 'string', + ), + ), + ), + ), + ), + ), + ), + ), + /** + 设置存储桶(Bucket) 的回调设置的方法. + */ + 'PutBucketNotification' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}?notification', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'PutBucketNotificationOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'NotificationConfiguration', + ), + ), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'CloudFunctionConfigurations' => array( + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'CloudFunctionConfiguration', + 'type' => 'object', + 'sentAs' => 'CloudFunctionConfiguration', + 'properties' => array( + 'Id' => array( + 'type' => 'string', + ), + 'CloudFunction' => array( + 'required' => true, + 'type' => 'string', + 'sentAs' => 'CloudFunction', + ), + 'Events' => array( + 'required' => true, + 'type' => 'array', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'Event', + 'type' => 'string', + 'sentAs' => 'Event', + ), + ), + 'Filter' => array( + 'type' => 'object', + 'properties' => array( + 'Key' => array( + 'type' => 'object', + 'sentAs' => 'Key', + 'properties' => array( + 'FilterRules' => array( + 'type' => 'array', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'FilterRule', + 'type' => 'object', + 'sentAs' => 'FilterRule', + 'properties' => array( + 'Name' => array( + 'type' => 'string', + ), + 'Value' => array( + 'type' => 'string', + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + 'RestoreObject' => array( + 'httpMethod' => 'POST', + 'uri' => '/{Bucket}{/Key*}?restore', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'RestoreObjectOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'RestoreRequest', + ), + ), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1, + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'versionId', + ), + 'Days' => array( + 'required' => true, + 'type' => 'numeric', + 'location' => 'xml', + ), + 'CASJobParameters' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'Tier' => array( + 'type' => 'string', + 'required' => true, + ), + ), + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + ), + 'errorResponses' => array( + array( + 'reason' => 'This operation is not allowed against this storage tier', + 'class' => 'ObjectAlreadyInActiveTierErrorException', + ), + ), + ), + /** + 查询存储桶(Bucket)中正在进行中的分块上传对象的方法. + + COS 支持查询 Bucket 中有哪些正在进行中的分块上传对象,单次请求操作最多列出 1000 个正在进行中的 分块上传对象. + + 关于查询 Bucket 中正在进行中的分块上传对象接口的具体描述,请查看 https://cloud.tencent.com/document/product/436/7736. + + cos php SDK 中查询 Bucket 中正在进行中的分块上传对象的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 ListParts 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,获取成功。 + + */ + 'ListParts' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'ListPartsOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1), + 'MaxParts' => array( + 'type' => 'numeric', + 'location' => 'query', + 'sentAs' => 'max-parts'), + 'PartNumberMarker' => array( + 'type' => 'numeric', + 'location' => 'query', + 'sentAs' => 'part-number-marker'), + 'UploadId' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'uploadId'), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml'))), + /** + 查询存储桶(Bucket) 下的部分或者全部对象的方法. + + COS 支持列出指定 Bucket 下的部分或者全部对象. + + 每次默认返回的最大条目数为 1000 条. + + 如果无法一次返回所有的对象,则返回结果中的 IsTruncated 为 true,同时会附加一个 NextMarker 字段,提示下 一个条目的起点. + + 若一次请求,已经返回了全部对象,则不会有 NextMarker 这个字段,同时 IsTruncated 为 false. + + 若把 prefix 设置为某个文件夹的全路径名,则可以列出以此 prefix 为开头的文件,即该文件 夹下递归的所有文件和子文件夹. + + 如果再设置 delimiter 定界符为 “/”,则只列出该文件夹下的文件,子文件夹下递归的文件和文件夹名 将不被列出.而子文件夹名将会以 CommonPrefix 的形式给出. + + 关于查询Bucket 下的部分或者全部对象接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7734. + + cos php SDK 中查询 Bucket 下的部分或者全部对象的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 ListObjects 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,则list成功。 + + 示例: + $result = $cosClient->ListObjects(array( + 'Bucket' => 'testbucket-1252448703')); + */ + 'ListObjects' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'ListObjectsOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri'), + 'Delimiter' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'delimiter'), + 'EncodingType' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'encoding-type'), + 'Marker' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'marker'), + 'MaxKeys' => array( + 'type' => 'numeric', + 'location' => 'query', + 'sentAs' => 'max-keys'), + 'Prefix' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'prefix'), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml')), + 'errorResponses' => array( + array( + 'reason' => 'The specified bucket does not exist.', + 'class' => 'NoSuchBucketException'))), + /** + 获取所属账户的所有存储空间列表的方法. + + 通过使用帯 Authorization 签名认证的请求,可以获取签名中 APPID 所属账户的所有存储空间列表 (Bucket list). + + 关于获取所有存储空间列表接口的具体描述,请查看https://cloud.tencent.com/document/product/436/8291. + + cos php SDK 中获取所属账户的所有存储空间列表的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 ListBuckets 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,获取成功。 + + 示例: + $result = $cosClient->listBuckets(); + print_r($result); + */ + 'ListBuckets' => array( + 'httpMethod' => 'GET', + 'uri' => '/', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'ListBucketsOutput', + 'responseType' => 'model', + 'parameters' => array( + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + 'ListObjectVersions' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?versions', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'ListObjectVersionsOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'Delimiter' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'delimiter', + ), + 'EncodingType' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'encoding-type', + ), + 'KeyMarker' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'key-marker', + ), + 'MaxKeys' => array( + 'type' => 'numeric', + 'location' => 'query', + 'sentAs' => 'max-keys', + ), + 'Prefix' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'prefix', + ), + 'VersionIdMarker' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'version-id-marker', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + 'ListMultipartUploads' => array( + 'httpMethod' => 'GET', + 'uri' => '/{Bucket}?uploads', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'ListMultipartUploadsOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'Delimiter' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'delimiter', + ), + 'EncodingType' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'encoding-type', + ), + 'KeyMarker' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'key-marker', + ), + 'MaxUploads' => array( + 'type' => 'numeric', + 'location' => 'query', + 'sentAs' => 'max-uploads', + ), + 'Prefix' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'prefix', + ), + 'UploadIdMarker' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'upload-id-marker', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ), + 'HeadObject' => array( + 'httpMethod' => 'HEAD', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'HeadObjectOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'IfMatch' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'If-Match', + ), + 'IfModifiedSince' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time-http', + 'location' => 'header', + 'sentAs' => 'If-Modified-Since', + ), + 'IfNoneMatch' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'If-None-Match', + ), + 'IfUnmodifiedSince' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time-http', + 'location' => 'header', + 'sentAs' => 'If-Unmodified-Since', + ), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1, + ), + 'Range' => array( + 'type' => 'string', + 'location' => 'header', + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'versionId', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKey' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + ), + 'errorResponses' => array( + array( + 'reason' => 'The specified key does not exist.', + 'class' => 'NoSuchKeyException', + ), + ), + ), + /** + 存储桶(Bucket) 是否存在的方法. + + 在开始使用 COS 时,需要确认该 Bucket 是否存在,是否有权限访问.若不存在,则可以调用putBucket(PutBucketRequest) 创建. + + 关于确认该 Bucket 是否存在,是否有权限访问接口的具体描述,请查看https://cloud.tencent.com/document/product/436/7735. + + cos php SDK 中Bucket 是否存在的方法具体步骤如下: + + 1. 初始化客户端cosClient,填入存储桶名,和一些额外需要的参数,如授权的具体信息等。 + + 2. 调用 HeadBucket 接口发出请求。 + + 3. 接收该接口的返回数据,若没有抛出异常,获取成功。 + + 示例: + $result = $cosClient->headObject(array( + 'Bucket' => 'testbucket-1252448703', + 'Key' => '11', + 'VersionId' =>'111', + 'ServerSideEncryption' => 'AES256')); + */ + 'HeadBucket' => array( + 'httpMethod' => 'HEAD', + 'uri' => '/{Bucket}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'HeadBucketOutput', + 'responseType' => 'model', + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + ), + 'errorResponses' => array( + array( + 'reason' => 'The specified bucket does not exist.', + 'class' => 'NoSuchBucketException', + ), + ), + ), + 'UploadPartCopy' => array( + 'httpMethod' => 'PUT', + 'uri' => '/{Bucket}{/Key*}', + 'class' => 'Qcloud\\Cos\\Command', + 'responseClass' => 'UploadPartCopyOutput', + 'responseType' => 'model', + 'data' => array( + 'xmlRoot' => array( + 'name' => 'UploadPartCopyRequest', + ), + ), + 'parameters' => array( + 'Bucket' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + ), + 'CopySource' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source', + ), + 'CopySourceIfMatch' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-if-match', + ), + 'CopySourceIfModifiedSince' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time-http', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-if-modified-since', + ), + 'CopySourceIfNoneMatch' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-if-none-match', + ), + 'CopySourceIfUnmodifiedSince' => array( + 'type' => array( + 'object', + 'string', + 'integer', + ), + 'format' => 'date-time-http', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-if-unmodified-since', + ), + 'CopySourceRange' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-range', + ), + 'Key' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'uri', + 'minLength' => 1, + ), + 'PartNumber' => array( + 'required' => true, + 'type' => 'numeric', + 'location' => 'query', + 'sentAs' => 'partNumber', + ), + 'UploadId' => array( + 'required' => true, + 'type' => 'string', + 'location' => 'query', + 'sentAs' => 'uploadId', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKey' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'CopySourceSSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-algorithm', + ), + 'CopySourceSSECustomerKey' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-key', + ), + 'CopySourceSSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-server-side-encryption-customer-key-MD5', + ), + 'RequestPayer' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-payer', + ), + 'command.expects' => array( + 'static' => true, + 'default' => 'application/xml', + ), + ), + ),), + 'models' => array( + 'AbortMultipartUploadOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id'))), + 'CreateBucketOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Location' => array( + 'type' => 'string', + 'location' => 'header'), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id'))), + 'CompleteMultipartUploadOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Location' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Bucket' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Key' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Expiration' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-expiration', + ), + 'ETag' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-version-id', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'CreateMultipartUploadOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Bucket' => array( + 'type' => 'string', + 'location' => 'xml', + 'sentAs' => 'Bucket'), + 'Key' => array( + 'type' => 'string', + 'location' => 'xml'), + 'UploadId' => array( + 'type' => 'string', + 'location' => 'xml'), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ))), + 'CopyObjectOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'ETag' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'LastModified' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Expiration' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-expiration', + ), + 'CopySourceVersionId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-version-id', + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-version-id', + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'DeleteBucketOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id'))), + 'DeleteBucketCorsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'DeleteObjectOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'DeleteMarker' => array( + 'type' => 'boolean', + 'location' => 'header', + 'sentAs' => 'x-cos-delete-marker', + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-version-id', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'DeleteObjectsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Deleted' => array( + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'DeletedObject', + 'type' => 'object', + 'properties' => array( + 'Key' => array( + 'type' => 'string', + ), + 'VersionId' => array( + 'type' => 'string', + ), + 'DeleteMarker' => array( + 'type' => 'boolean', + ), + 'DeleteMarkerVersionId' => array( + 'type' => 'string', + ), + ), + ), + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'Errors' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'Error', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'Error', + 'type' => 'object', + 'sentAs' => 'Error', + 'properties' => array( + 'Key' => array( + 'type' => 'string', + ), + 'VersionId' => array( + 'type' => 'string', + ), + 'Code' => array( + 'type' => 'string', + ), + 'Message' => array( + 'type' => 'string', + ), + ), + ), + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'DeleteBucketLifecycleOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'DeleteBucketReplicationOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'GetObjectOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Body' => array( + 'type' => 'string', + 'instanceOf' => 'Guzzle\\Http\\EntityBody', + 'location' => 'body', + ), + 'DeleteMarker' => array( + 'type' => 'boolean', + 'location' => 'header', + 'sentAs' => 'x-cos-delete-marker', + ), + 'AcceptRanges' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'accept-ranges', + ), + 'Expiration' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-expiration', + ), + 'Restore' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-restore', + ), + 'LastModified' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Last-Modified', + ), + 'ContentLength' => array( + 'type' => 'numeric', + 'location' => 'header', + 'sentAs' => 'Content-Length', + ), + 'ETag' => array( + 'type' => 'string', + 'location' => 'header', + ), + 'MissingMeta' => array( + 'type' => 'numeric', + 'location' => 'header', + 'sentAs' => 'x-cos-missing-meta', + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-version-id', + ), + 'CacheControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Cache-Control', + ), + 'ContentDisposition' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Disposition', + ), + 'ContentEncoding' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Encoding', + ), + 'ContentLanguage' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Language', + ), + 'ContentRange' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Range', + ), + 'ContentType' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Type', + ), + 'Expires' => array( + 'type' => 'string', + 'location' => 'header', + ), + 'WebsiteRedirectLocation' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-website-redirect-location', + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'Metadata' => array( + 'type' => 'object', + 'location' => 'header', + 'sentAs' => 'x-cos-meta-', + 'additionalProperties' => array( + 'type' => 'string', + ), + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'StorageClass' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-storage-class', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'ReplicationStatus' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-replication-status', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'GetObjectAclOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Owner' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string', + ), + 'ID' => array( + 'type' => 'string', + ), + ), + ), + 'Grants' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'AccessControlList', + 'items' => array( + 'name' => 'Grant', + 'type' => 'object', + 'sentAs' => 'Grant', + 'properties' => array( + 'Grantee' => array( + 'type' => 'object', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string'), + /* + 'EmailAddress' => array( + 'type' => 'string'), + */ + 'ID' => array( + 'type' => 'string'), + /* + 'Type' => array( + 'type' => 'string', + 'sentAs' => 'xsi:type', + 'data' => array( + 'xmlAttribute' => true, + 'xmlNamespace' => 'http://www.w3.org/2001/XMLSchema-instance')), + */ + /*'URI' => array( + 'type' => 'string') */)), + 'Permission' => array( + 'type' => 'string', + ), + ), + ), + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'GetBucketAclOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Owner' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string'), + 'ID' => array( + 'type' => 'string'))), + 'Grants' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'AccessControlList', + 'items' => array( + 'name' => 'Grant', + 'type' => 'object', + 'sentAs' => 'Grant', + 'properties' => array( + 'Grantee' => array( + 'type' => 'object', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string'), + /* + 'EmailAddress' => array( + 'type' => 'string'), + */ + 'ID' => array( + 'type' => 'string'), + /* + 'Type' => array( + 'type' => 'string', + 'sentAs' => 'xsi:type', + 'data' => array( + 'xmlAttribute' => true, + 'xmlNamespace' => 'http://www.w3.org/2001/XMLSchema-instance')), + */ + /*'URI' => array( + 'type' => 'string') */)), + 'Permission' => array( + 'type' => 'string')))), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id'))), + 'GetBucketCorsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'CORSRules' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'CORSRule', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'CORSRule', + 'type' => 'object', + 'sentAs' => 'CORSRule', + 'properties' => array( + 'ID' => array( + 'type' => 'string'), + 'AllowedHeaders' => array( + 'type' => 'array', + 'sentAs' => 'AllowedHeader', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'AllowedHeader', + 'type' => 'string', + 'sentAs' => 'AllowedHeader', + ), + ), + 'AllowedMethods' => array( + 'type' => 'array', + 'sentAs' => 'AllowedMethod', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'AllowedMethod', + 'type' => 'string', + 'sentAs' => 'AllowedMethod', + ), + ), + 'AllowedOrigins' => array( + 'type' => 'array', + 'sentAs' => 'AllowedOrigin', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'AllowedOrigin', + 'type' => 'string', + 'sentAs' => 'AllowedOrigin', + ), + ), + 'ExposeHeaders' => array( + 'type' => 'array', + 'sentAs' => 'ExposeHeader', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'ExposeHeader', + 'type' => 'string', + 'sentAs' => 'ExposeHeader', + ), + ), + 'MaxAgeSeconds' => array( + 'type' => 'numeric', + ), + ), + ), + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'GetBucketLifecycleOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Rules' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'Rule', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'Rule', + 'type' => 'object', + 'sentAs' => 'Rule', + 'properties' => array( + 'Expiration' => array( + 'type' => 'object', + 'properties' => array( + 'Date' => array( + 'type' => 'string', + ), + 'Days' => array( + 'type' => 'numeric', + ), + ), + ), + 'ID' => array( + 'type' => 'string', + ), + 'Filter' => array( + 'type' => 'object', + 'properties' => array( + 'Prefix' => array( + 'type' => 'string', + ), + 'Tag' => array( + 'type' => 'object', + 'properties' => array( + 'Key' => array( + 'type' => 'string' + ), + 'Value' => array( + 'type' => 'string' + ), + ) + ) + ), + ), + 'Status' => array( + 'type' => 'string', + ), + 'Transition' => array( + 'type' => 'object', + 'properties' => array( + 'Date' => array( + 'type' => 'string', + ), + 'Days' => array( + 'type' => 'numeric', + ), + 'StorageClass' => array( + 'type' => 'string', + ), + ), + ), + 'NoncurrentVersionTransition' => array( + 'type' => 'object', + 'properties' => array( + 'NoncurrentDays' => array( + 'type' => 'numeric', + ), + 'StorageClass' => array( + 'type' => 'string', + ), + ), + ), + 'NoncurrentVersionExpiration' => array( + 'type' => 'object', + 'properties' => array( + 'NoncurrentDays' => array( + 'type' => 'numeric', + ), + ), + ), + ), + ), + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'GetBucketVersioningOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Status' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'MFADelete' => array( + 'type' => 'string', + 'location' => 'xml', + 'sentAs' => 'MfaDelete', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'GetBucketReplicationOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Role' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Rules' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'Rule', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'ReplicationRule', + 'type' => 'object', + 'sentAs' => 'Rule', + 'properties' => array( + 'ID' => array( + 'type' => 'string', + ), + 'Prefix' => array( + 'type' => 'string', + ), + 'Status' => array( + 'type' => 'string', + ), + 'Destination' => array( + 'type' => 'object', + 'properties' => array( + 'Bucket' => array( + 'type' => 'string', + ), + 'StorageClass' => array( + 'type' => 'string', + ), + ), + ), + ), + ), + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'GetBucketLocationOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Location' => array( + 'type' => 'string', + 'location' => 'body', + 'filters' => array( + 'strval', + 'strip_tags', + 'trim', + ), + ), + ), + ), + 'UploadPartOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'ETag' => array( + 'type' => 'string', + 'location' => 'header', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'UploadPartCopyOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'CopySourceVersionId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-copy-source-version-id', + ), + 'ETag' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'LastModified' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'PutBucketAclOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id'))), + 'PutObjectOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Expiration' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-expiration', + ), + 'ETag' => array( + 'type' => 'string', + 'location' => 'header', + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-version-id', + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'PutObjectAclOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'PutBucketCorsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'PutBucketLifecycleOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'PutBucketVersioningOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'PutBucketReplicationOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'PutBucketNotificationOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'RestoreObjectOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'ListPartsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Bucket' => array( + 'type' => 'string', + 'location' => 'xml'), + 'Key' => array( + 'type' => 'string', + 'location' => 'xml'), + 'UploadId' => array( + 'type' => 'string', + 'location' => 'xml'), + 'PartNumberMarker' => array( + 'type' => 'numeric', + 'location' => 'xml'), + 'NextPartNumberMarker' => array( + 'type' => 'numeric', + 'location' => 'xml'), + 'MaxParts' => array( + 'type' => 'numeric', + 'location' => 'xml'), + 'IsTruncated' => array( + 'type' => 'boolean', + 'location' => 'xml'), + 'Parts' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'Part', + 'data' => array( + 'xmlFlattened' => true), + 'items' => array( + 'name' => 'Part', + 'type' => 'object', + 'sentAs' => 'Part', + 'properties' => array( + 'PartNumber' => array( + 'type' => 'numeric'), + 'LastModified' => array( + 'type' => 'string'), + 'ETag' => array( + 'type' => 'string'), + 'Size' => array( + 'type' => 'numeric')))), + 'Initiator' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'ID' => array( + 'type' => 'string'), + 'DisplayName' => array( + 'type' => 'string'))), + 'Owner' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string'), + 'ID' => array( + 'type' => 'string'))), + 'StorageClass' => array( + 'type' => 'string', + 'location' => 'xml'), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id'))), + 'ListObjectsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'IsTruncated' => array( + 'type' => 'boolean', + 'location' => 'xml'), + 'Marker' => array( + 'type' => 'string', + 'location' => 'xml'), + 'NextMarker' => array( + 'type' => 'string', + 'location' => 'xml'), + 'Contents' => array( + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true), + 'items' => array( + 'name' => 'Object', + 'type' => 'object', + 'properties' => array( + 'Key' => array( + 'type' => 'string'), + 'LastModified' => array( + 'type' => 'string'), + 'ETag' => array( + 'type' => 'string'), + 'Size' => array( + 'type' => 'numeric'), + 'StorageClass' => array( + 'type' => 'string'), + 'Owner' => array( + 'type' => 'object', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string'), + 'ID' => array( + 'type' => 'string')))))), + 'Name' => array( + 'type' => 'string', + 'location' => 'xml'), + 'Prefix' => array( + 'type' => 'string', + 'location' => 'xml'), + 'Delimiter' => array( + 'type' => 'string', + 'location' => 'xml'), + 'MaxKeys' => array( + 'type' => 'numeric', + 'location' => 'xml'), + 'CommonPrefixes' => array( + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true), + 'items' => array( + 'name' => 'CommonPrefix', + 'type' => 'object', + 'properties' => array( + 'Prefix' => array( + 'type' => 'string')))), + 'EncodingType' => array( + 'type' => 'string', + 'location' => 'xml'), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id'))), + 'ListBucketsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Buckets' => array( + 'type' => 'array', + 'location' => 'xml', + 'items' => array( + 'name' => 'Bucket', + 'type' => 'object', + 'sentAs' => 'Bucket', + 'properties' => array( + 'Name' => array( + 'type' => 'string', + ), + 'CreationDate' => array( + 'type' => 'string', + ), + ), + ), + ), + 'Owner' => array( + 'type' => 'object', + 'location' => 'xml', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string', + ), + 'ID' => array( + 'type' => 'string', + ), + ), + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'ListObjectVersionsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'IsTruncated' => array( + 'type' => 'boolean', + 'location' => 'xml', + ), + 'KeyMarker' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'VersionIdMarker' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'NextKeyMarker' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'NextVersionIdMarker' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Versions' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'Version', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'ObjectVersion', + 'type' => 'object', + 'sentAs' => 'Version', + 'properties' => array( + 'ETag' => array( + 'type' => 'string', + ), + 'Size' => array( + 'type' => 'numeric', + ), + 'StorageClass' => array( + 'type' => 'string', + ), + 'Key' => array( + 'type' => 'string', + ), + 'VersionId' => array( + 'type' => 'string', + ), + 'IsLatest' => array( + 'type' => 'boolean', + ), + 'LastModified' => array( + 'type' => 'string', + ), + 'Owner' => array( + 'type' => 'object', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string', + ), + 'ID' => array( + 'type' => 'string', + ), + ), + ), + ), + ), + ), + 'DeleteMarkers' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'DeleteMarker', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'DeleteMarkerEntry', + 'type' => 'object', + 'sentAs' => 'DeleteMarker', + 'properties' => array( + 'Owner' => array( + 'type' => 'object', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string', + ), + 'ID' => array( + 'type' => 'string', + ), + ), + ), + 'Key' => array( + 'type' => 'string', + ), + 'VersionId' => array( + 'type' => 'string', + ), + 'IsLatest' => array( + 'type' => 'boolean', + ), + 'LastModified' => array( + 'type' => 'string', + ), + ), + ), + ), + 'Name' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Prefix' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Delimiter' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'MaxKeys' => array( + 'type' => 'numeric', + 'location' => 'xml', + ), + 'CommonPrefixes' => array( + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'CommonPrefix', + 'type' => 'object', + 'properties' => array( + 'Prefix' => array( + 'type' => 'string', + ), + ), + ), + ), + 'EncodingType' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'ListMultipartUploadsOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'Bucket' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'KeyMarker' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'UploadIdMarker' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'NextKeyMarker' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Prefix' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'Delimiter' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'NextUploadIdMarker' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'MaxUploads' => array( + 'type' => 'numeric', + 'location' => 'xml', + ), + 'IsTruncated' => array( + 'type' => 'boolean', + 'location' => 'xml', + ), + 'Uploads' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'Upload', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'MultipartUpload', + 'type' => 'object', + 'sentAs' => 'Upload', + 'properties' => array( + 'UploadId' => array( + 'type' => 'string', + ), + 'Key' => array( + 'type' => 'string', + ), + 'Initiated' => array( + 'type' => 'string', + ), + 'StorageClass' => array( + 'type' => 'string', + ), + 'Owner' => array( + 'type' => 'object', + 'properties' => array( + 'DisplayName' => array( + 'type' => 'string', + ), + 'ID' => array( + 'type' => 'string', + ), + ), + ), + 'Initiator' => array( + 'type' => 'object', + 'properties' => array( + 'ID' => array( + 'type' => 'string', + ), + 'DisplayName' => array( + 'type' => 'string', + ), + ), + ), + ), + ), + ), + 'CommonPrefixes' => array( + 'type' => 'array', + 'location' => 'xml', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'CommonPrefix', + 'type' => 'object', + 'properties' => array( + 'Prefix' => array( + 'type' => 'string', + ), + ), + ), + ), + 'EncodingType' => array( + 'type' => 'string', + 'location' => 'xml', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'HeadObjectOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'DeleteMarker' => array( + 'type' => 'boolean', + 'location' => 'header', + 'sentAs' => 'x-cos-delete-marker', + ), + 'AcceptRanges' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'accept-ranges', + ), + 'Expiration' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-expiration', + ), + 'Restore' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-restore', + ), + 'LastModified' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Last-Modified', + ), + 'ContentLength' => array( + 'type' => 'numeric', + 'location' => 'header', + 'sentAs' => 'Content-Length', + ), + 'ETag' => array( + 'type' => 'string', + 'location' => 'header', + ), + 'MissingMeta' => array( + 'type' => 'numeric', + 'location' => 'header', + 'sentAs' => 'x-cos-missing-meta', + ), + 'VersionId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-version-id', + ), + 'CacheControl' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Cache-Control', + ), + 'ContentDisposition' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Disposition', + ), + 'ContentEncoding' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Encoding', + ), + 'ContentLanguage' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Language', + ), + 'ContentType' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'Content-Type', + ), + 'Expires' => array( + 'type' => 'string', + 'location' => 'header', + ), + 'WebsiteRedirectLocation' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-website-redirect-location', + ), + 'ServerSideEncryption' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption', + ), + 'Metadata' => array( + 'type' => 'object', + 'location' => 'header', + 'sentAs' => 'x-cos-meta-', + 'additionalProperties' => array( + 'type' => 'string', + ), + ), + 'SSECustomerAlgorithm' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-algorithm', + ), + 'SSECustomerKeyMD5' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-customer-key-MD5', + ), + 'SSEKMSKeyId' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-server-side-encryption-aws-kms-key-id', + ), + 'StorageClass' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-storage-class', + ), + 'RequestCharged' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-request-charged', + ), + 'ReplicationStatus' => array( + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'x-cos-replication-status', + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ))), + 'HeadBucketOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ), + 'GetBucketNotificationOutput' => array( + 'type' => 'object', + 'additionalProperties' => true, + 'properties' => array( + 'CloudFunctionConfigurations' => array( + 'type' => 'array', + 'location' => 'xml', + 'sentAs' => 'CloudFunctionConfiguration', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'CloudFunctionConfiguration', + 'type' => 'object', + 'sentAs' => 'CloudFunctionConfiguration', + 'properties' => array( + 'Id' => array( + 'type' => 'string', + ), + 'CloudFunction' => array( + 'type' => 'string', + 'sentAs' => 'CloudFunction', + ), + 'Events' => array( + 'type' => 'array', + 'sentAs' => 'Event', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'Event', + 'type' => 'string', + 'sentAs' => 'Event', + ), + ), + 'Filter' => array( + 'type' => 'object', + 'properties' => array( + 'Key' => array( + 'type' => 'object', + 'sentAs' => 'Key', + 'properties' => array( + 'FilterRules' => array( + 'type' => 'array', + 'sentAs' => 'FilterRule', + 'data' => array( + 'xmlFlattened' => true, + ), + 'items' => array( + 'name' => 'FilterRule', + 'type' => 'object', + 'sentAs' => 'FilterRule', + 'properties' => array( + 'Name' => array( + 'type' => 'string', + ), + 'Value' => array( + 'type' => 'string', + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + 'RequestId' => array( + 'location' => 'header', + 'sentAs' => 'x-cos-request-id', + ), + ), + ))); + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Signature.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Signature.php new file mode 100644 index 0000000..51c02e9 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Signature.php @@ -0,0 +1,50 @@ +accessKey = $accessKey; + $this->secretKey = $secretKey; + } + public function __destruct() { + } + public function signRequest(RequestInterface $request) { + $signTime = (string)(time() - 60) . ';' . (string)(time() + 3600); + $httpString = strtolower($request->getMethod()) . "\n" . urldecode($request->getPath()) . + "\n\nhost=" . $request->getHost() . "\n"; + $sha1edHttpString = sha1($httpString); + $stringToSign = "sha1\n$signTime\n$sha1edHttpString\n"; + $signKey = hash_hmac('sha1', $signTime, $this->secretKey); + $signature = hash_hmac('sha1', $stringToSign, $signKey); + $authorization = 'q-sign-algorithm=sha1&q-ak='. $this->accessKey . + "&q-sign-time=$signTime&q-key-time=$signTime&q-header-list=host&q-url-param-list=&" . + "q-signature=$signature"; + $request->setHeader('Authorization', $authorization); + } + public function createAuthorization( + RequestInterface $request, + $expires = "10 minutes" + ) { + $signTime = (string)(time() - 60) . ';' . (string)(strtotime($expires)); + $httpString = strtolower($request->getMethod()) . "\n" . urldecode($request->getPath()) . + "\n\nhost=" . $request->getHost() . "\n"; + $sha1edHttpString = sha1($httpString); + $stringToSign = "sha1\n$signTime\n$sha1edHttpString\n"; + $signKey = hash_hmac('sha1', $signTime, $this->secretKey); + $signature = hash_hmac('sha1', $stringToSign, $signKey); + $authorization = 'q-sign-algorithm=sha1&q-ak='. $this->accessKey . + "&q-sign-time=$signTime&q-key-time=$signTime&q-header-list=host&q-url-param-list=&" . + "q-signature=$signature"; + return $authorization; + } + public function createPresignedUrl( + RequestInterface $request, + $expires = "10 minutes" + ) { + $authorization = $this->createAuthorization($request, $expires); + $request->getQuery()->add('sign', $authorization); + return $request->getUrl(); + } +} \ No newline at end of file diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/SignatureListener.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/SignatureListener.php new file mode 100644 index 0000000..a979583 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/SignatureListener.php @@ -0,0 +1,45 @@ +signature = new Signature($accessKey, $secretKey); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + 'request.before_send' => array('onRequestBeforeSend', -255)); + } + + /** + * Signs requests before they are sent + * + * @param Event $event Event emitted + */ + public function onRequestBeforeSend(Event $event) { + + $this->signature->signRequest($event['request']); +/* + if(!$this->credentials instanceof NullCredentials) { + $this->signature->signRequest($event['request'], $this->credentials); + } +*/ + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/Test.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/Test.php new file mode 100644 index 0000000..d32f6ac --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/Test.php @@ -0,0 +1,1367 @@ +bucket = getenv('COS_BUCKET'); + TestHelper::nuke($this->bucket); + $this->cosClient = new Client(array('region' => getenv('COS_REGION'), + 'credentials' => array( + 'appId' => getenv('COS_APPID'), + 'secretId' => getenv('COS_KEY'), + 'secretKey' => getenv('COS_SECRET')))); + sleep(5); + } + + protected function tearDown() + { + TestHelper::nuke($this->bucket); + } + + /********************************** + * TestBucket + **********************************/ + + /* + * put bucket,bucket已经存在 + * BucketAlreadyOwnedByYou + * 409 + */ + public function testCreateExistingBucket() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'BucketAlreadyOwnedByYou' && $e->getStatusCode() === 409); + } + } + + /* + * put bucket,bucket名称非法 + * InvalidBucketName + * 400 + */ + public function testCreateInvalidBucket() + { + try { + $this->cosClient->createBucket(array('Bucket' => 'qwe_213')); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'InvalidBucketName' && $e->getStatusCode() === 400); + } + } + + /* + * put bucket,设置bucket公公权限为private + * 200 + */ + public function testCreatePrivateBucket() + { + try { + $this->cosClient->createBucket( + array( + 'Bucket' => $this->bucket, + 'ACL'=>'private' + )); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket,设置bucket公公权限为public-read + * 200 + */ + public function testCreatePublicReadBucket() + { + try { + $this->cosClient->createBucket( + array( + 'Bucket' => $this->bucket, + 'ACL'=>'public-read' + )); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket,公共权限非法 + * InvalidArgument + * 400 + */ + public function testCreateInvalidACLBucket() + { + try { + $this->cosClient->createBucket( + array( + 'Bucket' => $this->bucket, + 'ACL'=>'public' + )); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400); + } + } + + /* + * put bucket acl,设置bucket公共权限为private + * 200 + */ + public function testPutBucketAclPrivate() + { + try { + + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl( + array( + 'Bucket' => $this->bucket, + 'ACL'=>'private' + )); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,设置bucket公共权限为public-read + * 200 + */ + public function testPutBucketAclPublicRead() + { + try { + + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl( + array( + 'Bucket' => $this->bucket, + 'ACL'=>'public-read' + )); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,公共权限非法 + * InvalidArgument + * 400 + */ + public function testPutBucketAclInvalid() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl( + array( + 'Bucket' => $this->bucket, + 'ACL'=>'public' + )); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400); + } + } + + /* + * put bucket acl,设置bucket账号权限为grant-read + * 200 + */ + public function testPutBucketAclReadToUser() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantRead' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,设置bucket账号权限为grant-write + * 200 + */ + public function testPutBucketAclWriteToUser() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,设置bucket账号权限为grant-full-control + * 200 + */ + public function testPutBucketAclFullToUser() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,设置bucket账号权限,同时授权给多个账户 + * 200 + */ + public function testPutBucketAclToUsers() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970",id="qcs::cam::uin/2779643970:uin/2779643970",id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,设置bucket账号权限,授权给子账号 + * 200 + */ + public function testPutBucketAclToSubuser() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,设置bucket账号权限,同时指定read、write和fullcontrol + * 200 + */ + public function testPutBucketAclReadWriteFull() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantRead' => 'id="qcs::cam::uin/123:uin/123"', + 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"', + 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"',)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,设置bucket账号权限,grant值非法 + * InvalidArgument + * 400 + */ + public function testPutBucketAclInvalidGrant() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantFullControl' => 'id="qcs::camuin/321023:uin/2779643970"',)); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400); + } + } + + /* + * put bucket acl,设置bucket账号权限,通过body方式授权 + * 200 + */ + public function testPutBucketAclByBody() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + ))); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,设置bucket账号权限,通过body方式授权给anyone + * 200 + */ + public function testPutBucketAclByBodyToAnyone() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::anyone:anyone', + 'ID' => 'qcs::cam::anyone:anyone', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + ))); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket acl,bucket不存在 + * NoSuchBucket + * 404 + */ + public function testPutBucketAclBucketNonexisted() + { + try { + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantFullControl' => 'id="qcs::cam::uin/321023:uin/2779643970"',)); + } catch (ServiceResponseException $e) { +// echo($e->getExceptionCode()); +// echo($e->getStatusCode()); + $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404); + } + } + + /* + * put bucket acl,覆盖设置 + * x200 + */ + public function testPutBucketAclCover() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"', + 'GrantRead' => 'id="qcs::cam::uin/2779643970:uin/2779643970"', + 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + $this->cosClient->PutBucketAcl(array( + 'Bucket' => $this->bucket, + 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 正常head bucket + * 200 + */ + public function testHeadBucket() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->HeadBucket(array( + 'Bucket' => $this->bucket)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * head bucket,bucket不存在 + * NoSuchBucket + * 404 + */ + public function testHeadBucketNonexisted() + { + try { + $this->cosClient->HeadBucket(array( + 'Bucket' => $this->bucket,)); + } catch (ServiceResponseException $e) { +// echo($e->getExceptionCode()); +// echo($e->getStatusCode()); + $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404); + } + } + + /* + * get bucket,bucket为空 + * 200 + */ + public function testGetBucketEmpty() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->ListObjects(array( + 'Bucket' => $this->bucket)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * get bucket,bucket不存在 + * NoSuchBucket + * 404 + */ + public function testGetBucketNonexisted() + { + try { + $this->cosClient->ListObjects(array( + 'Bucket' => $this->bucket,)); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404); + } + } + + + /* + * put bucket cors,cors规则包含多条 + * 200 + */ + public function testPutBucketCors() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putBucketCors(array( + // Bucket is required + 'Bucket' => $this->bucket, + // CORSRules is required + 'CORSRules' => array( + array( + 'ID' => '1234', + 'AllowedHeaders' => array('*',), + // AllowedMethods is required + 'AllowedMethods' => array('PUT',), + // AllowedOrigins is required + 'AllowedOrigins' => array('*',), + 'ExposeHeaders' => array('*',), + 'MaxAgeSeconds' => 1, + ), + array( + 'ID' => '12345', + 'AllowedHeaders' => array('*',), + // AllowedMethods is required + 'AllowedMethods' => array('PUT',), + // AllowedOrigins is required + 'AllowedOrigins' => array('*',), + 'ExposeHeaders' => array('*',), + 'MaxAgeSeconds' => 1, + ), + // ... repeated + ), + )); + $this->cosClient->getBucketCors(array( + // Bucket is required + 'Bucket' => $this->bucket,)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + + /* + * 正常get bucket cors + * 200 + */ + public function testGetBucketCors() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putBucketCors(array( + // Bucket is required + 'Bucket' => $this->bucket, + // CORSRules is required + 'CORSRules' => array( + array( + 'ID' => '1234', + 'AllowedHeaders' => array('*',), + // AllowedMethods is required + 'AllowedMethods' => array('PUT',), + // AllowedOrigins is required + 'AllowedOrigins' => array('*',), + 'ExposeHeaders' => array('*',), + 'MaxAgeSeconds' => 1, + ), + array( + 'ID' => '12345', + 'AllowedHeaders' => array('*',), + // AllowedMethods is required + 'AllowedMethods' => array('PUT',), + // AllowedOrigins is required + 'AllowedOrigins' => array('*',), + 'ExposeHeaders' => array('*',), + 'MaxAgeSeconds' => 1, + ), + // ... repeated + ), + )); + $this->cosClient->getBucketCors(array( + // Bucket is required + 'Bucket' => $this->bucket,)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * bucket未设置cors规则,发送get bucket cors + * NoSuchCORSConfiguration + * 404 + */ + public function testGetBucketCorsNull() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->getBucketCors(array( + // Bucket is required + 'Bucket' => $this->bucket,)); + } catch (ServiceResponseException $e) { +// echo($e->getExceptionCode()); +// echo($e->getStatusCode()); + $this->assertTrue($e->getExceptionCode() === 'NoSuchCORSConfiguration' && $e->getStatusCode() === 404); + } + } + + /* + * bucket未设置cors规则,发送get bucket cors + * NoSuchCORSConfiguration + * 404 + */ + public function testGetBucketCorsNonExisted() + { + try { + $this->cosClient->getBucketCors(array( + // Bucket is required + 'Bucket' => $this->bucket,)); + } catch (ServiceResponseException $e) { +// echo($e->getExceptionCode()); +// echo($e->getStatusCode()); + $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404); + } + } + + /* + * 正常get bucket lifecycle + * 200 + */ + public function testGetBucketLifecycle() + { + try { + $result = $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $result = $this->cosClient->putBucketLifecycle(array( + 'Bucket' => $this->bucket, + 'Rules' => array( + array( + 'Status' => 'Enabled', + 'Filter' => array( + 'Tag' => array( + 'Key' => 'datalevel', + 'Value' => 'backup' + ) + ), + 'Transitions' => array( + array( + # 30天后转换为Standard_IA + 'Days' => 30, + 'StorageClass' => 'Standard_IA'), + array( + # 365天后转换为Archive + 'Days' => 365, + 'StorageClass' => 'Archive') + ), + 'Expiration' => array( + # 3650天后过期删除 + 'Days' => 3650, + ) + ) + ) + )); + $result = $this->cosClient->getBucketLifecycle(array( + // Bucket is required + 'Bucket' => $this->bucket, + )); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 正常delete bucket lifecycle + * 200 + */ + public function testDeleteBucketLifecycle() + { + try { + $result = $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $result = $this->cosClient->putBucketLifecycle(array( + 'Bucket' => $this->bucket, + 'Rules' => array( + array( + 'Status' => 'Enabled', + 'Filter' => array( + 'Tag' => array( + 'Key' => 'datalevel', + 'Value' => 'backup' + ) + ), + 'Transitions' => array( + array( + # 30天后转换为Standard_IA + 'Days' => 30, + 'StorageClass' => 'Standard_IA'), + array( + # 365天后转换为Archive + 'Days' => 365, + 'StorageClass' => 'Archive') + ), + 'Expiration' => array( + # 3650天后过期删除 + 'Days' => 3650, + ) + ) + ) + )); + $result = $this->cosClient->deleteBucketLifecycle(array( + // Bucket is required + 'Bucket' => $this->bucket, + )); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket lifecycle,请求body中不指定filter + * 200 + */ + public function testPutBucketLifecycleNonFilter() + { + try { + $result = $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $result = $this->cosClient->putBucketLifecycle(array( + // Bucket is required + 'Bucket' => $this->bucket, + // Rules is required + 'Rules' => array( + array( + 'Expiration' => array( + 'Days' => 1000, + ), + 'ID' => 'id1', + // Status is required + 'Status' => 'Enabled', + 'Transitions' => array( + array( + 'Days' => 100, + 'StorageClass' => 'Standard_IA'), + ), + // ... repeated + ), + ))); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404); + + } + } + + /* + * put bucket,bucket名称带有- + * 200 + */ + public function testPutBucket2() + { + try { + try{ + $this->cosClient->deleteBucket(array('Bucket' => '12345-'.$this->bucket)); + } catch (\Exception $e) { + } + $this->cosClient->createBucket(array('Bucket' => '12345-'.$this->bucket)); + $this->cosClient->deleteBucket(array('Bucket' => '12345-'.$this->bucket)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put bucket,bucket名称带有两个- + * 200 + */ + public function testPutBucket3() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket.'-12333-4445')); + $this->cosClient->deleteBucket(array('Bucket' => $this->bucket.'-12333-4445')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 正常get bucket location + * 200 + */ + public function testGetBucketLocation() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->getBucketLocation(array('Bucket' => $this->bucket)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * bucket不存在,发送get bucket location请求 + * NoSuchBucket + * 404 + */ + public function testGetBucketLocationNonExisted() + { + try { + $this->cosClient->getBucketLocation(array('Bucket' => $this->bucket)); + } catch (ServiceResponseException $e) { + // echo($e->getExceptionCode()); + // echo($e->getStatusCode()); + $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket' && $e->getStatusCode() === 404); + } + } + + /********************************** + * TestObject + **********************************/ + + /* + * put object,请求头部携带服务端加密参数 + * 200 + */ + public function testPutObjectEncryption() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array( + 'Bucket' => $this->bucket, + 'Key' => '11//32//43', + 'Body' => 'Hello World!', + 'ServerSideEncryption' => 'AES256')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * Copy大文件 + * 200 + */ + public function testCopyBigFile() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->Copy($bucket = $this->bucket, + $key = 'test10G', + $copysource = 'lewzylu01-1251668577.cos.ap-guangzhou.myqcloud.com/test10G'); + $rt = $this->cosClient->headObject(array('Bucket' => $this->$bucket, + 'Key' => 'test10G')); + assertTrue(true, $rt['ContentLength'] == 10485760000); + + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + + /* + * 上传文件Bucket不存在 + * NoSuchBucket + * 404 + */ + public function testPutObjectIntoNonexistedBucket() { + try { + $this->cosClient->putObject(array( + 'Bucket' => $this->bucket, 'Key' => 'hello.txt', 'Body' => 'Hello World')); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'NoSuchBucket'); + $this->assertTrue($e->getStatusCode() === 404); + } + } + + + /* + * 上传小文件 + * 200 + */ + public function testUploadSmallObject() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World'); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 上传空文件 + * 200 + */ + public function testPutObjectEmpty() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好.txt', '123'); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 上传已存在的文件 + * 200 + */ + public function testPutObjectExisted() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好.txt', '1234124'); + $this->cosClient->upload($this->bucket, '你好.txt', '请二位qwe'); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object,请求头部携带自定义头部x-cos-meta- + * 200 + */ + public function testPutObjectMeta() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'Body' => '1234124', + 'Metadata' => array( + 'lew' => str_repeat('a', 1 * 1024), + ))); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object,请求头部携带自定义头部x-cos-meta- + * KeyTooLong + * 400 + */ + public function testPutObjectMeta2K() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'Body' => '1234124', + 'Metadata' => array( + 'lew' => str_repeat('a', 3 * 1024), + ))); + } catch (ServiceResponseException $e) { +// echo($e->getExceptionCode()); +// echo($e->getStatusCode()); + $this->assertTrue($e->getExceptionCode() === 'KeyTooLong' && $e->getStatusCode() === 400); + } + } + + /* + * 上传复杂文件名的文件 + * 200 + */ + public function testUploadComplexObject() { + try { + $result = $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '→↓←→↖↗↙↘! \"#$%&\'()*+,-./0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~', 'Hello World'); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 上传大文件 + * 200 + */ + public function testUploadLargeObject() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, 'hello.txt', str_repeat('a', 9 * 1024 * 1024)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 下载文件 + * 200 + */ + public function testGetObject() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World'); + $this->cosClient->getObject(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt',)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * get object,object名称包含特殊字符 + * 200 + */ + public function testGetObjectSpecialName() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好<>!@#^%^&*&(&^!@#@!.txt', 'Hello World'); + $this->cosClient->getObject(array( + 'Bucket' => $this->bucket, + 'Key' => '你好<>!@#^%^&*&(&^!@#@!.txt',)); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * get object,请求头部带if-match,参数值为true + * 200 + */ + public function testGetObjectIfMatchTrue() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World'); + $this->cosClient->getObject(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'IfMatch' => '"b10a8db164e0754105b7a99be72e3fe5"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + + /* + * get object,请求头部带if-match,参数值为false + * PreconditionFailed + * 412 + */ + public function testGetObjectIfMatchFalse() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World'); + $this->cosClient->getObject(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'IfMatch' => '""')); + } catch (ServiceResponseException $e) { +// echo($e->getExceptionCode()); +// echo($e->getStatusCode()); + $this->assertTrue($e->getExceptionCode() === 'PreconditionFailed' && $e->getStatusCode() === 412); + } + } + + /* + * get object,请求头部带if-none-match,参数值为true + * 200 + */ + public function testGetObjectIfNoneMatchTrue() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World'); + $this->cosClient->getObject(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'IfNoneMatch' => '"b10a8db164e0754105b7a99be72e3fe5"')); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'NotModified' && $e->getStatusCode() === 304); + } + } + + + /* + * get object,请求头部带if-none-match,参数值为false + * PreconditionFailed + * 412 + */ + public function testGetObjectIfNoneMatchFalse() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '你好.txt', 'Hello World'); + $this->cosClient->getObject(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'IfNoneMatch' => '""')); + + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 获取文件url + * 200 + */ + public function testGetObjectUrl() { + try{ + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->getObjectUrl($this->bucket, 'hello.txt', '+10 minutes'); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * 设置objectacl + * 200 + */ + public function testPutObjectACL() { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '11', 'hello.txt'); + $this->cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '11', + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + ))); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + + } + + + /* + * 获取objectacl + * 200 + */ + public function testGetObjectACL() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->upload($this->bucket, '11', 'hello.txt'); + $this->cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '11', + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + ))); + + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object acl,设置object公共权限为private + * 200 + */ + public function testPutObjectAclPrivate() + { + try { + + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl( + array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'ACL'=>'private' + )); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object acl,设置object公共权限为public-read + * 200 + */ + public function testPutObjectAclPublicRead() + { + try { + + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl( + array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'ACL'=>'public-read' + )); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object acl,公共权限非法 + * InvalidArgument + * 400 + */ + public function testPutObjectAclInvalid() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl( + array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'ACL'=>'public' + )); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400); + } + } + + /* + * put object acl,设置object账号权限为grant-read + * 200 + */ + public function testPutObjectAclReadToUser() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'GrantRead' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object acl,设置object账号权限为grant-write + * 200 + */ +// public function testPutObjectAclWriteToUser() +// { +// try { +// $this->cosClient->createBucket(array('Bucket' => $this->bucket)); +// $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); +// $this->cosClient->PutObjectAcl(array( +// 'Bucket' => $this->bucket, +// 'Key' => '你好.txt', +// 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); +// } catch (ServiceResponseException $e) { +// $this->assertFalse(true, $e); +// } +// } + + /* + * put object acl,设置object账号权限为grant-full-control + * 200 + */ + public function testPutObjectAclFullToUser() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object acl,设置object账号权限,同时授权给多个账户 + * 200 + */ + public function testPutObjectAclToUsers() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970",id="qcs::cam::uin/2779643970:uin/2779643970",id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object acl,设置object账号权限,授权给子账号 + * 200 + */ + public function testPutObjectAclToSubuser() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"')); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object acl,设置object账号权限,同时指定read、write和fullcontrol + * 200 + */ +// public function testPutObjectAclReadWriteFull() +// { +// try { +// $this->cosClient->createBucket(array('Bucket' => $this->bucket)); +// $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); +// $this->cosClient->PutObjectAcl(array( +// 'Bucket' => $this->bucket, +// 'Key' => '你好.txt', +// 'GrantRead' => 'id="qcs::cam::uin/123:uin/123"', +// 'GrantWrite' => 'id="qcs::cam::uin/2779643970:uin/2779643970"', +// 'GrantFullControl' => 'id="qcs::cam::uin/2779643970:uin/2779643970"',)); +// } catch (ServiceResponseException $e) { +// $this->assertFalse(true, $e); +// } +// } + + /* + * put object acl,设置object账号权限,grant值非法 + * InvalidArgument + * 400 + */ + public function testPutObjectAclInvalidGrant() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'GrantFullControl' => 'id="qcs::camuin/321023:uin/2779643970"',)); + } catch (ServiceResponseException $e) { + $this->assertTrue($e->getExceptionCode() === 'InvalidArgument' && $e->getStatusCode() === 400); + } + } + + /* + * put object acl,设置object账号权限,通过body方式授权 + * 200 + */ + public function testPutObjectAclByBody() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->PutObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + ))); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + + /* + * put object acl,设置object账号权限,通过body方式授权给anyone + * 200 + */ + public function testPutObjectAclByBodyToAnyone() + { + try { + $this->cosClient->createBucket(array('Bucket' => $this->bucket)); + $this->cosClient->putObject(array('Bucket' => $this->bucket,'Key' => '你好.txt', 'Body' => '123')); + $this->cosClient->putObjectAcl(array( + 'Bucket' => $this->bucket, + 'Key' => '你好.txt', + 'Grants' => array( + array( + 'Grantee' => array( + 'DisplayName' => 'qcs::cam::anyone:anyone', + 'ID' => 'qcs::cam::anyone:anyone', + 'Type' => 'CanonicalUser', + ), + 'Permission' => 'FULL_CONTROL', + ), + // ... repeated + ), + 'Owner' => array( + 'DisplayName' => 'qcs::cam::uin/2779643970:uin/2779643970', + 'ID' => 'qcs::cam::uin/2779643970:uin/2779643970', + ))); + } catch (ServiceResponseException $e) { + $this->assertFalse(true, $e); + } + } + +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/TestHelper.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/TestHelper.php new file mode 100644 index 0000000..367b887 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/Tests/TestHelper.php @@ -0,0 +1,48 @@ + getenv('COS_REGION'), + 'credentials'=> array( + 'appId' => getenv('COS_APPID'), + 'secretId' => getenv('COS_KEY'), + 'secretKey' => getenv('COS_SECRET')))); + $result = $cosClient->listObjects(array('Bucket' => $bucket)); + if ($result->get('Contents')) { + foreach ($result ->get('Contents') as $content) { + $cosClient->deleteObject(array('Bucket' => $bucket, 'Key' => $content['Key'])); + } + } + $cosClient->deleteBucket(array('Bucket' => $bucket)); + + while(True){ + $result = $cosClient->ListMultipartUploads( + array('Bucket' => $bucket, + 'Prefix' => '')); + if (count($result['Uploads']) == 0){ + break; + } + foreach ($result['Uploads'] as $upload) { + try { + $rt = $cosClient->AbortMultipartUpload( + array('Bucket' => $bucket, + 'Key' => $upload['Key'], + 'UploadId' => $upload['UploadId'])); + print_r($rt); + } catch (\Exception $e) { + print_r($e); + } + } + } + } catch (\Exception $e) { + //echo "$e\n"; + // Ignore + } + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/TokenListener.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/TokenListener.php new file mode 100644 index 0000000..8bde999 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/TokenListener.php @@ -0,0 +1,48 @@ +token = $token; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + 'request.before_send' => array('onRequestBeforeSend', -240)); + } + + /** + * Signs requests before they are sent + * + * @param Event $event Event emitted + */ + public function onRequestBeforeSend(Event $event) { + if ($this->token != null) { + $event['request']->setHeader('x-cos-security-token', $this->token); + } +/* + if(!$this->credentials instanceof NullCredentials) { + $this->signature->signRequest($event['request'], $this->credentials); + } +*/ + } +} diff --git a/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/UploadBodyListener.php b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/UploadBodyListener.php new file mode 100644 index 0000000..f318029 --- /dev/null +++ b/source/vendor/qcloud/cos-sdk-v5/src/Qcloud/Cos/UploadBodyListener.php @@ -0,0 +1,79 @@ +commands = $commands; + $this->bodyParameter = (string) $bodyParameter; + $this->sourceParameter = (string) $sourceParameter; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return array('command.before_prepare' => array('onCommandBeforePrepare')); + } + + /** + * Converts filenames and file handles into EntityBody objects before the command is validated + * + * @param Event $event Event emitted + * @throws InvalidArgumentException + */ + public function onCommandBeforePrepare(Event $event) { + /** @var Command $command */ + $command = $event['command']; + if (in_array($command->getName(), $this->commands)) { + // Get the interesting parameters + $source = $command->get($this->sourceParameter); + $body = $command->get($this->bodyParameter); + + // If a file path is passed in then get the file handle + if (is_string($source) && file_exists($source)) { + $body = fopen($source, 'rb'); + } + + // Prepare the body parameter and remove the source file parameter + if (null !== $body) { + $command->remove($this->sourceParameter); + $command->set($this->bodyParameter, EntityBody::factory($body)); + } else { + throw new InvalidArgumentException( + "You must specify a non-null value for the {$this->bodyParameter} or {$this->sourceParameter} parameters."); + } + } + } +} diff --git a/source/vendor/qiniu/php-sdk/.gitignore b/source/vendor/qiniu/php-sdk/.gitignore new file mode 100644 index 0000000..4c842c8 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/.gitignore @@ -0,0 +1,12 @@ +*.phar +*.zip +build/artifacts +phpunit.xml +phpunit.functional.xml +.DS_Store +.swp +.build +composer.lock +vendor +src/package.xml +.idea/ diff --git a/source/vendor/qiniu/php-sdk/.scrutinizer.yml b/source/vendor/qiniu/php-sdk/.scrutinizer.yml new file mode 100644 index 0000000..8d9304c --- /dev/null +++ b/source/vendor/qiniu/php-sdk/.scrutinizer.yml @@ -0,0 +1,35 @@ +filter: + excluded_paths: [tests/*] +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: 1200 + runs: 3 + php_analyzer: true + php_code_coverage: false + php_code_sniffer: + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + excluded_dirs: [vendor, tests] + php_cpd: + enabled: true + excluded_dirs: [vendor, tests] diff --git a/source/vendor/qiniu/php-sdk/.travis.yml b/source/vendor/qiniu/php-sdk/.travis.yml new file mode 100644 index 0000000..60951b1 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/.travis.yml @@ -0,0 +1,23 @@ +sudo: false +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + +before_script: + - export QINIU_TEST_ENV="travis" + - travis_retry composer self-update + - travis_retry composer install --no-interaction --prefer-source --dev + +script: + - ./vendor/bin/phpcs --standard=PSR2 src + - ./vendor/bin/phpcs --standard=PSR2 examples + - ./vendor/bin/phpcs --standard=PSR2 tests + - ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover tests/Qiniu/Tests/ + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/source/vendor/qiniu/php-sdk/CHANGELOG.md b/source/vendor/qiniu/php-sdk/CHANGELOG.md new file mode 100644 index 0000000..8a284bd --- /dev/null +++ b/source/vendor/qiniu/php-sdk/CHANGELOG.md @@ -0,0 +1,105 @@ +# Changelog + +## 7.2.7 (2018-11-06) +* 添加 QVM 内网上传到 KODO 的 zone 设置 + +## 7.2.6 (2018-05-18) +* 修复rs,rsf在不同机房默认的https域名 + +## 7.2.5 (2018-05-10) +* 修复表单上传中多余的参数checkCrc导致的fname错位问题 + +## 7.2.4 (2018-05-09) +### 增加 +* 连麦功能 + +## 7.2.3 (2018-01-20) +### 增加 +* 新加坡机房 +### 修正 +* 获取域名的入口域名 +* http回复头部兼容大小写 + +## 7.2.2 (2017-11-06) +### 增加 +* Qiniu算法的鉴权方法 + +## 7.1.4 (2017-06-21) +### 增加 +* cdn 文件/目录 刷新 +* cdn 获取 流量/带宽 +* cdn 获取域名的访问日志列表 +* cdn 对资源链接进行时间戳防盗链签名 + +## 7.1.3 (2016-11-18) +### 增加 +* move, copy操作增加force参数 + +## 7.1.2 (2016-11-12) +### 修正 +* 明确抛出获取各区域域名失败时的报错 + +## 7.1.1 (2016-11-02) +### 修正 +* 多区域配置文件存储目录从home修改到tmp目录 + + +## 7.1.0 (2016-10-22) +### 增加 +* 多存储区域的支持 + +## 7.0.8 (2016-07-19) +### 增加 +* demo +* https url 支持 +* deleteAfterDays 策略 +* 添加图片处理链接统一拼接方法 by @SherlockRen + +## 7.0.7 (2016-01-12) +### 修正 +* PersistentFop参数pipeline和notify_url失效 +* resume 模式 close file inputstream + +## 7.0.6 (2015-12-05) +### 修正 +* php7.0 Json 对空字符串解析单元测试报错 +* 开启安全模式或者设置可操作目录树时,设置CURLOPT_FOLLOWLOCATION报错, by @twocabbages +* fetch 支持不指定key, by @sinkcup + +## 7.0.5 (2015-10-29) +### 增加 +* 增加上传策略最小文件大小限制 fsizeMin +* 增加常见examples + +## 7.0.4 (2015-07-23) +### 修正 +* 一些地方的严格比较检查 +* resumeupload 备用地址失效 + +## 7.0.3 (2015-07-10) +### 修改 +* 多zone 支持 + +## 7.0.2 (2015-04-18) +### 修改 +* fetch 接口返回内容调整 +* pfop 接口调整 + +###修正 +* exception 类调用 + +## 7.0.1 (2015-03-27) +### 增加 +* 增加代码注释 + +## 7.0.0 (2015-02-03) + +### 增加 +* 简化上传接口 +* 自动选择断点续上传还是直传 +* 重构代码,接口和内部结构更清晰 +* 改变mime +* 代码覆盖度报告 +* policy改为array, 便于灵活增加,并加入过期字段检查 +* 文件列表支持目录形式 +* 利用元编程方式支持 fop 和 pfop diff --git a/source/vendor/qiniu/php-sdk/CONTRIBUTING.md b/source/vendor/qiniu/php-sdk/CONTRIBUTING.md new file mode 100644 index 0000000..0466bf9 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# 贡献代码指南 + +我们非常欢迎大家来贡献代码,我们会向贡献者致以最诚挚的敬意。 + +一般可以通过在Github上提交[Pull Request](https://github.com/qiniu/php-sdk)来贡献代码。 + +## Pull Request要求 + +- **[PSR-2 编码风格标准](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** 。要通过项目中的code sniffer检查。 + +- **代码格式** 提交前 请按 ./vendor/bin/phpcbf --standard=PSR2 进行格式化。 + +- **必须添加测试!** - 如果没有测试(单元测试、集成测试都可以),那么提交的补丁是不会通过的。 + +- **记得更新文档** - 保证`README.md`以及其他相关文档及时更新,和代码的变更保持一致性。 + +- **考虑我们的发布周期** - 我们的版本号会服从[SemVer v2.0.0](http://semver.org/),我们绝对不会随意变更对外的API。 + +- **创建feature分支** - 最好不要从你的master分支提交 pull request。 + +- **一个feature提交一个pull请求** - 如果你的代码变更了多个操作,那就提交多个pull请求吧。 + +- **清晰的commit历史** - 保证你的pull请求的每次commit操作都是有意义的。如果你开发中需要执行多次的即时commit操作,那么请把它们放到一起再提交pull请求。 + +## 运行测试 + +``` bash +./vendor/bin/phpunit tests/Qiniu/Tests/ + +``` diff --git a/source/vendor/qiniu/php-sdk/LICENSE b/source/vendor/qiniu/php-sdk/LICENSE new file mode 100644 index 0000000..ba646be --- /dev/null +++ b/source/vendor/qiniu/php-sdk/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Qiniu, Ltd. + +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/source/vendor/qiniu/php-sdk/README.md b/source/vendor/qiniu/php-sdk/README.md new file mode 100644 index 0000000..16c8b49 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/README.md @@ -0,0 +1,75 @@ +# Qiniu Cloud SDK for PHP +[![doxygen.io](http://doxygen.io/github.com/qiniu/php-sdk/?status.svg)](http://doxygen.io/github.com/qiniu/php-sdk/) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) +[![Build Status](https://travis-ci.org/qiniu/php-sdk.svg)](https://travis-ci.org/qiniu/php-sdk) +[![Latest Stable Version](https://img.shields.io/packagist/v/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk) +[![Total Downloads](https://img.shields.io/packagist/dt/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/qiniu/php-sdk/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/qiniu/php-sdk/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master) +[![Join Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/qiniu/php-sdk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![@qiniu on weibo](http://img.shields.io/badge/weibo-%40qiniutek-blue.svg)](http://weibo.com/qiniutek) + +## 安装 + +* 通过composer,这是推荐的方式,可以使用composer.json 声明依赖,或者运行下面的命令。SDK 包已经放到这里 [`qiniu/php-sdk`][install-packagist] 。 +```bash +$ composer require qiniu/php-sdk +``` +* 直接下载安装,SDK 没有依赖其他第三方库,但需要参照 composer的autoloader,增加一个自己的autoloader程序。 + +## 运行环境 + +| Qiniu SDK版本 | PHP 版本 | +|:--------------------:|:---------------------------:| +| 7.x | cURL extension, 5.3 - 5.6,7.0 | +| 6.x | cURL extension, 5.2 - 5.6 | + +## 使用方法 + +### 上传 +```php +use Qiniu\Storage\UploadManager; +use Qiniu\Auth; +... + $upManager = new UploadManager(); + $auth = new Auth($accessKey, $secretKey); + $token = $auth->uploadToken($bucketName); + list($ret, $error) = $upManager->put($token, 'formput', 'hello world'); +... +``` + +## 测试 + +``` bash +$ ./vendor/bin/phpunit tests/Qiniu/Tests/ +``` + +## 常见问题 + +- $error保留了请求响应的信息,失败情况下ret 为none, 将$error可以打印出来,提交给我们。 +- API 的使用 demo 可以参考 [单元测试](https://github.com/qiniu/php-sdk/blob/master/tests)。 + +## 代码贡献 + +详情参考[代码提交指南](https://github.com/qiniu/php-sdk/blob/master/CONTRIBUTING.md)。 + +## 贡献记录 + +- [所有贡献者](https://github.com/qiniu/php-sdk/contributors) + +## 联系我们 + +- 如果需要帮助,请提交工单(在portal右侧点击咨询和建议提交工单,或者直接向 support@qiniu.com 发送邮件) +- 如果有什么问题,可以到问答社区提问,[问答社区](http://qiniu.segmentfault.com/) +- 更详细的文档,见[官方文档站](http://developer.qiniu.com/) +- 如果发现了bug, 欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues) +- 如果有功能需求,欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues) +- 如果要提交代码,欢迎提交 pull request +- 欢迎关注我们的[微信](http://www.qiniu.com/#weixin) [微博](http://weibo.com/qiniutek),及时获取动态信息。 + +## 代码许可 + +The MIT License (MIT).详情见 [License文件](https://github.com/qiniu/php-sdk/blob/master/LICENSE). + +[packagist]: http://packagist.org +[install-packagist]: https://packagist.org/packages/qiniu/php-sdk diff --git a/source/vendor/qiniu/php-sdk/autoload.php b/source/vendor/qiniu/php-sdk/autoload.php new file mode 100644 index 0000000..4379b91 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/autoload.php @@ -0,0 +1,14 @@ +=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.3" + }, + "autoload": { + "psr-4": {"Qiniu\\": "src/Qiniu"}, + "files": ["src/Qiniu/functions.php"] + } +} diff --git a/source/vendor/qiniu/php-sdk/docs/rtc/README.md b/source/vendor/qiniu/php-sdk/docs/rtc/README.md new file mode 100644 index 0000000..7b98ca0 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/docs/rtc/README.md @@ -0,0 +1,71 @@ +# Rtc Streaming Cloud Server-Side Library For PHP + +## Features + +- Appclient + - [x] 创建房间: client->createApp() + - [x] 查看房间: client->getApp() + - [x] 删除房间: client->deleteApp() + - [x] 生成房间token: client->appToken() + + + +## Contents + +- [Installation](#installation) +- [Usage](#usage) + - [Configuration](#configuration) + - [App](#app) + - [Create a app](#create-a-app) + - [Get a app](#get-a-app) + - [Delete a app](#delete-a-app) + - [Generate a app token](#generate-a-app-token) + + +## Usage + +### App + +#### Create a app + +```php +$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5"; +$sk = "xxxx"; +$auth = new Auth($ak, $sk); +$client = new Qiniu\Rtc\AppClient($auth); +$resp=$client->createApp("901","testApp"); +print_r($resp); +``` + +#### Get an app + +```php +$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5"; +$sk = "xxxx"; +$auth = new Auth($ak, $sk); +$client = new Qiniu\Rtc\AppClient($auth); +$resp=$client->getApp("deq02uhb6"); +print_r($resp); +``` + +#### Delete an app + +```php +$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5"; +$sk = "xxxx"; +$auth = new Auth($ak, $sk); +$client = new Qiniu\Rtc\AppClient($auth); +$resp=$client->deleteApp("deq02uhb6"); +print_r($resp); +``` + +#### Generate an app token + +```php +$ak = "gwd_gV4gPKZZsmEOvAuNU1AcumicmuHooTfu64q5"; +$sk = "xxxx"; +$auth = new Auth($ak, $sk); +$client = new Qiniu\Rtc\AppClient($auth); +$resp=$client->appToken("deq02uhb6", "lfx", '1111', (time()+3600), 'user'); +print_r($resp); +``` \ No newline at end of file diff --git a/source/vendor/qiniu/php-sdk/docs/rtc/example.php b/source/vendor/qiniu/php-sdk/docs/rtc/example.php new file mode 100644 index 0000000..f30b9b4 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/docs/rtc/example.php @@ -0,0 +1,42 @@ +createApp($hub, $title, $maxUsers); + print_r($resp); + // 获取app状态 + $resp = $client->getApp('dgdl5ge8y'); + print_r($resp); + //修改app状态 + $mergePublishRtmp = null; + $mergePublishRtmp['enable'] = true; + $resp = $client->updateApp('dgdl5ge8y', $hub, $title, $maxUsers, $mergePublishRtmp); + print_r($resp); + //删除app + $resp = $client->deleteApp('dgdl5ge8y'); + print_r($resp); + //获取房间连麦的成员 + $resp=$client->listUser("dgbfvvzid", 'lfxl'); + print_r($resp); + //剔除房间的连麦成员 + $resp=$client->kickUser("dgbfvvzid", 'lfx', "qiniu-f6e07b78-4dc8-45fb-a701-a9e158abb8e6"); + print_r($resp); + // 列举房间 + $resp=$client->listActiveRooms("dgbfvvzid", 'lfx', null, null); + print_r($resp); + //鉴权的有效时间: 1个小时. + $resp = $client->appToken("dgd4vecde", "lfxl", '1111', (time()+3600), 'user'); + print_r($resp); +} catch (\Exception $e) { + echo "Error:", $e, "\n"; +} diff --git a/source/vendor/qiniu/php-sdk/examples/README.md b/source/vendor/qiniu/php-sdk/examples/README.md new file mode 100644 index 0000000..6cf8e30 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/README.md @@ -0,0 +1,10 @@ +# examples + +这些 examples 旨在帮助你快速了解使用七牛的sdk。这些demo都是可以直接运行的, 但是在运行之前需要填上您自己的参数。 + +比如: + +* `$bucket` 需要填上您想操作的 [bucket名字](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#bucket)。 +* `$accessKey` 和 `$secretKey` 可以在我们的[管理后台](https://portal.qiniu.com/setting/key)找到。 +* 在进行`视频转码`, `压缩文件`等异步操作时 需要使用到的队列名称也可以在我们[管理后台](https://portal.qiniu.com/mps/pipeline)新建。 + diff --git a/source/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php b/source/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php new file mode 100644 index 0000000..4d2ccf6 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/cdn_get_bandwidth.php @@ -0,0 +1,40 @@ +getBandwidthData( + $domains, + $startDate, + $endDate, + $granularity +); + +if ($getBandwidthErr != null) { + var_dump($getBandwidthErr); +} else { + echo "get bandwidth data success\n"; + print_r($bandwidthData); +} diff --git a/source/vendor/qiniu/php-sdk/examples/cdn_get_flux.php b/source/vendor/qiniu/php-sdk/examples/cdn_get_flux.php new file mode 100644 index 0000000..56da550 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/cdn_get_flux.php @@ -0,0 +1,34 @@ +getFluxData($domains, $startDate, $endDate, $granularity); +if ($getFluxErr != null) { + var_dump($getFluxErr); +} else { + echo "get flux data success\n"; + print_r($fluxData); +} diff --git a/source/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php b/source/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php new file mode 100644 index 0000000..4e5c942 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/cdn_get_log_list.php @@ -0,0 +1,29 @@ +getCdnLogList($domains, $logDate); +if ($getLogErr != null) { + var_dump($getLogErr); +} else { + echo "get cdn log list success\n"; + print_r($logListData); +} diff --git a/source/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php b/source/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php new file mode 100644 index 0000000..c05e75f --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/cdn_refresh_urls_dirs.php @@ -0,0 +1,52 @@ +refreshUrlsAndDirs($urls, $dirs); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh request sent\n"; + print_r($refreshResult); +} + +//如果只有刷新链接或者目录的需求,可以分布使用 + +list($refreshResult, $refreshErr) = $cdnManager->refreshUrls($urls); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh request sent\n"; + print_r($refreshResult); +} + +list($refreshResult, $refreshErr) = $cdnManager->refreshDirs($dirs); +if ($refreshErr != null) { + var_dump($refreshErr); +} else { + echo "refresh request sent\n"; + print_r($refreshResult); +} diff --git a/source/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php b/source/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php new file mode 100644 index 0000000..d9fd023 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/cdn_timestamp_antileech.php @@ -0,0 +1,19 @@ + + */ +$thumbLink = $imageUrlBuilder->thumbnail($url, 1, 100, 100); + +// 函数方式调用 也可拼接多个操作参数 图片+水印 +$thumbLink2 = \Qiniu\thumbnail($url2, 1, 100, 100); +var_dump($thumbLink, $thumbLink2); + +/** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param numeric $dissolve 透明度 [可选] + * @param string $gravity 水印位置 [可选] + * @param numeric $dx 横轴边距 [可选] + * @param numeric $dy 纵轴边距 [可选] + * @param numeric $watermarkScale 自适应原图的短边比例 [可选] + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @return string + * @author Sherlock Ren + */ +$waterLink = $imageUrlBuilder->waterImg($url, $waterImage); +// 函数调用方法 +//$waterLink = \Qiniu\waterImg($url, $waterImage); +var_dump($waterLink); + +/** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 [可选] + * @param numeric $dissolve 透明度 [可选] + * @param string $gravity 水印位置 [可选] + * @param numeric $dx 横轴边距 [可选] + * @param numeric $dy 纵轴边距 [可选] + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren + */ +$textLink = $imageUrlBuilder->waterText($url, '你瞅啥', '微软雅黑', 300); +// 函数调用方法 +// $textLink = \Qiniu\waterText($url, '你瞅啥', '微软雅黑', 300); +var_dump($textLink); diff --git a/source/vendor/qiniu/php-sdk/examples/persistent_fop_init.php b/source/vendor/qiniu/php-sdk/examples/persistent_fop_init.php new file mode 100644 index 0000000..2df01e9 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/persistent_fop_init.php @@ -0,0 +1,19 @@ +status($persistentId); + +if ($err) { + print_r($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/pfop_mkzip.php b/source/vendor/qiniu/php-sdk/examples/pfop_mkzip.php new file mode 100644 index 0000000..746b356 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/pfop_mkzip.php @@ -0,0 +1,43 @@ +execute($bucket, $key, $fops, $pipeline, $notify_url, $force); + +echo "\n====> pfop mkzip result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; + + $res = "http://api.qiniu.com/status/get/prefop?id=$id"; + echo "Processing result: $res"; +} diff --git a/source/vendor/qiniu/php-sdk/examples/pfop_vframe.php b/source/vendor/qiniu/php-sdk/examples/pfop_vframe.php new file mode 100644 index 0000000..e1df2d5 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/pfop_vframe.php @@ -0,0 +1,46 @@ +useHTTPS = true; +$pfop = new PersistentFop($auth, $config); + +//要进行视频截图操作 +$fops = "vframe/jpg/offset/1/w/480/h/360/rotate/90|saveas/" . + \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_480x360.jpg"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +//查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php b/source/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php new file mode 100644 index 0000000..aebe815 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/pfop_video_avthumb.php @@ -0,0 +1,47 @@ +useHTTPS=true; + +$pfop = new PersistentFop($auth, $config); + +//要进行转码的转码操作。 http://developer.qiniu.com/docs/v6/api/reference/fop/av/avthumb.html +$fops = "avthumb/mp4/s/640x360/vb/1.4m|saveas/" . \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_640x360.mp4"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +//查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/pfop_watermark.php b/source/vendor/qiniu/php-sdk/examples/pfop_watermark.php new file mode 100644 index 0000000..72aa6c4 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/pfop_watermark.php @@ -0,0 +1,52 @@ +useHTTPS=true; +$pfop = new PersistentFop($auth, $config); + +//需要添加水印的图片UrlSafeBase64 +//可以参考http://developer.qiniu.com/code/v6/api/dora-api/av/video-watermark.html +$base64URL = Qiniu\base64_urlSafeEncode('http://devtools.qiniu.com/qiniu.png'); + +//水印参数 +$fops = "avthumb/mp4/s/640x360/vb/1.4m/image/" . $base64URL . "|saveas/" + . \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_wm.mp4"); + +list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force); +echo "\n====> pfop avthumb result: \n"; +if ($err != null) { + var_dump($err); +} else { + echo "PersistentFop Id: $id\n"; +} + +//查询转码的进度和状态 +list($ret, $err) = $pfop->status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/php-logo.png b/source/vendor/qiniu/php-sdk/examples/php-logo.png new file mode 100644 index 0000000..77e051f Binary files /dev/null and b/source/vendor/qiniu/php-sdk/examples/php-logo.png differ diff --git a/source/vendor/qiniu/php-sdk/examples/prefop.php b/source/vendor/qiniu/php-sdk/examples/prefop.php new file mode 100644 index 0000000..ae61a5f --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/prefop.php @@ -0,0 +1,29 @@ +status($id); +echo "\n====> pfop avthumb status: \n"; +if ($err != null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/pulpvideo.php b/source/vendor/qiniu/php-sdk/examples/pulpvideo.php new file mode 100644 index 0000000..bad8821 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/pulpvideo.php @@ -0,0 +1,55 @@ + 'pulp', + 'params' => array( + 'labels' => array( + array( + 'label' => "1", + 'select' => 1, + 'score' => 2, + ), + ) + ) + ), +); + +$params = array(); +$params = array( + 'async' => false, + 'vframe' => array( + 'mode' => 1, + 'interval' => 8, + ) +); + +$req = array(); +$req['data'] = $reqBody; +$req['ops'] = $ops; +$req['params'] = $params; +$body = json_encode($req); + +$vid = "xxxx"; +list($ret, $err) = $argusManager->pulpVideo($body, $vid); + +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/qetag.php b/source/vendor/qiniu/php-sdk/examples/qetag.php new file mode 100644 index 0000000..f6aff8a --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/qetag.php @@ -0,0 +1,11 @@ + 'video/x-mp4', + 'qiniu.png' => 'image/x-png', + 'qiniu.jpg' => 'image/x-jpg' +); + +$ops = $bucketManager->buildBatchChangeMime($bucket, $keyMimePairs); +list($ret, $err) = $bucketManager->batch($ops); +if ($err) { + print_r($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php b/source/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php new file mode 100644 index 0000000..5f3f1cd --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_batch_change_type.php @@ -0,0 +1,34 @@ +buildBatchChangeType($bucket, $keyTypePairs); +list($ret, $err) = $bucketManager->batch($ops); +if ($err) { + print_r($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_batch_copy.php b/source/vendor/qiniu/php-sdk/examples/rs_batch_copy.php new file mode 100644 index 0000000..988c642 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_batch_copy.php @@ -0,0 +1,36 @@ +buildBatchCopy($srcBucket, $keyPairs, $destBucket, true); +list($ret, $err) = $bucketManager->batch($ops); +if ($err) { + print_r($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_batch_delete.php b/source/vendor/qiniu/php-sdk/examples/rs_batch_delete.php new file mode 100644 index 0000000..4f15586 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_batch_delete.php @@ -0,0 +1,28 @@ +buildBatchDelete($bucket, $keys); +list($ret, $err) = $bucketManager->batch($ops); +if ($err) { + print_r($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php b/source/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php new file mode 100644 index 0000000..dabfe84 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_batch_delete_after_days.php @@ -0,0 +1,34 @@ +buildBatchDeleteAfterDays($bucket, $keyDayPairs); +list($ret, $err) = $bucketManager->batch($ops); +if ($err) { + print_r($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_batch_move.php b/source/vendor/qiniu/php-sdk/examples/rs_batch_move.php new file mode 100644 index 0000000..8922522 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_batch_move.php @@ -0,0 +1,36 @@ +buildBatchMove($srcBucket, $keyPairs, $destBucket, true); +list($ret, $err) = $bucketManager->batch($ops); +if ($err) { + print_r($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_batch_stat.php b/source/vendor/qiniu/php-sdk/examples/rs_batch_stat.php new file mode 100644 index 0000000..a95fee7 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_batch_stat.php @@ -0,0 +1,28 @@ +buildBatchStat($bucket, $keys); +list($ret, $err) = $bucketManager->batch($ops); +if ($err) { + print_r($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php b/source/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php new file mode 100644 index 0000000..ea27cdc --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_bucket_domains.php @@ -0,0 +1,19 @@ +domains($bucket); +if ($err) { + print_r($err); +} else { + print_r($domains); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_buckets.php b/source/vendor/qiniu/php-sdk/examples/rs_buckets.php new file mode 100644 index 0000000..5fe1304 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_buckets.php @@ -0,0 +1,19 @@ +buckets(true); +if ($err) { + print_r($err); +} else { + print_r($buckets); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_change_mime.php b/source/vendor/qiniu/php-sdk/examples/rs_change_mime.php new file mode 100644 index 0000000..0d3f3ad --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_change_mime.php @@ -0,0 +1,20 @@ +changeMime($bucket, $key, $newMime); +if ($err) { + print_r($err); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_change_status.php b/source/vendor/qiniu/php-sdk/examples/rs_change_status.php new file mode 100644 index 0000000..cbcea5c --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_change_status.php @@ -0,0 +1,20 @@ +changeStatus($bucket, $key, $status); +if ($err) { + print_r($err); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_change_type.php b/source/vendor/qiniu/php-sdk/examples/rs_change_type.php new file mode 100644 index 0000000..acb8963 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_change_type.php @@ -0,0 +1,20 @@ +changeType($bucket, $key, $fileType); +if ($err) { + print_r($err); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_copy.php b/source/vendor/qiniu/php-sdk/examples/rs_copy.php new file mode 100644 index 0000000..10e7de8 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_copy.php @@ -0,0 +1,22 @@ +copy($srcBucket, $srcKey, $destBucket, $destKey, true); +if ($err) { + print_r($err); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_delete.php b/source/vendor/qiniu/php-sdk/examples/rs_delete.php new file mode 100644 index 0000000..365d3be --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_delete.php @@ -0,0 +1,17 @@ +delete($bucket, $key); +if ($err) { + print_r($err); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php b/source/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php new file mode 100644 index 0000000..ba0b586 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_delete_after_days.php @@ -0,0 +1,20 @@ +deleteAfterDays($bucket, $key, $days); +if ($err) { + print_r($err); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_download_urls.php b/source/vendor/qiniu/php-sdk/examples/rs_download_urls.php new file mode 100644 index 0000000..522b9f2 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_download_urls.php @@ -0,0 +1,17 @@ +/ +$baseUrl = 'http://if-pri.qiniudn.com/qiniu.png?imageView2/1/h/500'; +// 对链接进行签名 +$signedUrl = $auth->privateDownloadUrl($baseUrl); + +echo $signedUrl; diff --git a/source/vendor/qiniu/php-sdk/examples/rs_fetch.php b/source/vendor/qiniu/php-sdk/examples/rs_fetch.php new file mode 100644 index 0000000..6792410 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_fetch.php @@ -0,0 +1,34 @@ +fetch($url, $bucket, $key); +echo "=====> fetch $url to bucket: $bucket key: $key\n"; +if ($err !== null) { + var_dump($err); +} else { + print_r($ret); +} + +// 不指定key时,以文件内容的hash作为文件名 +$key = null; +list($ret, $err) = $bucketManager->fetch($url, $bucket, $key); +echo "=====> fetch $url to bucket: $bucket key: $(etag)\n"; +if ($err !== null) { + var_dump($err); +} else { + print_r($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_move.php b/source/vendor/qiniu/php-sdk/examples/rs_move.php new file mode 100644 index 0000000..5610585 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_move.php @@ -0,0 +1,22 @@ +move($srcBucket, $srcKey, $destBucket, $destKey, true); +if ($err) { + print_r($err); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_prefetch.php b/source/vendor/qiniu/php-sdk/examples/rs_prefetch.php new file mode 100644 index 0000000..de947a7 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_prefetch.php @@ -0,0 +1,17 @@ +prefetch($bucket, $key); +if ($err) { + print_r($err); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rs_stat.php b/source/vendor/qiniu/php-sdk/examples/rs_stat.php new file mode 100644 index 0000000..891e4e0 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rs_stat.php @@ -0,0 +1,19 @@ +stat($bucket, $key); +if ($err) { + print_r($err); +} else { + print_r($fileInfo); +} diff --git a/source/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php b/source/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php new file mode 100644 index 0000000..5ce9a62 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rsf_list_bucket.php @@ -0,0 +1,46 @@ +listFiles($bucket, $prefix, $marker, $limit, $delimiter); + if ($err !== null) { + echo "\n====> list file err: \n"; + var_dump($err); + } else { + $marker = null; + if (array_key_exists('marker', $ret)) { + $marker = $ret['marker']; + } + echo "Marker: $marker\n"; + echo "\nList Items====>\n"; + //var_dump($ret['items']); + print('items count:' . count($ret['items']) . "\n"); + if (array_key_exists('commonPrefixes', $ret)) { + print_r($ret['commonPrefixes']); + } + } +} while (!empty($marker)); diff --git a/source/vendor/qiniu/php-sdk/examples/rsf_list_files.php b/source/vendor/qiniu/php-sdk/examples/rsf_list_files.php new file mode 100644 index 0000000..a3981c5 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/rsf_list_files.php @@ -0,0 +1,38 @@ +listFiles($bucket, $prefix, $marker, $limit, $delimiter); +if ($err !== null) { + echo "\n====> list file err: \n"; + var_dump($err); +} else { + if (array_key_exists('marker', $ret)) { + echo "Marker:" . $ret["marker"] . "\n"; + } + echo "\nList Iterms====>\n"; + //var_dump($ret['items']); +} diff --git a/source/vendor/qiniu/php-sdk/examples/saveas.php b/source/vendor/qiniu/php-sdk/examples/saveas.php new file mode 100644 index 0000000..d896f3b --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/saveas.php @@ -0,0 +1,28 @@ +:';//为生成缩略图的文件名 +//生成的值 +$encodedEntryURI = \Qiniu\base64_urlSafeEncode($entry); + +//使用SecretKey对新的下载URL进行HMAC1-SHA1签名 +$newurl = "78re52.com1.z0.glb.clouddn.com/resource/Ship.jpg?imageView2/2/w/200/h/200|saveas/" . $encodedEntryURI; + +$sign = hash_hmac("sha1", $newurl, $secretKey, true); + +//对签名进行URL安全的Base64编码 +$encodedSign = \Qiniu\base64_urlSafeEncode($sign); +//最终得到的完整下载URL +$finalURL = "http://" . $newurl . "/sign/" . $accessKey . ":" . $encodedSign; + +$callbackBody = file_get_contents("$finalURL"); + +echo $callbackBody; diff --git a/source/vendor/qiniu/php-sdk/examples/upload_and_callback.php b/source/vendor/qiniu/php-sdk/examples/upload_and_callback.php new file mode 100644 index 0000000..4762bd3 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/upload_and_callback.php @@ -0,0 +1,40 @@ + 'http://your.domain.com/upload_verify_callback.php', + 'callbackBody' => 'filename=$(fname)&filesize=$(fsize)' +); +$uptoken = $auth->uploadToken($bucket, null, 3600, $policy); + +//上传文件的本地路径 +$filePath = './php-logo.png'; + +//指定 config +// $uploadMgr = new UploadManager($config); +$uploadMgr = new UploadManager(); + +list($ret, $err) = $uploadMgr->putFile($uptoken, null, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/upload_and_pfop.php b/source/vendor/qiniu/php-sdk/examples/upload_and_pfop.php new file mode 100644 index 0000000..898c09c --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/upload_and_pfop.php @@ -0,0 +1,38 @@ + $pfop, + 'persistentNotifyUrl' => $notifyUrl, + 'persistentPipeline' => $pipeline +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); + +list($ret, $err) = $uploadMgr->putFile($token, null, $key); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/upload_mgr_init.php b/source/vendor/qiniu/php-sdk/examples/upload_mgr_init.php new file mode 100644 index 0000000..3459ef1 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/upload_mgr_init.php @@ -0,0 +1,18 @@ +uploadToken($bucket); + +// 构建 UploadManager 对象 +$uploadMgr = new UploadManager(); diff --git a/source/vendor/qiniu/php-sdk/examples/upload_multi_demos.php b/source/vendor/qiniu/php-sdk/examples/upload_multi_demos.php new file mode 100644 index 0000000..3bbcd60 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/upload_multi_demos.php @@ -0,0 +1,85 @@ +uploadToken($bucket); +$uploadMgr = new UploadManager(); + +//----------------------------------------upload demo1 ---------------------------------------- +// 上传字符串到七牛 +list($ret, $err) = $uploadMgr->put($token, null, 'content string'); +echo "\n====> put result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//----------------------------------------upload demo2 ---------------------------------------- +// 上传文件到七牛 +$filePath = './php-logo.png'; +$key = 'php-logo.png'; +list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//----------------------------------------upload demo3 ---------------------------------------- +// 上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器. +// 可参考文档: http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html +$policy = array( + 'callbackUrl' => 'http://172.30.251.210/upload_verify_callback.php', + 'callbackBody' => 'filename=$(fname)&filesize=$(fsize)' +// 'callbackBodyType' => 'application/json', +// 'callbackBody' => '{"filename":$(fname), "filesize": $(fsize)}' //设置application/json格式回调 +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); + + +list($ret, $err) = $uploadMgr->putFile($token, null, $key); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} + + +//----------------------------------------upload demo4 ---------------------------------------- +//上传视频,上传完成后进行m3u8的转码, 并给视频打水印 +$wmImg = Qiniu\base64_urlSafeEncode('http://devtools.qiniudn.com/qiniu.png'); +$pfop = "avthumb/m3u8/wmImage/$wmImg"; + +//转码完成后回调到业务服务器。(公网可以访问,并相应200 OK) +$notifyUrl = 'http://notify.fake.com'; + +//独立的转码队列:https://portal.qiniu.com/mps/pipeline + + +$policy = array( + 'persistentOps' => $pfop, + 'persistentNotifyUrl' => $notifyUrl, + 'persistentPipeline' => $pipeline +); +$token = $auth->uploadToken($bucket, null, 3600, $policy); +print($token); +list($ret, $err) = $uploadMgr->putFile($token, null, $key); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/upload_simple_file.php b/source/vendor/qiniu/php-sdk/examples/upload_simple_file.php new file mode 100644 index 0000000..9d003f0 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/upload_simple_file.php @@ -0,0 +1,37 @@ +uploadToken($bucket); + +// 要上传文件的本地路径 +$filePath = './php-logo.png'; + +// 上传到七牛后保存的文件名 +$key = 'my-php-logo.png'; + +// 初始化 UploadManager 对象并进行文件的上传。 +$uploadMgr = new UploadManager(); + +// 调用 UploadManager 的 putFile 方法进行文件的上传。 +list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath); +echo "\n====> putFile result: \n"; +if ($err !== null) { + var_dump($err); +} else { + var_dump($ret); +} diff --git a/source/vendor/qiniu/php-sdk/examples/upload_tokens.php b/source/vendor/qiniu/php-sdk/examples/upload_tokens.php new file mode 100644 index 0000000..90f2423 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/upload_tokens.php @@ -0,0 +1,68 @@ +uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +// 自定义凭证有效期(示例2小时) +$expires = 7200; +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +// 覆盖上传凭证 +$expires = 3600; +$keyToOverwrite = 'qiniu.mp4'; +$upToken = $auth->uploadToken($bucket, $keyToOverwrite, $expires, $policy, true); +print($upToken . "\n"); + +//自定义上传回复(非callback模式)凭证 +$returnBody = '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}'; +$policy = array( + 'returnBody' => $returnBody +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//带回调业务服务器的凭证(application/json) +$policy = array( + 'callbackUrl' => 'http://api.example.com/qiniu/upload/callback', + 'callbackBody' => '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}', + 'callbackBodyType' => 'application/json' +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + + +//带回调业务服务器的凭证(application/x-www-form-urlencoded) +$policy = array( + 'callbackUrl' => 'http://api.example.com/qiniu/upload/callback', + 'callbackBody' => 'key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)' +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); + +//带数据处理的凭证 +$saveMp4Entry = \Qiniu\base64_urlSafeEncode($bucket . ":avthumb_test_target.mp4"); +$saveJpgEntry = \Qiniu\base64_urlSafeEncode($bucket . ":vframe_test_target.jpg"); +$avthumbMp4Fop = "avthumb/mp4|saveas/" . $saveMp4Entry; +$vframeJpgFop = "vframe/jpg/offset/1|saveas/" . $saveJpgEntry; +$policy = array( + 'persistentOps' => $avthumbMp4Fop . ";" . $vframeJpgFop, + 'persistentPipeline' => "video-pipe", + 'persistentNotifyUrl' => "http://api.example.com/qiniu/pfop/notify", +); +$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true); +print($upToken . "\n"); diff --git a/source/vendor/qiniu/php-sdk/examples/upload_verify_callback.php b/source/vendor/qiniu/php-sdk/examples/upload_verify_callback.php new file mode 100644 index 0000000..ffbbc80 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/examples/upload_verify_callback.php @@ -0,0 +1,32 @@ +verifyCallback($contentType, $authorization, $url, $callbackBody); + +if ($isQiniuCallback) { + $resp = array('ret' => 'success'); +} else { + $resp = array('ret' => 'failed'); +} + +echo json_encode($resp); diff --git a/source/vendor/qiniu/php-sdk/phpunit.xml.dist b/source/vendor/qiniu/php-sdk/phpunit.xml.dist new file mode 100644 index 0000000..72ff67f --- /dev/null +++ b/source/vendor/qiniu/php-sdk/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + tests + + + + diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Auth.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Auth.php new file mode 100644 index 0000000..b5e4a6b --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Auth.php @@ -0,0 +1,187 @@ +accessKey = $accessKey; + $this->secretKey = $secretKey; + } + + public function getAccessKey() + { + return $this->accessKey; + } + + public function sign($data) + { + $hmac = hash_hmac('sha1', $data, $this->secretKey, true); + return $this->accessKey . ':' . \Qiniu\base64_urlSafeEncode($hmac); + } + + public function signWithData($data) + { + $encodedData = \Qiniu\base64_urlSafeEncode($data); + return $this->sign($encodedData) . ':' . $encodedData; + } + + public function signRequest($urlString, $body, $contentType = null) + { + $url = parse_url($urlString); + $data = ''; + if (array_key_exists('path', $url)) { + $data = $url['path']; + } + if (array_key_exists('query', $url)) { + $data .= '?' . $url['query']; + } + $data .= "\n"; + + if ($body !== null && $contentType === 'application/x-www-form-urlencoded') { + $data .= $body; + } + return $this->sign($data); + } + + public function verifyCallback($contentType, $originAuthorization, $url, $body) + { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + return $originAuthorization === $authorization; + } + + public function privateDownloadUrl($baseUrl, $expires = 3600) + { + $deadline = time() + $expires; + + $pos = strpos($baseUrl, '?'); + if ($pos !== false) { + $baseUrl .= '&e='; + } else { + $baseUrl .= '?e='; + } + $baseUrl .= $deadline; + + $token = $this->sign($baseUrl); + return "$baseUrl&token=$token"; + } + + public function uploadToken($bucket, $key = null, $expires = 3600, $policy = null, $strictPolicy = true) + { + $deadline = time() + $expires; + $scope = $bucket; + if ($key !== null) { + $scope .= ':' . $key; + } + + $args = self::copyPolicy($args, $policy, $strictPolicy); + $args['scope'] = $scope; + $args['deadline'] = $deadline; + + $b = json_encode($args); + return $this->signWithData($b); + } + + /** + *上传策略,参数规格详见 + *http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html + */ + private static $policyFields = array( + 'callbackUrl', + 'callbackBody', + 'callbackHost', + 'callbackBodyType', + 'callbackFetchKey', + + 'returnUrl', + 'returnBody', + + 'endUser', + 'saveKey', + 'insertOnly', + + 'detectMime', + 'mimeLimit', + 'fsizeMin', + 'fsizeLimit', + + 'persistentOps', + 'persistentNotifyUrl', + 'persistentPipeline', + + 'deleteAfterDays', + 'fileType', + 'isPrefixalScope', + ); + + private static function copyPolicy(&$policy, $originPolicy, $strictPolicy) + { + if ($originPolicy === null) { + return array(); + } + foreach ($originPolicy as $key => $value) { + if (!$strictPolicy || in_array((string)$key, self::$policyFields, true)) { + $policy[$key] = $value; + } + } + return $policy; + } + + public function authorization($url, $body = null, $contentType = null) + { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + return array('Authorization' => $authorization); + } + + public function authorizationV2($url, $method, $body = null, $contentType = null) + { + $urlItems = parse_url($url); + $host = $urlItems['host']; + + if (isset($urlItems['port'])) { + $port = $urlItems['port']; + } else { + $port = ''; + } + + $path = $urlItems['path']; + if (isset($urlItems['query'])) { + $query = $urlItems['query']; + } else { + $query = ''; + } + + //write request uri + $toSignStr = $method . ' ' . $path; + if (!empty($query)) { + $toSignStr .= '?' . $query; + } + + //write host and port + $toSignStr .= "\nHost: " . $host; + if (!empty($port)) { + $toSignStr .= ":" . $port; + } + + //write content type + if (!empty($contentType)) { + $toSignStr .= "\nContent-Type: " . $contentType; + } + + $toSignStr .= "\n\n"; + + //write body + if (!empty($body)) { + $toSignStr .= $body; + } + + $sign = $this->sign($toSignStr); + $auth = 'Qiniu ' . $sign; + return array('Authorization' => $auth); + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php new file mode 100644 index 0000000..0e6abac --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Cdn/CdnManager.php @@ -0,0 +1,191 @@ +auth = $auth; + $this->server = 'http://fusion.qiniuapi.com'; + } + + /** + * @param array $urls 待刷新的文件链接数组 + * @return array + */ + public function refreshUrls(array $urls) + { + return $this->refreshUrlsAndDirs($urls, array()); + } + + /** + * @param array $dirs 待刷新的文件链接数组 + * @return array + * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh + * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category + */ + public function refreshDirs(array $dirs) + { + return $this->refreshUrlsAndDirs(array(), $dirs); + } + + /** + * @param array $urls 待刷新的文件链接数组 + * @param array $dirs 待刷新的目录链接数组 + * + * @return array 刷新的请求回复和错误,参考 examples/cdn_manager.php 代码 + * @link http://developer.qiniu.com/article/fusion/api/refresh.html + * + * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh + * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category + */ + public function refreshUrlsAndDirs(array $urls, array $dirs) + { + $req = array(); + if (!empty($urls)) { + $req['urls'] = $urls; + } + if (!empty($dirs)) { + $req['dirs'] = $dirs; + } + + $url = $this->server . '/v2/tune/refresh'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $urls 待预取的文件链接数组 + * + * @return array 预取的请求回复和错误,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/refresh.html + */ + public function prefetchUrls(array $urls) + { + $req = array( + 'urls' => $urls, + ); + + $url = $this->server . '/v2/tune/prefetch'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取带宽数据的域名数组 + * @param string $startDate 开始的日期,格式类似 2017-01-01 + * @param string $endDate 结束的日期,格式类似 2017-01-01 + * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day + * + * @return array 带宽数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html + */ + public function getBandwidthData(array $domains, $startDate, $endDate, $granularity) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['startDate'] = $startDate; + $req['endDate'] = $endDate; + $req['granularity'] = $granularity; + + $url = $this->server . '/v2/tune/bandwidth'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取流量数据的域名数组 + * @param string $startDate 开始的日期,格式类似 2017-01-01 + * @param string $endDate 结束的日期,格式类似 2017-01-01 + * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day + * + * @return array 流量数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html + */ + public function getFluxData(array $domains, $startDate, $endDate, $granularity) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['startDate'] = $startDate; + $req['endDate'] = $endDate; + $req['granularity'] = $granularity; + + $url = $this->server . '/v2/tune/flux'; + $body = json_encode($req); + return $this->post($url, $body); + } + + /** + * @param array $domains 待获取日志下载链接的域名数组 + * @param string $logDate 获取指定日期的日志下载链接,格式类似 2017-01-01 + * + * @return array 日志下载链接数据和错误信息,参考 examples/cdn_manager.php 代码 + * + * @link http://developer.qiniu.com/article/fusion/api/log.html + */ + public function getCdnLogList(array $domains, $logDate) + { + $req = array(); + $req['domains'] = implode(';', $domains); + $req['day'] = $logDate; + + $url = $this->server . '/v2/tune/log/list'; + $body = json_encode($req); + return $this->post($url, $body); + } + + private function post($url, $body) + { + $headers = $this->auth->authorization($url, $body, 'application/json'); + $headers['Content-Type'] = 'application/json'; + $ret = Client::post($url, $body, $headers); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + /** + * 构建时间戳防盗链鉴权的访问外链 + * + * @param string $rawUrl 需要签名的资源url + * @param string $encryptKey 时间戳防盗链密钥 + * @param string $durationInSeconds 链接的有效期(以秒为单位) + * + * @return string 带鉴权信息的资源外链,参考 examples/cdn_timestamp_antileech.php 代码 + */ + public static function createTimestampAntiLeechUrl($rawUrl, $encryptKey, $durationInSeconds) + { + + $parsedUrl = parse_url($rawUrl); + + $deadline = time() + $durationInSeconds; + $expireHex = dechex($deadline); + $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : ''; + $path = implode('/', array_map('rawurlencode', explode('/', $path))); + + $strToSign = $encryptKey . $path . $expireHex; + $signStr = md5($strToSign); + + if (isset($parsedUrl['query'])) { + $signedUrl = $rawUrl . '&sign=' . $signStr . '&t=' . $expireHex; + } else { + $signedUrl = $rawUrl . '?sign=' . $signStr . '&t=' . $expireHex; + } + + return $signedUrl; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Config.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Config.php new file mode 100644 index 0000000..b1f6367 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Config.php @@ -0,0 +1,137 @@ +zone = $z; + $this->useHTTPS = false; + $this->useCdnDomains = false; + $this->zoneCache = array(); + } + + public function getUpHost($accessKey, $bucket) + { + $zone = $this->getZone($accessKey, $bucket); + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $zone->srcUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $zone->cdnUpHosts[0]; + } + + return $scheme . $host; + } + + public function getUpBackupHost($accessKey, $bucket) + { + $zone = $this->getZone($accessKey, $bucket); + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + $host = $zone->cdnUpHosts[0]; + if ($this->useCdnDomains === true) { + $host = $zone->srcUpHosts[0]; + } + + return $scheme . $host; + } + + public function getRsHost($accessKey, $bucket) + { + $zone = $this->getZone($accessKey, $bucket); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $zone->rsHost; + } + + public function getRsfHost($accessKey, $bucket) + { + $zone = $this->getZone($accessKey, $bucket); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $zone->rsfHost; + } + + public function getIovipHost($accessKey, $bucket) + { + $zone = $this->getZone($accessKey, $bucket); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $zone->iovipHost; + } + + public function getApiHost($accessKey, $bucket) + { + $zone = $this->getZone($accessKey, $bucket); + + if ($this->useHTTPS === true) { + $scheme = "https://"; + } else { + $scheme = "http://"; + } + + return $scheme . $zone->apiHost; + } + + private function getZone($accessKey, $bucket) + { + $cacheId = "$accessKey:$bucket"; + + if (isset($this->zoneCache[$cacheId])) { + $zone = $this->zoneCache[$cacheId]; + } elseif (isset($this->zone)) { + $zone = $this->zone; + $this->zoneCache[$cacheId] = $zone; + } else { + $zone = Zone::queryZone($accessKey, $bucket); + $this->zoneCache[$cacheId] = $zone; + } + return $zone; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Etag.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Etag.php new file mode 100644 index 0000000..d7be064 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Etag.php @@ -0,0 +1,76 @@ + $val) { + array_push($data, '--' . $mimeBoundary); + array_push($data, "Content-Disposition: form-data; name=\"$key\""); + array_push($data, ''); + array_push($data, $val); + } + + array_push($data, '--' . $mimeBoundary); + $finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType; + $finalFileName = self::escapeQuotes($fileName); + array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$finalFileName\""); + array_push($data, "Content-Type: $finalMimeType"); + array_push($data, ''); + array_push($data, $fileBody); + + array_push($data, '--' . $mimeBoundary . '--'); + array_push($data, ''); + + $body = implode("\r\n", $data); + $contentType = 'multipart/form-data; boundary=' . $mimeBoundary; + $headers['Content-Type'] = $contentType; + $request = new Request('POST', $url, $headers, $body); + return self::sendRequest($request); + } + + private static function userAgent() + { + $sdkInfo = "QiniuPHP/" . Config::SDK_VER; + + $systemInfo = php_uname("s"); + $machineInfo = php_uname("m"); + + $envInfo = "($systemInfo/$machineInfo)"; + + $phpVer = phpversion(); + + $ua = "$sdkInfo $envInfo PHP/$phpVer"; + return $ua; + } + + public static function sendRequest($request) + { + $t1 = microtime(true); + $ch = curl_init(); + $options = array( + CURLOPT_USERAGENT => self::userAgent(), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => false, + CURLOPT_CUSTOMREQUEST => $request->method, + CURLOPT_URL => $request->url, + ); + + // Handle open_basedir & safe mode + if (!ini_get('safe_mode') && !ini_get('open_basedir')) { + $options[CURLOPT_FOLLOWLOCATION] = true; + } + + if (!empty($request->headers)) { + $headers = array(); + foreach ($request->headers as $key => $val) { + array_push($headers, "$key: $val"); + } + $options[CURLOPT_HTTPHEADER] = $headers; + } + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); + + if (!empty($request->body)) { + $options[CURLOPT_POSTFIELDS] = $request->body; + } + curl_setopt_array($ch, $options); + $result = curl_exec($ch); + $t2 = microtime(true); + $duration = round($t2 - $t1, 3); + $ret = curl_errno($ch); + if ($ret !== 0) { + $r = new Response(-1, $duration, array(), null, curl_error($ch)); + curl_close($ch); + return $r; + } + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $headers = self::parseHeaders(substr($result, 0, $header_size)); + $body = substr($result, $header_size); + curl_close($ch); + return new Response($code, $duration, $headers, $body, null); + } + + private static function parseHeaders($raw) + { + $headers = array(); + $headerLines = explode("\r\n", $raw); + foreach ($headerLines as $line) { + $headerLine = trim($line); + $kv = explode(':', $headerLine); + if (count($kv) > 1) { + $kv[0] =self::ucwordsHyphen($kv[0]); + $headers[$kv[0]] = trim($kv[1]); + } + } + return $headers; + } + + private static function escapeQuotes($str) + { + $find = array("\\", "\""); + $replace = array("\\\\", "\\\""); + return str_replace($find, $replace, $str); + } + + private static function ucwordsHyphen($str) + { + return str_replace('- ', '-', ucwords(str_replace('-', '- ', $str))); + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php new file mode 100644 index 0000000..73477cf --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Error.php @@ -0,0 +1,35 @@ + + * {"error" : "detailed error message"} + * + */ +final class Error +{ + private $url; + private $response; + + public function __construct($url, $response) + { + $this->url = $url; + $this->response = $response; + } + + public function code() + { + return $this->response->statusCode; + } + + public function getResponse() + { + return $this->response; + } + + public function message() + { + return $this->response->error; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php new file mode 100644 index 0000000..43b0bfd --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Request.php @@ -0,0 +1,18 @@ +method = strtoupper($method); + $this->url = $url; + $this->headers = $headers; + $this->body = $body; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php new file mode 100644 index 0000000..f22ab37 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Http/Response.php @@ -0,0 +1,176 @@ + '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', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use 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 Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 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 (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ); + + /** + * @param int $code 状态码 + * @param double $duration 请求时长 + * @param array $headers 响应头部 + * @param string $body 响应内容 + * @param string $error 错误描述 + */ + public function __construct($code, $duration, array $headers = array(), $body = null, $error = null) + { + $this->statusCode = $code; + $this->duration = $duration; + $this->headers = $headers; + $this->body = $body; + $this->error = $error; + $this->jsonData = null; + if ($error !== null) { + return; + } + + if ($body === null) { + if ($code >= 400) { + $this->error = self::$statusTexts[$code]; + } + return; + } + if (self::isJson($headers)) { + try { + $jsonData = self::bodyJson($body); + if ($code >= 400) { + $this->error = $body; + if ($jsonData['error'] !== null) { + $this->error = $jsonData['error']; + } + } + $this->jsonData = $jsonData; + } catch (\InvalidArgumentException $e) { + $this->error = $body; + if ($code >= 200 && $code < 300) { + $this->error = $e->getMessage(); + } + } + } elseif ($code >= 400) { + $this->error = $body; + } + return; + } + + public function json() + { + return $this->jsonData; + } + + private static function bodyJson($body) + { + return \Qiniu\json_decode((string) $body, true, 512); + } + + public function xVia() + { + $via = $this->headers['X-Via']; + if ($via === null) { + $via = $this->headers['X-Px']; + } + if ($via === null) { + $via = $this->headers['Fw-Via']; + } + return $via; + } + + public function xLog() + { + return $this->headers['X-Log']; + } + + public function xReqId() + { + return $this->headers['X-Reqid']; + } + + public function ok() + { + return $this->statusCode >= 200 && $this->statusCode < 300 && $this->error === null; + } + + public function needRetry() + { + $code = $this->statusCode; + if ($code < 0 || ($code / 100 === 5 and $code !== 579) || $code === 996) { + return true; + } + } + + private static function isJson($headers) + { + return array_key_exists('Content-Type', $headers) && + strpos($headers['Content-Type'], 'application/json') === 0; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php new file mode 100644 index 0000000..1ac5bf7 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/ImageUrlBuilder.php @@ -0,0 +1,282 @@ + + */ + public function thumbnail( + $url, + $mode, + $width, + $height, + $format = null, + $interlace = null, + $quality = null, + $ignoreError = 1 + ) { + + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + // 参数合法性效验 + if (!in_array(intval($mode), $this->modeArr, true)) { + return $url; + } + + if (!$width || !$height) { + return $url; + } + + $thumbStr = 'imageView2/' . $mode . '/w/' . $width . '/h/' . $height . '/'; + + // 拼接输出格式 + if (!is_null($format) + && in_array($format, $this->formatArr) + ) { + $thumbStr .= 'format/' . $format . '/'; + } + + // 拼接渐进显示 + if (!is_null($interlace) + && in_array(intval($interlace), array(0, 1), true) + ) { + $thumbStr .= 'interlace/' . $interlace . '/'; + } + + // 拼接图片质量 + if (!is_null($quality) + && intval($quality) >= 0 + && intval($quality) <= 100 + ) { + $thumbStr .= 'q/' . $quality . '/'; + } + + $thumbStr .= 'ignore-error/' . $ignoreError . '/'; + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $thumbStr; + } + + /** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @param numeric $watermarkScale 自适应原图的短边比例 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @return string + * @author Sherlock Ren + */ + public function waterImg( + $url, + $image, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null, + $watermarkScale = null + ) { + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + $waterStr = 'watermark/1/image/' . \Qiniu\base64_urlSafeEncode($image) . '/'; + + // 拼接水印透明度 + if (is_numeric($dissolve) + && $dissolve <= 100 + ) { + $waterStr .= 'dissolve/' . $dissolve . '/'; + } + + // 拼接水印位置 + if (in_array($gravity, $this->gravityArr, true)) { + $waterStr .= 'gravity/' . $gravity . '/'; + } + + // 拼接横轴边距 + if (!is_null($dx) + && is_numeric($dx) + ) { + $waterStr .= 'dx/' . $dx . '/'; + } + + // 拼接纵轴边距 + if (!is_null($dy) + && is_numeric($dy) + ) { + $waterStr .= 'dy/' . $dy . '/'; + } + + // 拼接自适应原图的短边比例 + if (!is_null($watermarkScale) + && is_numeric($watermarkScale) + && $watermarkScale > 0 + && $watermarkScale < 1 + ) { + $waterStr .= 'ws/' . $watermarkScale . '/'; + } + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr; + } + + /** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren + */ + public function waterText( + $url, + $text, + $font = '黑体', + $fontSize = 0, + $fontColor = null, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null + ) { + // url合法效验 + if (!$this->isUrl($url)) { + return $url; + } + + $waterStr = 'watermark/2/text/' + . \Qiniu\base64_urlSafeEncode($text) . '/font/' + . \Qiniu\base64_urlSafeEncode($font) . '/'; + + // 拼接文字大小 + if (is_int($fontSize)) { + $waterStr .= 'fontsize/' . $fontSize . '/'; + } + + // 拼接文字颜色 + if (!is_null($fontColor) + && $fontColor + ) { + $waterStr .= 'fill/' . \Qiniu\base64_urlSafeEncode($fontColor) . '/'; + } + + // 拼接水印透明度 + if (is_numeric($dissolve) + && $dissolve <= 100 + ) { + $waterStr .= 'dissolve/' . $dissolve . '/'; + } + + // 拼接水印位置 + if (in_array($gravity, $this->gravityArr, true)) { + $waterStr .= 'gravity/' . $gravity . '/'; + } + + // 拼接横轴边距 + if (!is_null($dx) + && is_numeric($dx) + ) { + $waterStr .= 'dx/' . $dx . '/'; + } + + // 拼接纵轴边距 + if (!is_null($dy) + && is_numeric($dy) + ) { + $waterStr .= 'dy/' . $dy . '/'; + } + + // 如果有query_string用|线分割实现多参数 + return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr; + } + + /** + * 效验url合法性 + * + * @param string $url url链接 + * @return string + * @author Sherlock Ren + */ + protected function isUrl($url) + { + $urlArr = parse_url($url); + + return $urlArr['scheme'] + && in_array($urlArr['scheme'], array('http', 'https')) + && $urlArr['host'] + && $urlArr['path']; + } + + /** + * 检测是否有query + * + * @param string $url url链接 + * @return string + * @author Sherlock Ren + */ + protected function hasQuery($url) + { + $urlArr = parse_url($url); + + return !empty($urlArr['query']); + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php new file mode 100644 index 0000000..919136f --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/Operation.php @@ -0,0 +1,60 @@ +auth = $auth; + $this->domain = $domain; + $this->token_expire = $token_expire; + } + + + /** + * 对资源文件进行处理 + * + * @param $key 待处理的资源文件名 + * @param $fops string|array fop操作,多次fop操作以array的形式传入。 + * eg. imageView2/1/w/200/h/200, imageMogr2/thumbnail/!75px + * + * @return array 文件处理后的结果及错误。 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/ + */ + public function execute($key, $fops) + { + $url = $this->buildUrl($key, $fops); + $resp = Client::get($url); + if (!$resp->ok()) { + return array(null, new Error($url, $resp)); + } + if ($resp->json() !== null) { + return array($resp->json(), null); + } + return array($resp->body, null); + } + + public function buildUrl($key, $fops, $protocol = 'http') + { + if (is_array($fops)) { + $fops = implode('|', $fops); + } + + $url = $protocol . "://$this->domain/$key?$fops"; + if ($this->auth !== null) { + $url = $this->auth->privateDownloadUrl($url, $this->token_expire); + } + + return $url; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php new file mode 100644 index 0000000..24e7b73 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Processing/PersistentFop.php @@ -0,0 +1,94 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + } + + /** + * 对资源文件进行异步持久化处理 + * @param $bucket 资源所在空间 + * @param $key 待处理的源文件 + * @param $fops string|array 待处理的pfop操作,多个pfop操作以array的形式传入。 + * eg. avthumb/mp3/ab/192k, vframe/jpg/offset/7/w/480/h/360 + * @param $pipeline 资源处理队列 + * @param $notify_url 处理结果通知地址 + * @param $force 是否强制执行一次新的指令 + * + * + * @return array 返回持久化处理的persistentId, 和返回的错误。 + * + * @link http://developer.qiniu.com/docs/v6/api/reference/fop/ + */ + public function execute($bucket, $key, $fops, $pipeline = null, $notify_url = null, $force = false) + { + if (is_array($fops)) { + $fops = implode(';', $fops); + } + $params = array('bucket' => $bucket, 'key' => $key, 'fops' => $fops); + \Qiniu\setWithoutEmpty($params, 'pipeline', $pipeline); + \Qiniu\setWithoutEmpty($params, 'notifyURL', $notify_url); + if ($force) { + $params['force'] = 1; + } + $data = http_build_query($params); + $scheme = "http://"; + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $url = $scheme . Config::API_HOST . '/pfop/'; + $headers = $this->auth->authorization($url, $data, 'application/x-www-form-urlencoded'); + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $response = Client::post($url, $data, $headers); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + $r = $response->json(); + $id = $r['persistentId']; + return array($id, null); + } + + public function status($id) + { + $scheme = "http://"; + + if ($this->config->useHTTPS === true) { + $scheme = "https://"; + } + $url = $scheme . Config::API_HOST . "/status/get/prefop?id=$id"; + $response = Client::get($url); + if (!$response->ok()) { + return array(null, new Error($url, $response)); + } + return array($response->json(), null); + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php new file mode 100644 index 0000000..c07ee72 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Rtc/AppClient.php @@ -0,0 +1,204 @@ +auth = $auth; + + $this->baseURL = sprintf("%s/%s/apps", Config::RTCAPI_HOST, Config::RTCAPI_VERSION); + } + + /* + * hub: 直播空间名 + * title: app 的名称 注意,Title 不是唯一标识,重复 create 动作将生成多个 app + * maxUsers:人数限制 + * NoAutoKickUser: bool 类型,可选,禁止自动踢人(抢流)。默认为 false , + 即同一个身份的 client (app/room/user) ,新的连麦请求可以成功,旧连接被关闭。 + */ + public function createApp($hub, $title, $maxUsers = null, $noAutoKickUser = null) + { + $params['hub'] = $hub; + $params['title'] = $title; + if (!empty($maxUsers)) { + $params['maxUsers'] = $maxUsers; + } + if (!empty($noAutoKickUser)) { + $params['noAutoKickUser'] = $noAutoKickUser; + } + $body = json_encode($params); + $ret = $this->post($this->baseURL, $body); + return $ret; + } + + /* + * appId: app 的唯一标识,创建的时候由系统生成。 + * Title: app 的名称, 可选。 + * Hub: 绑定的直播 hub,可选,用于合流后 rtmp 推流。 + * MaxUsers: int 类型,可选,连麦房间支持的最大在线人数。 + * NoAutoKickUser: bool 类型,可选,禁止自动踢人。 + * MergePublishRtmp: 连麦合流转推 RTMP 的配置,可选择。其详细配置包括如下 + Enable: 布尔类型,用于开启和关闭所有房间的合流功能。 + AudioOnly: 布尔类型,可选,指定是否只合成音频。 + Height, Width: int64,可选,指定合流输出的高和宽,默认为 640 x 480。 + OutputFps: int64,可选,指定合流输出的帧率,默认为 25 fps 。 + OutputKbps: int64,可选,指定合流输出的码率,默认为 1000 。 + URL: 合流后转推旁路直播的地址,可选,支持魔法变量配置按照连麦房间号生成不同的推流地址。如果是转推到七牛直播云,不建议使用该配置。 + StreamTitle: 转推七牛直播云的流名,可选,支持魔法变量配置按照连麦房间号生成不同的流名。例如,配置 Hub 为 qn-zhibo ,配置 StreamTitle 为 $(roomName) ,则房间 meeting-001 的合流将会被转推到 rtmp://pili-publish.qn-zhibo.***.com/qn-zhibo/meeting-001地址。详细配置细则,请咨询七牛技术支持。 + */ + public function updateApp($appId, $hub, $title, $maxUsers = null, $mergePublishRtmp = null, $noAutoKickUser = null) + { + $url = $this->baseURL . '/' . $appId; + $params['hub'] = $hub; + $params['title'] = $title; + if (!empty($maxUsers)) { + $params['maxUsers'] = $maxUsers; + } + if (!empty($noAutoKickUser)) { + $params['noAutoKickUser'] = $noAutoKickUser; + } + if (!empty($mergePublishRtmp)) { + $params['mergePublishRtmp'] = $mergePublishRtmp; + } + $body = json_encode($params); + $ret = $this->post($url, $body); + return $ret; + } + + /* + * appId: app 的唯一标识,创建的时候由系统生成。 + */ + public function getApp($appId) + { + $url = $this->baseURL . '/' . $appId; + $ret = $this->get($url); + return $ret; + } + + /* + * appId: app 的唯一标识,创建的时候由系统生成 + */ + public function deleteApp($appId) + { + $url = $this->baseURL . '/' . $appId; + list(, $err) = $this->delete($url); + return $err; + } + + /* + * 获取房间的人数 + * appId: app 的唯一标识,创建的时候由系统生成。 + * roomName: 操作所查询的连麦房间。 + */ + public function listUser($appId, $roomName) + { + $url = sprintf("%s/%s/rooms/%s/users", $this->baseURL, $appId, $roomName); + $ret = $this->get($url); + return $ret; + } + + /* + * 踢出玩家 + * appId: app 的唯一标识,创建的时候由系统生成。 + * roomName: 连麦房间 + * userId: 请求加入房间的用户ID + */ + public function kickUser($appId, $roomName, $userId) + { + $url = sprintf("%s/%s/rooms/%s/users/%s", $this->baseURL, $appId, $roomName, $userId); + list(, $err) = $this->delete($url); + return $err; + } + + /* + * 获取房间的人数 + * appId: app 的唯一标识,创建的时候由系统生成。 + * prefix: 所查询房间名的前缀索引,可以为空。 + * offset: int 类型,分页查询的位移标记。 + * limit: int 类型,此次查询的最大长度。 + * GET /v3/apps//rooms?prefix=&offset=&limit= + */ + public function listActiveRooms($appId, $prefix = null, $offset = null, $limit = null) + { + if (isset($prefix)) { + $query['prefix'] = $prefix; + } + if (isset($offset)) { + $query['offset'] = $offset; + } + if (isset($limit)) { + $query['limit'] = $limit; + } + if (isset($query) && !empty($query)) { + $query = '?' . http_build_query($query); + $url = sprintf("%s/%s/rooms%s", $this->baseURL, $appId, $query); + } else { + $url = sprintf("%s/%s/rooms", $this->baseURL, $appId); + } + $ret = $this->get($url); + return $ret; + } + + /* + * appId: app 的唯一标识,创建的时候由系统生成。 + * roomName: 房间名称,需满足规格 ^[a-zA-Z0-9_-]{3,64}$ + * userId: 请求加入房间的用户 ID,需满足规格 ^[a-zA-Z0-9_-]{3,50}$ + * expireAt: int64 类型,鉴权的有效时间,传入以秒为单位的64位Unix + 绝对时间,token 将在该时间后失效。 + * permission: 该用户的房间管理权限,"admin" 或 "user",默认为 "user" 。 + 当权限角色为 "admin" 时,拥有将其他用户移除出房间等特权. + */ + public function appToken($appId, $roomName, $userId, $expireAt, $permission) + { + $params['appId'] = $appId; + $params['userId'] = $userId; + $params['roomName'] = $roomName; + $params['permission'] = $permission; + $params['expireAt'] = $expireAt; + $appAccessString = json_encode($params); + return $this->auth->signWithData($appAccessString); + } + + private function get($url, $cType = null) + { + $rtcToken = $this->auth->authorizationV2($url, "GET", null, $cType); + $rtcToken['Content-Type'] = $cType; + $ret = Client::get($url, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function delete($url, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "DELETE", null, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::delete($url, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body, $contentType = 'application/json') + { + $rtcToken = $this->auth->authorizationV2($url, "POST", $body, $contentType); + $rtcToken['Content-Type'] = $contentType; + $ret = Client::post($url, $body, $rtcToken); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php new file mode 100644 index 0000000..0889370 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/ArgusManager.php @@ -0,0 +1,73 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + } + + /** + * 视频鉴黄 + * + * @param $body body信息 + * @param $vid videoID + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/dora/manual/4258/video-pulp + */ + public function pulpVideo($body, $vid) + { + $path = '/v1/video/' . $vid; + + return $this->arPost($path, $body); + } + + private function getArHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::ARGUS_HOST; + } + + private function arPost($path, $body = null) + { + $url = $this->getArHost() . $path; + return $this->post($url, $body); + } + + private function post($url, $body) + { + $headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/json'); + $headers['Content-Type']='application/json'; + $ret = Client::post($url, $body, $headers); + if (!$ret->ok()) { + print($ret->statusCode); + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php new file mode 100644 index 0000000..2e82a14 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/BucketManager.php @@ -0,0 +1,492 @@ +auth = $auth; + if ($config == null) { + $this->config = new Config(); + } else { + $this->config = $config; + } + } + + /** + * 获取指定账号下所有的空间名。 + * + * @return string[] 包含所有空间名 + */ + public function buckets($shared = true) + { + $includeShared = "false"; + if ($shared === true) { + $includeShared = "true"; + } + return $this->rsGet('/buckets?shared=' . $includeShared); + } + + /** + * 获取指定空间绑定的所有的域名 + * + * @return string[] 包含所有空间域名 + */ + public function domains($bucket) + { + return $this->apiGet('/v6/domain/list?tbl=' . $bucket); + } + + /** + * 获取空间绑定的域名列表 + * @return string[] 包含空间绑定的所有域名 + */ + + /** + * 列取空间的文件列表 + * + * @param $bucket 空间名 + * @param $prefix 列举前缀 + * @param $marker 列举标识符 + * @param $limit 单次列举个数限制 + * @param $delimiter 指定目录分隔符 + * + * @return array 包含文件信息的数组,类似:[ + * { + * "hash" => "", + * "key" => "", + * "fsize" => "", + * "putTime" => "" + * }, + * ... + * ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html + */ + public function listFiles($bucket, $prefix = null, $marker = null, $limit = 1000, $delimiter = null) + { + $query = array('bucket' => $bucket); + \Qiniu\setWithoutEmpty($query, 'prefix', $prefix); + \Qiniu\setWithoutEmpty($query, 'marker', $marker); + \Qiniu\setWithoutEmpty($query, 'limit', $limit); + \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter); + $url = $this->getRsfHost() . '/list?' . http_build_query($query); + return $this->get($url); + } + + /** + * 获取资源的元信息,但不返回文件内容 + * + * @param $bucket 待获取信息资源所在的空间 + * @param $key 待获取资源的文件名 + * + * @return array 包含文件信息的数组,类似: + * [ + * "hash" => "", + * "key" => "", + * "fsize" => , + * "putTime" => "" + * "fileType" => + * ] + * + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/stat.html + */ + public function stat($bucket, $key) + { + $path = '/stat/' . \Qiniu\entry($bucket, $key); + return $this->rsGet($path); + } + + /** + * 删除指定资源 + * + * @param $bucket 待删除资源所在的空间 + * @param $key 待删除资源的文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/delete.html + */ + public function delete($bucket, $key) + { + $path = '/delete/' . \Qiniu\entry($bucket, $key); + list(, $error) = $this->rsPost($path); + return $error; + } + + + /** + * 给资源进行重命名,本质为move操作。 + * + * @param $bucket 待操作资源所在空间 + * @param $oldname 待操作资源文件名 + * @param $newname 目标资源文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + */ + public function rename($bucket, $oldname, $newname) + { + return $this->move($bucket, $oldname, $bucket, $newname); + } + + /** + * 给资源进行重命名,本质为move操作。 + * + * @param $from_bucket 待操作资源所在空间 + * @param $from_key 待操作资源文件名 + * @param $to_bucket 目标资源空间名 + * @param $to_key 目标资源文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/copy.html + */ + public function copy($from_bucket, $from_key, $to_bucket, $to_key, $force = false) + { + $from = \Qiniu\entry($from_bucket, $from_key); + $to = \Qiniu\entry($to_bucket, $to_key); + $path = '/copy/' . $from . '/' . $to; + if ($force === true) { + $path .= '/force/true'; + } + list(, $error) = $this->rsPost($path); + return $error; + } + + /** + * 将资源从一个空间到另一个空间 + * + * @param $from_bucket 待操作资源所在空间 + * @param $from_key 待操作资源文件名 + * @param $to_bucket 目标资源空间名 + * @param $to_key 目标资源文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/move.html + */ + public function move($from_bucket, $from_key, $to_bucket, $to_key, $force = false) + { + $from = \Qiniu\entry($from_bucket, $from_key); + $to = \Qiniu\entry($to_bucket, $to_key); + $path = '/move/' . $from . '/' . $to; + if ($force) { + $path .= '/force/true'; + } + list(, $error) = $this->rsPost($path); + return $error; + } + + /** + * 主动修改指定资源的文件类型 + * + * @param $bucket 待操作资源所在空间 + * @param $key 待操作资源文件名 + * @param $mime 待操作文件目标mimeType + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/chgm.html + */ + public function changeMime($bucket, $key, $mime) + { + $resource = \Qiniu\entry($bucket, $key); + $encode_mime = \Qiniu\base64_urlSafeEncode($mime); + $path = '/chgm/' . $resource . '/mime/' . $encode_mime; + list(, $error) = $this->rsPost($path); + return $error; + } + + + /** + * 修改指定资源的存储类型 + * + * @param $bucket 待操作资源所在空间 + * @param $key 待操作资源文件名 + * @param $fileType 待操作文件目标文件类型 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/kodo/api/3710/modify-the-file-type + */ + public function changeType($bucket, $key, $fileType) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/chtype/' . $resource . '/type/' . $fileType; + list(, $error) = $this->rsPost($path); + return $error; + } + + /** + * 修改文件的存储状态,即禁用状态和启用状态间的的互相转换 + * + * @param $bucket 待操作资源所在空间 + * @param $key 待操作资源文件名 + * @param $status 待操作文件目标文件类型 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link https://developer.qiniu.com/kodo/api/4173/modify-the-file-status + */ + public function changeStatus($bucket, $key, $status) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/chstatus/' . $resource . '/status/' . $status; + list(, $error) = $this->rsPost($path); + return $error; + } + + /** + * 从指定URL抓取资源,并将该资源存储到指定空间中 + * + * @param $url 指定的URL + * @param $bucket 目标资源空间 + * @param $key 目标资源文件名 + * + * @return array 包含已拉取的文件信息。 + * 成功时: [ + * [ + * "hash" => "", + * "key" => "" + * ], + * null + * ] + * + * 失败时: [ + * null, + * Qiniu/Http/Error + * ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/fetch.html + */ + public function fetch($url, $bucket, $key = null) + { + + $resource = \Qiniu\base64_urlSafeEncode($url); + $to = \Qiniu\entry($bucket, $key); + $path = '/fetch/' . $resource . '/to/' . $to; + + $ak = $this->auth->getAccessKey(); + $ioHost = $this->config->getIovipHost($ak, $bucket); + + $url = $ioHost . $path; + return $this->post($url, null); + } + + /** + * 从镜像源站抓取资源到空间中,如果空间中已经存在,则覆盖该资源 + * + * @param $bucket 待获取资源所在的空间 + * @param $key 代获取资源文件名 + * + * @return mixed 成功返回NULL,失败返回对象Qiniu\Http\Error + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/prefetch.html + */ + public function prefetch($bucket, $key) + { + $resource = \Qiniu\entry($bucket, $key); + $path = '/prefetch/' . $resource; + + $ak = $this->auth->getAccessKey(); + $ioHost = $this->config->getIovipHost($ak, $bucket); + + $url = $ioHost . $path; + list(, $error) = $this->post($url, null); + return $error; + } + + /** + * 在单次请求中进行多个资源管理操作 + * + * @param $operations 资源管理操作数组 + * + * @return array 每个资源的处理情况,结果类似: + * [ + * { "code" => , "data" => }, + * { "code" => }, + * { "code" => }, + * { "code" => }, + * { "code" => , "data" => { "error": "" } }, + * ... + * ] + * @link http://developer.qiniu.com/docs/v6/api/reference/rs/batch.html + */ + public function batch($operations) + { + $params = 'op=' . implode('&op=', $operations); + return $this->rsPost('/batch', $params); + } + + /** + * 设置文件的生命周期 + * + * @param $bucket 设置文件生命周期文件所在的空间 + * @param $key 设置文件生命周期文件的文件名 + * @param $days 设置该文件多少天后删除,当$days设置为0时表示取消该文件的生命周期 + * + * @return Mixed + * @link https://developer.qiniu.com/kodo/api/update-file-lifecycle + */ + public function deleteAfterDays($bucket, $key, $days) + { + $entry = \Qiniu\entry($bucket, $key); + $path = "/deleteAfterDays/$entry/$days"; + list(, $error) = $this->rsPost($path); + return $error; + } + + private function getRsfHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::RSF_HOST; + } + + private function getRsHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::RS_HOST; + } + + private function getApiHost() + { + $scheme = "http://"; + if ($this->config->useHTTPS == true) { + $scheme = "https://"; + } + return $scheme . Config::API_HOST; + } + + private function rsPost($path, $body = null) + { + $url = $this->getRsHost() . $path; + return $this->post($url, $body); + } + + private function apiGet($path) + { + $url = $this->getApiHost() . $path; + return $this->get($url); + } + + private function rsGet($path) + { + $url = $this->getRsHost() . $path; + return $this->get($url); + } + + private function get($url) + { + $headers = $this->auth->authorization($url); + $ret = Client::get($url, $headers); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + return array($ret->json(), null); + } + + private function post($url, $body) + { + $headers = $this->auth->authorization($url, $body, 'application/x-www-form-urlencoded'); + $ret = Client::post($url, $body, $headers); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + return array($r, null); + } + + public static function buildBatchCopy($source_bucket, $key_pairs, $target_bucket, $force) + { + return self::twoKeyBatch('/copy', $source_bucket, $key_pairs, $target_bucket, $force); + } + + + public static function buildBatchRename($bucket, $key_pairs, $force) + { + return self::buildBatchMove($bucket, $key_pairs, $bucket, $force); + } + + + public static function buildBatchMove($source_bucket, $key_pairs, $target_bucket, $force) + { + return self::twoKeyBatch('/move', $source_bucket, $key_pairs, $target_bucket, $force); + } + + + public static function buildBatchDelete($bucket, $keys) + { + return self::oneKeyBatch('/delete', $bucket, $keys); + } + + + public static function buildBatchStat($bucket, $keys) + { + return self::oneKeyBatch('/stat', $bucket, $keys); + } + + public static function buildBatchDeleteAfterDays($bucket, $key_day_pairs) + { + $data = array(); + foreach ($key_day_pairs as $key => $day) { + array_push($data, '/deleteAfterDays/' . \Qiniu\entry($bucket, $key) . '/' . $day); + } + return $data; + } + + public static function buildBatchChangeMime($bucket, $key_mime_pairs) + { + $data = array(); + foreach ($key_mime_pairs as $key => $mime) { + array_push($data, '/chgm/' . \Qiniu\entry($bucket, $key) . '/mime/' . base64_encode($mime)); + } + return $data; + } + + public static function buildBatchChangeType($bucket, $key_type_pairs) + { + $data = array(); + foreach ($key_type_pairs as $key => $type) { + array_push($data, '/chtype/' . \Qiniu\entry($bucket, $key) . '/type/' . $type); + } + return $data; + } + + private static function oneKeyBatch($operation, $bucket, $keys) + { + $data = array(); + foreach ($keys as $key) { + array_push($data, $operation . '/' . \Qiniu\entry($bucket, $key)); + } + return $data; + } + + private static function twoKeyBatch($operation, $source_bucket, $key_pairs, $target_bucket, $force) + { + if ($target_bucket === null) { + $target_bucket = $source_bucket; + } + $data = array(); + $forceOp = "false"; + if ($force) { + $forceOp = "true"; + } + foreach ($key_pairs as $from_key => $to_key) { + $from = \Qiniu\entry($source_bucket, $from_key); + $to = \Qiniu\entry($target_bucket, $to_key); + array_push($data, $operation . '/' . $from . '/' . $to . "/force/" . $forceOp); + } + return $data; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php new file mode 100644 index 0000000..5c3361f --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/FormUploader.php @@ -0,0 +1,139 @@ + "", + * "key" => "" + * ] + */ + public static function put( + $upToken, + $key, + $data, + $config, + $params, + $mime, + $fname + ) { + + $fields = array('token' => $upToken); + if ($key === null) { + $fname='nullkey'; + } else { + $fields['key'] = $key; + } + + //enable crc32 check by default + $fields['crc32'] = \Qiniu\crc32_data($data); + + if ($params) { + foreach ($params as $k => $v) { + $fields[$k] = $v; + } + } + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + $upHost = $config->getUpHost($accessKey, $bucket); + + $response = Client::multipartPost($upHost, $fields, 'file', $fname, $data, $mime); + if (!$response->ok()) { + return array(null, new Error($upHost, $response)); + } + return array($response->json(), null); + } + + /** + * 上传文件到七牛,内部使用 + * + * @param $upToken 上传凭证 + * @param $key 上传文件名 + * @param $filePath 上传文件的路径 + * @param $config 上传配置 + * @param $params 自定义变量,规格参考 + * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param $mime 上传数据的mimeType + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + */ + public static function putFile( + $upToken, + $key, + $filePath, + $config, + $params, + $mime + ) { + + + $fields = array('token' => $upToken, 'file' => self::createFile($filePath, $mime)); + if ($key !== null) { + $fields['key'] = $key; + } + + $fields['crc32'] = \Qiniu\crc32_file($filePath); + + if ($params) { + foreach ($params as $k => $v) { + $fields[$k] = $v; + } + } + $fields['key'] = $key; + $headers = array('Content-Type' => 'multipart/form-data'); + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + $upHost = $config->getUpHost($accessKey, $bucket); + + $response = Client::post($upHost, $fields, $headers); + if (!$response->ok()) { + return array(null, new Error($upHost, $response)); + } + return array($response->json(), null); + } + + private static function createFile($filename, $mime) + { + // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax + // See: https://wiki.php.net/rfc/curl-file-upload + if (function_exists('curl_file_create')) { + return curl_file_create($filename, $mime); + } + + // Use the old style if using an older version of PHP + $value = "@{$filename}"; + if (!empty($mime)) { + $value .= ';type=' . $mime; + } + + return $value; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php new file mode 100644 index 0000000..5d8bf87 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/ResumeUploader.php @@ -0,0 +1,169 @@ +upToken = $upToken; + $this->key = $key; + $this->inputStream = $inputStream; + $this->size = $size; + $this->params = $params; + $this->mime = $mime; + $this->contexts = array(); + $this->config = $config; + + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken); + if ($err != null) { + return array(null, $err); + } + + $upHost = $config->getUpHost($accessKey, $bucket); + if ($err != null) { + throw new \Exception($err->message(), 1); + } + $this->host = $upHost; + } + + /** + * 上传操作 + */ + public function upload($fname) + { + $uploaded = 0; + while ($uploaded < $this->size) { + $blockSize = $this->blockSize($uploaded); + $data = fread($this->inputStream, $blockSize); + if ($data === false) { + throw new \Exception("file read failed", 1); + } + $crc = \Qiniu\crc32_data($data); + $response = $this->makeBlock($data, $blockSize); + $ret = null; + if ($response->ok() && $response->json() != null) { + $ret = $response->json(); + } + if ($response->statusCode < 0) { + list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken); + if ($err != null) { + return array(null, $err); + } + + $upHostBackup = $this->config->getUpBackupHost($accessKey, $bucket); + $this->host = $upHostBackup; + } + if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + $response = $this->makeBlock($data, $blockSize); + $ret = $response->json(); + } + + if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) { + return array(null, new Error($this->currentUrl, $response)); + } + array_push($this->contexts, $ret['ctx']); + $uploaded += $blockSize; + } + return $this->makeFile($fname); + } + + /** + * 创建块 + */ + private function makeBlock($block, $blockSize) + { + $url = $this->host . '/mkblk/' . $blockSize; + return $this->post($url, $block); + } + + private function fileUrl($fname) + { + $url = $this->host . '/mkfile/' . $this->size; + $url .= '/mimeType/' . \Qiniu\base64_urlSafeEncode($this->mime); + if ($this->key != null) { + $url .= '/key/' . \Qiniu\base64_urlSafeEncode($this->key); + } + $url .= '/fname/' . \Qiniu\base64_urlSafeEncode($fname); + if (!empty($this->params)) { + foreach ($this->params as $key => $value) { + $val = \Qiniu\base64_urlSafeEncode($value); + $url .= "/$key/$val"; + } + } + return $url; + } + + /** + * 创建文件 + */ + private function makeFile($fname) + { + $url = $this->fileUrl($fname); + $body = implode(',', $this->contexts); + $response = $this->post($url, $body); + if ($response->needRetry()) { + $response = $this->post($url, $body); + } + if (!$response->ok()) { + return array(null, new Error($this->currentUrl, $response)); + } + return array($response->json(), null); + } + + private function post($url, $data) + { + $this->currentUrl = $url; + $headers = array('Authorization' => 'UpToken ' . $this->upToken); + return Client::post($url, $data, $headers); + } + + private function blockSize($uploaded) + { + if ($this->size < $uploaded + Config::BLOCK_SIZE) { + return $this->size - $uploaded; + } + return Config::BLOCK_SIZE; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php new file mode 100644 index 0000000..209df11 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Storage/UploadManager.php @@ -0,0 +1,144 @@ +config = $config; + } + + /** + * 上传二进制流到七牛 + * + * @param $upToken 上传凭证 + * @param $key 上传文件名 + * @param $data 上传二进制流 + * @param $params 自定义变量,规格参考 + * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param $mime 上传数据的mimeType + * @param $checkCrc 是否校验crc32 + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + */ + public function put( + $upToken, + $key, + $data, + $params = null, + $mime = 'application/octet-stream', + $fname = null + ) { + + $params = self::trimParams($params); + return FormUploader::put( + $upToken, + $key, + $data, + $this->config, + $params, + $mime, + $fname + ); + } + + + /** + * 上传文件到七牛 + * + * @param $upToken 上传凭证 + * @param $key 上传文件名 + * @param $filePath 上传文件的路径 + * @param $params 自定义变量,规格参考 + * http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar + * @param $mime 上传数据的mimeType + * @param $checkCrc 是否校验crc32 + * + * @return array 包含已上传文件的信息,类似: + * [ + * "hash" => "", + * "key" => "" + * ] + */ + public function putFile( + $upToken, + $key, + $filePath, + $params = null, + $mime = 'application/octet-stream', + $checkCrc = false + ) { + + $file = fopen($filePath, 'rb'); + if ($file === false) { + throw new \Exception("file can not open", 1); + } + $params = self::trimParams($params); + $stat = fstat($file); + $size = $stat['size']; + if ($size <= Config::BLOCK_SIZE) { + $data = fread($file, $size); + fclose($file); + if ($data === false) { + throw new \Exception("file can not read", 1); + } + return FormUploader::put( + $upToken, + $key, + $data, + $this->config, + $params, + $mime, + basename($filePath) + ); + } + + $up = new ResumeUploader( + $upToken, + $key, + $file, + $size, + $params, + $mime, + $this->config + ); + $ret = $up->upload(basename($filePath)); + fclose($file); + return $ret; + } + + public static function trimParams($params) + { + if ($params === null) { + return null; + } + $ret = array(); + foreach ($params as $k => $v) { + $pos1 = strpos($k, 'x:'); + $pos2 = strpos($k, 'x-qn-meta-'); + if (($pos1 === 0 || $pos2 === 0) && !empty($v)) { + $ret[$k] = $v; + } + } + return $ret; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/Zone.php b/source/vendor/qiniu/php-sdk/src/Qiniu/Zone.php new file mode 100644 index 0000000..9b142dc --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/Zone.php @@ -0,0 +1,197 @@ +srcUpHosts = $srcUpHosts; + $this->cdnUpHosts = $cdnUpHosts; + $this->rsHost = $rsHost; + $this->rsfHost = $rsfHost; + $this->apiHost = $apiHost; + $this->iovipHost = $iovipHost; + } + + //华东机房 + public static function zone0() + { + $Zone_z0 = new Zone( + array("up.qiniup.com", 'up-jjh.qiniup.com', 'up-xs.qiniup.com'), + array('upload.qiniup.com', 'upload-jjh.qiniup.com', 'upload-xs.qiniup.com'), + 'rs.qbox.me', + 'rsf.qbox.me', + 'api.qiniu.com', + 'iovip.qbox.me' + ); + return $Zone_z0; + } + + //华东机房内网上传 + public static function zoneZ0() + { + $Zone_z01 = new Zone( + array("free-qvm-z0-xs.qiniup.com"), + 'rs.qbox.me', + 'rsf.qbox.me', + 'api.qiniu.com', + 'iovip.qbox.me' + ); + return $Zone_z01; + } + + //华北机房内网上传 + public static function zoneZ1() + { + $Zone_z12 = new Zone( + array("free-qvm-z1-zz.qiniup.com"), + "rs-z1.qbox.me", + "rsf-z1.qbox.me", + "api-z1.qiniu.com", + "iovip-z1.qbox.me" + ); + return $Zone_z12; + } + + //华北机房 + public static function zone1() + { + $Zone_z1 = new Zone( + array('up-z1.qiniup.com'), + array('upload-z1.qiniup.com'), + "rs-z1.qbox.me", + "rsf-z1.qbox.me", + "api-z1.qiniu.com", + "iovip-z1.qbox.me" + ); + + return $Zone_z1; + } + + //华南机房 + public static function zone2() + { + $Zone_z2 = new Zone( + array('up-z2.qiniup.com', 'up-dg.qiniup.com', 'up-fs.qiniup.com'), + array('upload-z2.qiniup.com', 'upload-dg.qiniup.com', 'upload-fs.qiniup.com'), + "rs-z2.qbox.me", + "rsf-z2.qbox.me", + "api-z2.qiniu.com", + "iovip-z2.qbox.me" + ); + return $Zone_z2; + } + + //北美机房 + public static function zoneNa0() + { + //北美机房 + $Zone_na0 = new Zone( + array('up-na0.qiniup.com'), + array('upload-na0.qiniup.com'), + "rs-na0.qbox.me", + "rsf-na0.qbox.me", + "api-na0.qiniu.com", + "iovip-na0.qbox.me" + ); + return $Zone_na0; + } + + //新加坡机房 + public static function zoneAs0() + { + //新加坡机房 + $Zone_as0 = new Zone( + array('up-as0.qiniup.com'), + array('upload-as0.qiniup.com'), + "rs-as0.qbox.me", + "rsf-as0.qbox.me", + "api-as0.qiniu.com", + "iovip-as0.qbox.me" + ); + return $Zone_as0; + } + + /* + * GET /v2/query?ak=&&bucket= + **/ + public static function queryZone($ak, $bucket) + { + $zone = new Zone(); + $url = Config::UC_HOST . '/v2/query' . "?ak=$ak&bucket=$bucket"; + $ret = Client::Get($url); + if (!$ret->ok()) { + return array(null, new Error($url, $ret)); + } + $r = ($ret->body === null) ? array() : $ret->json(); + //print_r($ret); + //parse zone; + + $iovipHost = $r['io']['src']['main'][0]; + $zone->iovipHost = $iovipHost; + $accMain = $r['up']['acc']['main'][0]; + array_push($zone->cdnUpHosts, $accMain); + if (isset($r['up']['acc']['backup'])) { + foreach ($r['up']['acc']['backup'] as $key => $value) { + array_push($zone->cdnUpHosts, $value); + } + } + $srcMain = $r['up']['src']['main'][0]; + array_push($zone->srcUpHosts, $srcMain); + if (isset($r['up']['src']['backup'])) { + foreach ($r['up']['src']['backup'] as $key => $value) { + array_push($zone->srcUpHosts, $value); + } + } + + //set specific hosts + if (strstr($zone->iovipHost, "z1") !== false) { + $zone->rsHost = "rs-z1.qbox.me"; + $zone->rsfHost = "rsf-z1.qbox.me"; + $zone->apiHost = "api-z1.qiniu.com"; + } elseif (strstr($zone->iovipHost, "z2") !== false) { + $zone->rsHost = "rs-z2.qbox.me"; + $zone->rsfHost = "rsf-z2.qbox.me"; + $zone->apiHost = "api-z2.qiniu.com"; + } elseif (strstr($zone->iovipHost, "na0") !== false) { + $zone->rsHost = "rs-na0.qbox.me"; + $zone->rsfHost = "rsf-na0.qbox.me"; + $zone->apiHost = "api-na0.qiniu.com"; + } elseif (strstr($zone->iovipHost, "as0") !== false) { + $zone->rsHost = "rs-as0.qbox.me"; + $zone->rsfHost = "rsf-as0.qbox.me"; + $zone->apiHost = "api-as0.qiniu.com"; + } else { + $zone->rsHost = "rs.qbox.me"; + $zone->rsfHost = "rsf.qbox.me"; + $zone->apiHost = "api.qiniu.com"; + } + + return $zone; + } +} diff --git a/source/vendor/qiniu/php-sdk/src/Qiniu/functions.php b/source/vendor/qiniu/php-sdk/src/Qiniu/functions.php new file mode 100644 index 0000000..5831a51 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/src/Qiniu/functions.php @@ -0,0 +1,264 @@ + 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ); + + if (empty($json)) { + return null; + } + $data = \json_decode($json, $assoc, $depth); + + if (JSON_ERROR_NONE !== json_last_error()) { + $last = json_last_error(); + throw new \InvalidArgumentException( + 'Unable to parse JSON data: ' + . (isset($jsonErrors[$last]) + ? $jsonErrors[$last] + : 'Unknown error') + ); + } + + return $data; + } + + /** + * 计算七牛API中的数据格式 + * + * @param $bucket 待操作的空间名 + * @param $key 待操作的文件名 + * + * @return string 符合七牛API规格的数据格式 + * @link http://developer.qiniu.com/docs/v6/api/reference/data-formats.html + */ + function entry($bucket, $key) + { + $en = $bucket; + if (!empty($key)) { + $en = $bucket . ':' . $key; + } + return base64_urlSafeEncode($en); + } + + /** + * array 辅助方法,无值时不set + * + * @param $array 待操作array + * @param $key key + * @param $value value 为null时 不设置 + * + * @return array 原来的array,便于连续操作 + */ + function setWithoutEmpty(&$array, $key, $value) + { + if (!empty($value)) { + $array[$key] = $value; + } + return $array; + } + + /** + * 缩略图链接拼接 + * + * @param string $url 图片链接 + * @param int $mode 缩略模式 + * @param int $width 宽度 + * @param int $height 长度 + * @param string $format 输出类型 + * @param int $quality 图片质量 + * @param int $interlace 是否支持渐进显示 + * @param int $ignoreError 忽略结果 + * @return string + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html + * @author Sherlock Ren + */ + function thumbnail( + $url, + $mode, + $width, + $height, + $format = null, + $quality = null, + $interlace = null, + $ignoreError = 1 + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'thumbnail'), func_get_args()); + } + + /** + * 图片水印 + * + * @param string $url 图片链接 + * @param string $image 水印图片链接 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @param numeric $watermarkScale 自适应原图的短边比例 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html + * @return string + * @author Sherlock Ren + */ + function waterImg( + $url, + $image, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null, + $watermarkScale = null + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'waterImg'), func_get_args()); + } + + /** + * 文字水印 + * + * @param string $url 图片链接 + * @param string $text 文字 + * @param string $font 文字字体 + * @param string $fontSize 文字字号 + * @param string $fontColor 文字颜色 + * @param numeric $dissolve 透明度 + * @param string $gravity 水印位置 + * @param numeric $dx 横轴边距 + * @param numeric $dy 纵轴边距 + * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark + * @return string + * @author Sherlock Ren + */ + function waterText( + $url, + $text, + $font = '黑体', + $fontSize = 0, + $fontColor = null, + $dissolve = 100, + $gravity = 'SouthEast', + $dx = null, + $dy = null + ) { + + static $imageUrlBuilder = null; + if (is_null($imageUrlBuilder)) { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder; + } + + return call_user_func_array(array($imageUrlBuilder, 'waterText'), func_get_args()); + } + + /** + * 从uptoken解析accessKey和bucket + * + * @param $upToken + * @return array(ak,bucket,err=null) + */ + function explodeUpToken($upToken) + { + $items = explode(':', $upToken); + if (count($items) != 3) { + return array(null, null, "invalid uptoken"); + } + $accessKey = $items[0]; + $putPolicy = json_decode(base64_urlSafeDecode($items[2])); + $scope = $putPolicy->scope; + $scopeItems = explode(':', $scope); + $bucket = $scopeItems[0]; + return array($accessKey, $bucket, null); + } +} diff --git a/source/vendor/qiniu/php-sdk/test-env.sh b/source/vendor/qiniu/php-sdk/test-env.sh new file mode 100644 index 0000000..eedf6b5 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/test-env.sh @@ -0,0 +1,4 @@ +export QINIU_ACCESS_KEY=xxx +export QINIU_SECRET_KEY=xxx +export QINIU_TEST_BUCKET=phpsdk +export QINIU_TEST_DOMAIN=phpsdk.qiniudn.com \ No newline at end of file diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php new file mode 100644 index 0000000..712cbd5 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/AuthTest.php @@ -0,0 +1,71 @@ +sign('test'); + $this->assertEquals('abcdefghklmnopq:mSNBTR7uS2crJsyFr2Amwv1LaYg=', $token); + } + + public function testSignWithData() + { + global $dummyAuth; + $token = $dummyAuth->signWithData('test'); + $this->assertEquals('abcdefghklmnopq:-jP8eEV9v48MkYiBGs81aDxl60E=:dGVzdA==', $token); + } + + public function testSignRequest() + { + global $dummyAuth; + $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', ''); + $this->assertEquals('abcdefghklmnopq:cFyRVoWrE3IugPIMP5YJFTO-O-Y=', $token); + $ctype = 'application/x-www-form-urlencoded'; + $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', $ctype); + $this->assertEquals($token, 'abcdefghklmnopq:svWRNcacOE-YMsc70nuIYdaa1e4='); + } + + public function testPrivateDownloadUrl() + { + global $dummyAuth; + $_SERVER['override_qiniu_auth_time'] = true; + $url = $dummyAuth->privateDownloadUrl('http://www.qiniu.com?go=1'); + $expect = 'http://www.qiniu.com?go=1&e=1234571490&token=abcdefghklmnopq:8vzBeLZ9W3E4kbBLFLW0Xe0u7v4='; + $this->assertEquals($expect, $url); + unset($_SERVER['override_qiniu_auth_time']); + } + + public function testUploadToken() + { + global $dummyAuth; + $_SERVER['override_qiniu_auth_time'] = true; + $token = $dummyAuth->uploadToken('1', '2', 3600, array('endUser' => 'y')); + // @codingStandardsIgnoreStart + $exp = 'abcdefghklmnopq:yyeexeUkPOROoTGvwBjJ0F0VLEo=:eyJlbmRVc2VyIjoieSIsInNjb3BlIjoiMToyIiwiZGVhZGxpbmUiOjEyMzQ1NzE0OTB9'; + // @codingStandardsIgnoreEnd + $this->assertEquals($exp, $token); + unset($_SERVER['override_qiniu_auth_time']); + } + + public function testVerifyCallback() + { + } + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php new file mode 100644 index 0000000..6d63353 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Base64Test.php @@ -0,0 +1,14 @@ +assertEquals($a, \Qiniu\base64_urlSafeDecode($b)); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php new file mode 100644 index 0000000..534d8c2 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/BucketTest.php @@ -0,0 +1,227 @@ +bucketName = $bucketName; + $this->key = $key; + $this->key2 = $key2; + + global $testAuth; + $this->bucketManager = new BucketManager($testAuth); + + global $dummyAuth; + $this->dummyBucketManager = new BucketManager($dummyAuth); + } + + public function testBuckets() + { + + list($list, $error) = $this->bucketManager->buckets(); + $this->assertTrue(in_array($this->bucketName, $list)); + $this->assertNull($error); + + list($list2, $error) = $this->dummyBucketManager->buckets(); + $this->assertEquals(401, $error->code()); + $this->assertNull($list2); + $this->assertNotNull($error->message()); + } + + public function testList() + { + list($ret, $error) = $this->bucketManager->listFiles($this->bucketName, null, null, 10); + $this->assertNotNull($ret['items'][0]); + $this->assertNotNull($ret['marker']); + } + + public function testStat() + { + list($stat, $error) = $this->bucketManager->stat($this->bucketName, $this->key); + $this->assertNotNull($stat); + $this->assertNull($error); + $this->assertNotNull($stat['hash']); + + list($stat, $error) = $this->bucketManager->stat($this->bucketName, 'nofile'); + $this->assertNull($stat); + $this->assertEquals(612, $error->code()); + $this->assertNotNull($error->message()); + + list($stat, $error) = $this->bucketManager->stat('nobucket', 'nofile'); + $this->assertNull($stat); + $this->assertEquals(631, $error->code()); + $this->assertNotNull($error->message()); + } + + public function testDelete() + { + $error = $this->bucketManager->delete($this->bucketName, 'del'); + $this->assertEquals(612, $error->code()); + } + + + public function testRename() + { + $key = 'renamefrom' . rand(); + $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key); + $key2 = 'renameto' . $key; + $error = $this->bucketManager->rename($this->bucketName, $key, $key2); + $this->assertNull($error); + $error = $this->bucketManager->delete($this->bucketName, $key2); + $this->assertNull($error); + } + + + public function testCopy() + { + $key = 'copyto' . rand(); + $this->bucketManager->delete($this->bucketName, $key); + + $error = $this->bucketManager->copy( + $this->bucketName, + $this->key, + $this->bucketName, + $key + ); + $this->assertNull($error); + + //test force copy + $error = $this->bucketManager->copy( + $this->bucketName, + $this->key2, + $this->bucketName, + $key, + true + ); + $this->assertNull($error); + + list($key2Stat,) = $this->bucketManager->stat($this->bucketName, $this->key2); + list($key2CopiedStat,) = $this->bucketManager->stat($this->bucketName, $key); + + $this->assertEquals($key2Stat['hash'], $key2CopiedStat['hash']); + + $error = $this->bucketManager->delete($this->bucketName, $key); + $this->assertNull($error); + } + + + public function testChangeMime() + { + $error = $this->bucketManager->changeMime( + $this->bucketName, + 'php-sdk.html', + 'text/html' + ); + $this->assertNull($error); + } + + public function testPrefetch() + { + $error = $this->bucketManager->prefetch( + $this->bucketName, + 'php-sdk.html' + ); + $this->assertNull($error); + } + + public function testFetch() + { + list($ret, $error) = $this->bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + $this->bucketName, + 'fetch.html' + ); + $this->assertArrayHasKey('hash', $ret); + $this->assertNull($error); + + list($ret, $error) = $this->bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + $this->bucketName, + '' + ); + $this->assertArrayHasKey('key', $ret); + $this->assertNull($error); + + list($ret, $error) = $this->bucketManager->fetch( + 'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html', + $this->bucketName + ); + $this->assertArrayHasKey('key', $ret); + $this->assertNull($error); + } + + public function testBatchCopy() + { + $key = 'copyto' . rand(); + $ops = BucketManager::buildBatchCopy( + $this->bucketName, + array($this->key => $key), + $this->bucketName, + true + ); + list($ret, $error) = $this->bucketManager->batch($ops); + $this->assertEquals(200, $ret[0]['code']); + $ops = BucketManager::buildBatchDelete($this->bucketName, array($key)); + list($ret, $error) = $this->bucketManager->batch($ops); + $this->assertEquals(200, $ret[0]['code']); + } + + public function testBatchMove() + { + $key = 'movefrom' . rand(); + $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key); + $key2 = $key . 'to'; + $ops = BucketManager::buildBatchMove( + $this->bucketName, + array($key => $key2), + $this->bucketName, + true + ); + list($ret, $error) = $this->bucketManager->batch($ops); + $this->assertEquals(200, $ret[0]['code']); + $error = $this->bucketManager->delete($this->bucketName, $key2); + $this->assertNull($error); + } + + public function testBatchRename() + { + $key = 'rename' . rand(); + $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key); + $key2 = $key . 'to'; + $ops = BucketManager::buildBatchRename($this->bucketName, array($key => $key2), true); + list($ret, $error) = $this->bucketManager->batch($ops); + $this->assertEquals(200, $ret[0]['code']); + $error = $this->bucketManager->delete($this->bucketName, $key2); + $this->assertNull($error); + } + + public function testBatchStat() + { + $ops = BucketManager::buildBatchStat($this->bucketName, array('php-sdk.html')); + list($ret, $error) = $this->bucketManager->batch($ops); + $this->assertEquals(200, $ret[0]['code']); + } + + public function testDeleteAfterDays() + { + $key = rand(); + $err = $this->bucketManager->deleteAfterDays($this->bucketName, $key, 1); + $this->assertEquals(612, $err->code()); + + $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key); + $err = $this->bucketManager->deleteAfterDays($this->bucketName, $key, 1); + $this->assertEquals(null, $err); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php new file mode 100644 index 0000000..5a858df --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/CdnManagerTest.php @@ -0,0 +1,50 @@ +cdnManager = new CdnManager($testAuth); + $this->encryptKey = $timestampAntiLeechEncryptKey; + $this->imgUrl = $customDomain . '/24.jpg'; + } + + public function testCreateTimestampAntiLeechUrl() + { + + $signUrl = $this->cdnManager->createTimestampAntiLeechUrl($this->imgUrl, $this->encryptKey, 3600); + + $response = Client::get($signUrl); + $this->assertEquals($response->statusCode, 200); + $this->assertNull($response->error); + + $url2 = $this->imgUrl . '?imageInfo'; + $signUrl2 = $this->cdnManager->createTimestampAntiLeechUrl($url2, $this->encryptKey, 3600); + + $response = Client::get($signUrl2); + $imgInfo = $response->json(); + + $this->assertEquals($response->statusCode, 200); + $this->assertEquals($imgInfo['size'], 2196145); + $this->assertNull($response->error); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php new file mode 100644 index 0000000..bfb36da --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/Crc32Test.php @@ -0,0 +1,21 @@ +assertEquals('1352841281', $b); + } + + public function testFile() + { + $b = \Qiniu\crc32_file(__file__); + $c = \Qiniu\crc32_file(__file__); + $this->assertEquals($c, $b); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php new file mode 100644 index 0000000..82990f2 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/DownloadTest.php @@ -0,0 +1,25 @@ +privateDownloadUrl($base_url); + $response = Client::get($private_url); + $this->assertEquals(200, $response->statusCode); + } + + public function testFop() + { + global $testAuth; + $base_url = 'http://private-res.qiniudn.com/gogopher.jpg?exif'; + $private_url = $testAuth->privateDownloadUrl($base_url); + $response = Client::get($private_url); + $this->assertEquals(200, $response->statusCode); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php new file mode 100644 index 0000000..42a1499 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/EtagTest.php @@ -0,0 +1,52 @@ +assertEquals('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', $r); + $this->assertNull($error); + } + + public function testLess4M() + { + $file = qiniuTempFile(3 * 1024 * 1024); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('Fs5BpnAjRykYTg6o5E09cjuXrDkG', $r); + $this->assertNull($error); + } + + public function test4M() + { + $file = qiniuTempFile(4 * 1024 * 1024); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('FiuKULnybewpEnrfTmxjsxc-3dWp', $r); + $this->assertNull($error); + } + + public function testMore4M() + { + $file = qiniuTempFile(5 * 1024 * 1024); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('lhvyfIWMYFTq4s4alzlhXoAkqfVL', $r); + $this->assertNull($error); + } + + public function test8M() + { + $file = qiniuTempFile(8 * 1024 * 1024); + list($r, $error) = Etag::sum($file); + unlink($file); + $this->assertEquals('lmRm9ZfGZ86bnMys4wRTWtJj9ClG', $r); + $this->assertNull($error); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php new file mode 100644 index 0000000..e1ea730 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FopTest.php @@ -0,0 +1,37 @@ +execute('gogopher.jpg', 'exif'); + $this->assertNull($error); + $this->assertNotNull($exif); + } + + public function testExifPrivate() + { + global $testAuth; + $fop = new Operation('private-res.qiniudn.com', $testAuth); + list($exif, $error) = $fop->execute('noexif.jpg', 'exif'); + $this->assertNotNull($error); + $this->assertNull($exif); + } + + public function testbuildUrl() + { + $fops = 'imageView2/2/h/200'; + $fop = new Operation('testres.qiniudn.com'); + $url = $fop->buildUrl('gogopher.jpg', $fops); + $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200'); + + $fops = array('imageView2/2/h/200', 'imageInfo'); + $url = $fop->buildUrl('gogopher.jpg', $fops); + $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200|imageInfo'); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php new file mode 100644 index 0000000..4813eed --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/FormUpTest.php @@ -0,0 +1,59 @@ +bucketName = $bucketName; + + global $testAuth; + $this->auth = $testAuth; + $this->cfg = new Config(); + } + + public function testData() + { + $token = $this->auth->uploadToken($this->bucketName); + list($ret, $error) = FormUploader::put($token, 'formput', 'hello world', $this->cfg, null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testData2() + { + $upManager = new UploadManager(); + $token = $this->auth->uploadToken($this->bucketName); + list($ret, $error) = $upManager->put($token, 'formput', 'hello world', null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFile() + { + $key = 'formPutFile'; + $token = $this->auth->uploadToken($this->bucketName, $key); + list($ret, $error) = FormUploader::putFile($token, $key, __file__, $this->cfg, null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } + + public function testFile2() + { + $key = 'formPutFile'; + $token = $this->auth->uploadToken($this->bucketName, $key); + $upManager = new UploadManager(); + list($ret, $error) = $upManager->putFile($token, $key, __file__, null, 'text/plain', null); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php new file mode 100644 index 0000000..e2ab5fc --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/HttpTest.php @@ -0,0 +1,43 @@ +assertEquals($response->statusCode, 200); + $this->assertNotNull($response->body); + $this->assertNull($response->error); + } + + public function testGetQiniu() + { + $response = Client::get('up.qiniu.com'); + $this->assertEquals(405, $response->statusCode); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } + + public function testPost() + { + $response = Client::post('baidu.com', null); + $this->assertEquals($response->statusCode, 200); + $this->assertNotNull($response->body); + $this->assertNull($response->error); + } + + public function testPostQiniu() + { + $response = Client::post('up.qiniu.com', null); + $this->assertEquals($response->statusCode, 400); + $this->assertNotNull($response->body); + $this->assertNotNull($response->xReqId()); + $this->assertNotNull($response->xLog()); + $this->assertNotNull($response->error); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php new file mode 100644 index 0000000..fca87b6 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ImageUrlBuilderTest.php @@ -0,0 +1,261 @@ + + */ +class ImageUrlBuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * 缩略图测试 + * + * @test + * @return void + * @author Sherlock Ren + */ + public function testThumbutl() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?watermark/1/gravity/SouthEast/dx/0/dy/0/image/' + . 'aHR0cDovL2Fkcy1jZG4uY2h1Y2h1amllLmNvbS9Ga1R6bnpIY2RLdmRBUFc5cHZZZ3pTc21UY0tB'; + // 异常测试 + $this->assertEquals($url, $imageUrlBuilder->thumbnail($url, 1, 0, 0)); + $this->assertEquals($url, \Qiniu\thumbnail($url, 1, 0, 0)); + + // 简单缩略测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200) + ); + + // 输出格式测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png') + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png') + ); + + // 渐进显示测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png', 2) + ); + + // 图片质量测试 + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/q/80/ignore-error/1/', + $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1, 80) + ); + $this->assertEquals( + $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/', + \Qiniu\thumbnail($url, 1, 200, 200, 'png', 1, 101) + ); + + // 多参数测试 + $this->assertEquals( + $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/', + $imageUrlBuilder->thumbnail($url2, 1, 200, 200) + ); + $this->assertEquals( + $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/', + \Qiniu\thumbnail($url2, 1, 200, 200) + ); + } + + /** + * 图片水印测试 + * + * @test + * @param void + * @return void + * @author Sherlock Ren + */ + public function waterImgTest() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/'; + $image = 'http://developer.qiniu.com/resource/logo-2.jpg'; + + // 水印简单测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url, $image) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url, $image, 101) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==/', + $imageUrlBuilder->waterImg($url, $image, 101, 'sdfsd') + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image) + ); + + // 横轴边距测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad') + ); + + // 纵轴边距测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf') + ); + + // 自适应原图的短边比例测试 + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/ws/0.5/', + $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10, 0.5) + ); + $this->assertEquals( + $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf', 2) + ); + + // 多参数测试 + $this->assertEquals( + $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterImg($url2, $image) + ); + $this->assertEquals( + $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterImg($url2, $image) + ); + } + + /** + * 文字水印测试 + * + * @test + * @param void + * @return void + * @author Sherlock Ren + */ + public function waterTextTest() + { + $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder(); + $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg'; + $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/'; + $text = '测试一下'; + $font = '微软雅黑'; + $fontColor = '#FF0000'; + + // 水印简单测试 + $this->assertEquals($url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', $imageUrlBuilder->waterText($url, $text, $font, 500)); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf') + ); + + // 字体颜色测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/' + . 'I0ZGMDAwMA==/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==' + . '/dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor) + ); + + // 透明度测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/SouthEast/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==' + . '/gravity/SouthEast/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101) + ); + + // 水印位置测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East') + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf') + ); + + // 横轴距离测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/dx/10/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs') + ); + + // 纵轴距离测试 + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA==' + . '/dissolve/80/gravity/East/dx/10/dy/10/', + $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10, 10) + ); + $this->assertEquals( + $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/', + \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs', 'ssdf') + ); + // 多参数测试 + $this->assertEquals( + $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', + $imageUrlBuilder->waterText($url2, $text, $font, 500) + ); + $this->assertEquals( + $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/' + . 'fontsize/500/dissolve/100/gravity/SouthEast/', + \Qiniu\waterText($url2, $text, $font, 500) + ); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php new file mode 100644 index 0000000..d03b3f6 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/PfopTest.php @@ -0,0 +1,66 @@ +execute($bucket, $key, $fops); + $this->assertNull($error); + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } + + + public function testPfops() + { + global $testAuth; + $bucket = 'testres'; + $key = 'sintel_trailer.mp4'; + $fops = array( + 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240', + 'vframe/jpg/offset/7/w/480/h/360', + ); + $pfop = new PersistentFop($testAuth, null); + + list($id, $error) = $pfop->execute($bucket, $key, $fops); + $this->assertNull($error); + + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } + + public function testMkzip() + { + global $testAuth; + $bucket = 'phpsdk'; + $key = 'php-logo.png'; + $pfop = new PersistentFop($testAuth, null); + + $url1 = 'http://phpsdk.qiniudn.com/php-logo.png'; + $url2 = 'http://phpsdk.qiniudn.com/php-sdk.html'; + $zipKey = 'test.zip'; + + $fops = 'mkzip/2/url/' . \Qiniu\base64_urlSafeEncode($url1); + $fops .= '/url/' . \Qiniu\base64_urlSafeEncode($url2); + $fops .= '|saveas/' . \Qiniu\base64_urlSafeEncode("$bucket:$zipKey"); + + list($id, $error) = $pfop->execute($bucket, $key, $fops); + $this->assertNull($error); + + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php new file mode 100644 index 0000000..00008d4 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ResumeUpTest.php @@ -0,0 +1,60 @@ +bucketName = $bucketName; + + global $testAuth; + $this->auth = $testAuth; + } + + public function test4ML() + { + $key = 'resumePutFile4ML'; + $upManager = new UploadManager(); + $token = $this->auth->uploadToken($this->bucketName, $key); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + list($ret, $error) = $upManager->putFile($token, $key, $tempFile); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + unlink($tempFile); + } + + public function test4ML2() + { + $key = 'resumePutFile4ML'; + $zone = new Zone(array('up.qiniup.com')); + $cfg = new Config($zone); + $upManager = new UploadManager($cfg); + $token = $this->auth->uploadToken($this->bucketName, $key); + $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10); + list($ret, $error) = $upManager->putFile($token, $key, $tempFile); + $this->assertNull($error); + $this->assertNotNull($ret['hash']); + unlink($tempFile); + } + + // public function test8M() + // { + // $key = 'resumePutFile8M'; + // $upManager = new UploadManager(); + // $token = $this->auth->uploadToken($this->bucketName, $key); + // $tempFile = qiniuTempFile(8*1024*1024+10); + // list($ret, $error) = $upManager->putFile($token, $key, $tempFile); + // $this->assertNull($error); + // $this->assertNotNull($ret['hash']); + // unlink($tempFile); + // } +} diff --git a/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php new file mode 100644 index 0000000..d32875b --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/Qiniu/Tests/ZoneTest.php @@ -0,0 +1,59 @@ +bucketName = $bucketName; + + global $bucketNameBC; + $this->bucketNameBC = $bucketNameBC; + + global $bucketNameNA; + $this->bucketNameNA = $bucketNameNA; + + global $accessKey; + $this->ak = $accessKey; + + $this->zone = new Zone(); + $this->zoneHttps = new Zone('https'); + } + + public function testUpHosts() + { + $zone = Zone::queryZone($this->ak, $this->bucketName); + $this->assertContains('upload.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameBC); + $this->assertContains('upload-z1.qiniup.com', $zone->cdnUpHosts); + + $zone = Zone::queryZone($this->ak, $this->bucketNameNA); + $this->assertContains('upload-na0.qiniup.com', $zone->cdnUpHosts); + } + + public function testIoHosts() + { + $zone = Zone::queryZone($this->ak, $this->bucketName); + $this->assertEquals($zone->iovipHost, 'iovip.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameBC); + $this->assertEquals($zone->iovipHost, 'iovip-z1.qbox.me'); + + $zone = Zone::queryZone($this->ak, $this->bucketNameNA); + $this->assertEquals($zone->iovipHost, 'iovip-na0.qbox.me'); + } +} diff --git a/source/vendor/qiniu/php-sdk/tests/bootstrap.php b/source/vendor/qiniu/php-sdk/tests/bootstrap.php new file mode 100644 index 0000000..5bd8b05 --- /dev/null +++ b/source/vendor/qiniu/php-sdk/tests/bootstrap.php @@ -0,0 +1,43 @@ + 0) { + fseek($file, $size - 1); + fwrite($file, ' '); + } + fclose($file); + return $fileName; +} diff --git a/source/vendor/symfony/event-dispatcher/.gitignore b/source/vendor/symfony/event-dispatcher/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/source/vendor/symfony/event-dispatcher/CHANGELOG.md b/source/vendor/symfony/event-dispatcher/CHANGELOG.md new file mode 100644 index 0000000..bb42ee1 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,23 @@ +CHANGELOG +========= + +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/source/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/source/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php new file mode 100644 index 0000000..4dcede7 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php @@ -0,0 +1,183 @@ + + * + * 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\Component\DependencyInjection\ContainerInterface; + +/** + * Lazily loads listeners and subscribers from the dependency injection + * container. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * @author Jordan Alliot + */ +class ContainerAwareEventDispatcher extends EventDispatcher +{ + private $container; + + /** + * The service IDs of the event listeners and subscribers. + */ + private $listenerIds = array(); + + /** + * The services registered as listeners. + */ + private $listeners = array(); + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Adds a service as event listener. + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param int $priority The higher this value, the earlier an event listener + * will be triggered in the chain. + * Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) + { + if (!\is_array($callback) || 2 !== \count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); + } + + public function removeListener($eventName, $listener) + { + $this->lazyLoad($eventName); + + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $i => $args) { + list($serviceId, $method) = $args; + $key = $serviceId.'.'.$method; + if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { + unset($this->listeners[$eventName][$key]); + if (empty($this->listeners[$eventName])) { + unset($this->listeners[$eventName]); + } + unset($this->listenerIds[$eventName][$i]); + if (empty($this->listenerIds[$eventName])) { + unset($this->listenerIds[$eventName]); + } + } + } + } + + parent::removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + if (null === $eventName) { + return $this->listenerIds || $this->listeners || parent::hasListeners(); + } + + if (isset($this->listenerIds[$eventName])) { + return true; + } + + return parent::hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null === $eventName) { + foreach ($this->listenerIds as $serviceEventName => $args) { + $this->lazyLoad($serviceEventName); + } + } else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + $this->lazyLoad($eventName); + + return parent::getListenerPriority($eventName, $listener); + } + + /** + * Adds a service as event subscriber. + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) + { + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (\is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } elseif (\is_string($params[0])) { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + public function getContainer() + { + return $this->container; + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) + { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $args) { + list($serviceId, $method, $priority) = $args; + $listener = $this->container->get($serviceId); + + $key = $serviceId.'.'.$method; + if (!isset($this->listeners[$eventName][$key])) { + $this->addListener($eventName, array($listener, $method), $priority); + } elseif ($this->listeners[$eventName][$key] !== $listener) { + parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); + $this->addListener($eventName, array($listener, $method), $priority); + } + + $this->listeners[$eventName][$key] = $listener; + } + } + } +} diff --git a/source/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/source/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000..53d7c5d --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.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\EventDispatcher\Debug; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements TraceableEventDispatcherInterface +{ + protected $logger; + protected $stopwatch; + + private $called; + private $dispatcher; + private $wrappedListeners; + + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->called = array(); + $this->wrappedListeners = array(); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($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($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + if (!method_exists($this->dispatcher, 'getListenerPriority')) { + return 0; + } + + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + if (null !== $this->logger && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + $this->preDispatch($eventName, $event); + + $e = $this->stopwatch->start($eventName, 'section'); + + $this->dispatcher->dispatch($eventName, $event); + + if ($e->isStarted()) { + $e->stop(); + } + + $this->postDispatch($eventName, $event); + $this->postProcess($eventName); + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getCalledListeners() + { + $called = array(); + foreach ($this->called as $eventName => $listeners) { + foreach ($listeners as $listener) { + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + $called[$eventName.'.'.$info['pretty']] = $info; + } + } + + return $called; + } + + /** + * {@inheritdoc} + */ + public function getNotCalledListeners() + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e)); + } + + // unable to retrieve the uncalled listeners + return array(); + } + + $notCalled = array(); + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called = false; + if (isset($this->called[$eventName])) { + foreach ($this->called[$eventName] as $l) { + if ($l->getWrappedListener() === $listener) { + $called = true; + + break; + } + } + } + + if (!$called) { + $info = $this->getListenerInfo($listener, $eventName); + $notCalled[$eventName.'.'.$info['pretty']] = $info; + } + } + } + + uasort($notCalled, array($this, 'sortListenersByPriority')); + + return $notCalled; + } + + /** + * 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($method, $arguments) + { + return \call_user_func_array(array($this->dispatcher, $method), $arguments); + } + + /** + * Called before dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * Called after dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function postDispatch($eventName, Event $event) + { + } + + private function preProcess($eventName) + { + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $info = $this->getListenerInfo($listener, $eventName); + $name = isset($info['class']) ? $info['class'] : $info['type']; + $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $info['priority']); + } + } + + private function postProcess($eventName) + { + 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); + + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty'])); + } + + if (!isset($this->called[$eventName])) { + $this->called[$eventName] = new \SplObjectStorage(); + } + + $this->called[$eventName]->attach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName)); + } + + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName)); + } + + $skipped = true; + } + } + } + + /** + * Returns information about the listener. + * + * @param object $listener The listener + * @param string $eventName The event name + * + * @return array Information about the listener + */ + private function getListenerInfo($listener, $eventName) + { + $info = array( + 'event' => $eventName, + 'priority' => $this->getListenerPriority($eventName, $listener), + ); + + // unwrap for correct listener info + if ($listener instanceof WrappedListener) { + $listener = $listener->getWrappedListener(); + } + + if ($listener instanceof \Closure) { + $info += array( + 'type' => 'Closure', + 'pretty' => 'closure', + ); + } elseif (\is_string($listener)) { + try { + $r = new \ReflectionFunction($listener); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Function', + 'function' => $listener, + 'file' => $file, + 'line' => $line, + 'pretty' => $listener, + ); + } elseif (\is_array($listener) || (\is_object($listener) && \is_callable($listener))) { + if (!\is_array($listener)) { + $listener = array($listener, '__invoke'); + } + $class = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0]; + try { + $r = new \ReflectionMethod($class, $listener[1]); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Method', + 'class' => $class, + 'method' => $listener[1], + 'file' => $file, + 'line' => $line, + 'pretty' => $class.'::'.$listener[1], + ); + } + + return $info; + } + + private function sortListenersByPriority($a, $b) + { + 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/source/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/source/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php new file mode 100644 index 0000000..5483e81 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.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\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +interface TraceableEventDispatcherInterface extends EventDispatcherInterface +{ + /** + * Gets the called listeners. + * + * @return array An array of called listeners + */ + public function getCalledListeners(); + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + */ + public function getNotCalledListeners(); +} diff --git a/source/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/source/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 0000000..1552af0 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Debug/WrappedListener.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\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Fabien Potencier + */ +class WrappedListener +{ + private $listener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + + public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->name = $name; + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled() + { + return $this->called; + } + + public function stoppedPropagation() + { + return $this->stoppedPropagation; + } + + public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + { + $this->called = true; + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + \call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/source/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/source/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 0000000..5a94ae8 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,100 @@ + + * + * 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; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + protected $dispatcherService; + protected $listenerTag; + protected $subscriberTag; + + /** + * @param string $dispatcherService Service name of the event dispatcher in processed container + * @param string $listenerTag Tag name used for listener + * @param string $subscriberTag Tag name used for subscribers + */ + public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $definition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id)); + } + + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback(array( + '/(?<=\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']); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id)); + } + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getParameterBag()->resolveValue($def->getClass()); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + + if (!is_subclass_of($class, $interface)) { + if (!class_exists($class, false)) { + throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/source/vendor/symfony/event-dispatcher/Event.php b/source/vendor/symfony/event-dispatcher/Event.php new file mode 100644 index 0000000..320919a --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Event.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\EventDispatcher; + +/** + * 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 + */ +class Event +{ + /** + * @var bool Whether no further event listeners should be triggered + */ + private $propagationStopped = false; + + /** + * @var EventDispatcherInterface Dispatcher that dispatched this event + */ + private $dispatcher; + + /** + * @var string This event's name + */ + private $name; + + /** + * Returns whether further event listeners should be triggered. + * + * @see Event::stopPropagation() + * + * @return bool Whether propagation was already stopped for this event + */ + public function isPropagationStopped() + { + 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() + { + $this->propagationStopped = true; + } + + /** + * Stores the EventDispatcher that dispatches this Event. + * + * @param EventDispatcherInterface $dispatcher + * + * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call. + */ + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Returns the EventDispatcher that dispatches this Event. + * + * @return EventDispatcherInterface + * + * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call. + */ + public function getDispatcher() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0. The event dispatcher instance can be received in the listener call instead.', E_USER_DEPRECATED); + + return $this->dispatcher; + } + + /** + * Gets the event's name. + * + * @return string + * + * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call. + */ + public function getName() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0. The event name can be received in the listener call instead.', E_USER_DEPRECATED); + + return $this->name; + } + + /** + * Sets the event's name property. + * + * @param string $name The event name + * + * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call. + */ + public function setName($name) + { + $this->name = $name; + } +} diff --git a/source/vendor/symfony/event-dispatcher/EventDispatcher.php b/source/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 0000000..b41b98e --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/EventDispatcher.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 Symfony\Component\EventDispatcher; + +/** + * 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 + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = array(); + private $sorted = array(); + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + $event->setDispatcher($this); + $event->setName($eventName); + + if ($listeners = $this->getListeners($eventName)) { + $this->doDispatch($listeners, $eventName, $event); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null !== $eventName) { + if (!isset($this->listeners[$eventName])) { + return array(); + } + + 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); + } + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @param string $eventName The name of the event + * @param callable $listener The listener + * + * @return int|null The event listener priority + */ + public function getListenerPriority($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== \in_array($listener, $listeners, true)) { + return $priority; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return (bool) $this->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (\is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $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, array($subscriber, $listener[0])); + } + } else { + $this->removeListener($eventName, array($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 Event $event The event object to pass to the event handlers/listeners + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + if ($event->isPropagationStopped()) { + break; + } + \call_user_func($listener, $event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + * + * @param string $eventName The name of the event + */ + private function sortListeners($eventName) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = \call_user_func_array('array_merge', $this->listeners[$eventName]); + } +} diff --git a/source/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/source/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 0000000..60160a9 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/EventDispatcherInterface.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\EventDispatcher; + +/** + * 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 +{ + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + * @param Event $event The event to pass to the event handlers/listeners + * If not supplied, an empty Event instance is created + * + * @return Event + */ + public function dispatch($eventName, Event $event = null); + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $listener The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function addListener($eventName, $listener, $priority = 0); + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + */ + public function addSubscriber(EventSubscriberInterface $subscriber); + + /** + * Removes an event listener from the specified events. + * + * @param string $eventName The event to remove a listener from + * @param callable $listener The listener to remove + */ + public function removeListener($eventName, $listener); + + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @param string $eventName The name of the event + * + * @return array The event listeners for the specified event, or all event listeners by event name + */ + public function getListeners($eventName = null); + + /** + * Checks whether an event has any registered listeners. + * + * @param string $eventName The name of the event + * + * @return bool true if the specified event has any listeners, false otherwise + */ + public function hasListeners($eventName = null); +} diff --git a/source/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/source/vendor/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 0000000..8af7789 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/EventSubscriberInterface.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; + +/** + * An EventSubscriber knows himself what events he 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: + * + * * array('eventName' => '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(); +} diff --git a/source/vendor/symfony/event-dispatcher/GenericEvent.php b/source/vendor/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 0000000..f0be7e1 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/GenericEvent.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\EventDispatcher; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + */ +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 = array()) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed The observer subject + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @param string $key Key + * + * @return mixed Contents of array key + * + * @throws \InvalidArgumentException if key is not found + */ + public function getArgument($key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @param string $key Argument name + * @param mixed $value Value + * + * @return $this + */ + public function setArgument($key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @param array $args Arguments + * + * @return $this + */ + public function setArguments(array $args = array()) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @param string $key Key of arguments array + * + * @return bool + */ + public function hasArgument($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 + */ + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set + * @param mixed $value Value + */ + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key + */ + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key + * + * @return bool + */ + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/source/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/source/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 0000000..b3cf56c --- /dev/null +++ b/source/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($eventName, Event $event = null) + { + return $this->dispatcher->dispatch($eventName, $event); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $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($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($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/source/vendor/symfony/event-dispatcher/LICENSE b/source/vendor/symfony/event-dispatcher/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 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/source/vendor/symfony/event-dispatcher/README.md b/source/vendor/symfony/event-dispatcher/README.md new file mode 100644 index 0000000..185c3fe --- /dev/null +++ b/source/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/index.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/source/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php b/source/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php new file mode 100644 index 0000000..48de632 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php @@ -0,0 +1,398 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +abstract class AbstractEventDispatcherTest extends TestCase +{ + /* Some pseudo events */ + const preFoo = 'pre.foo'; + const postFoo = 'post.foo'; + const preBar = 'pre.bar'; + const postBar = 'post.bar'; + + /** + * @var EventDispatcher + */ + private $dispatcher; + + private $listener; + + protected function setUp() + { + $this->dispatcher = $this->createEventDispatcher(); + $this->listener = new TestEventListener(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->listener = null; + } + + abstract protected function createEventDispatcher(); + + public function testInitialState() + { + $this->assertEquals(array(), $this->dispatcher->getListeners()); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddListener() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->assertTrue($this->dispatcher->hasListeners()); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo)); + $this->assertCount(2, $this->dispatcher->getListeners()); + } + + public function testGetListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener1->name = '1'; + $listener2->name = '2'; + $listener3->name = '3'; + + $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10); + $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10); + $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo')); + + $expected = array( + array($listener2, 'preFoo'), + array($listener3, 'preFoo'), + array($listener1, 'preFoo'), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo')); + } + + public function testGetAllListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener4 = new TestEventListener(); + $listener5 = new TestEventListener(); + $listener6 = new TestEventListener(); + + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->addListener('post.foo', $listener4, -10); + $this->dispatcher->addListener('post.foo', $listener5); + $this->dispatcher->addListener('post.foo', $listener6, 10); + + $expected = array( + 'pre.foo' => array($listener3, $listener2, $listener1), + 'post.foo' => array($listener6, $listener5, $listener4), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners()); + } + + public function testGetListenerPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + + $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1)); + $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2)); + $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2)); + $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {})); + } + + public function testDispatch() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->dispatcher->dispatch(self::preFoo); + $this->assertTrue($this->listener->preFooInvoked); + $this->assertFalse($this->listener->postFooInvoked); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent')); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo)); + $event = new Event(); + $return = $this->dispatcher->dispatch(self::preFoo, $event); + $this->assertSame($event, $return); + } + + /** + * @group legacy + */ + public function testLegacyDispatch() + { + $event = new Event(); + $this->dispatcher->dispatch(self::preFoo, $event); + $this->assertEquals('pre.foo', $event->getName()); + } + + public function testDispatchForClosure() + { + $invoked = 0; + $listener = function () use (&$invoked) { + ++$invoked; + }; + $this->dispatcher->addListener('pre.foo', $listener); + $this->dispatcher->addListener('post.foo', $listener); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(1, $invoked); + } + + public function testStopEventPropagation() + { + $otherListener = new TestEventListener(); + + // postFoo() stops the propagation, so only one listener should + // be executed + // Manually set priority to enforce $this->listener to be called first + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10); + $this->dispatcher->addListener('post.foo', array($otherListener, 'postFoo')); + $this->dispatcher->dispatch(self::postFoo); + $this->assertTrue($this->listener->postFooInvoked); + $this->assertFalse($otherListener->postFooInvoked); + } + + public function testDispatchByPriority() + { + $invoked = array(); + $listener1 = function () use (&$invoked) { + $invoked[] = '1'; + }; + $listener2 = function () use (&$invoked) { + $invoked[] = '2'; + }; + $listener3 = function () use (&$invoked) { + $invoked[] = '3'; + }; + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(array('3', '2', '1'), $invoked); + } + + public function testRemoveListener() + { + $this->dispatcher->addListener('pre.bar', $this->listener); + $this->assertTrue($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('pre.bar', $this->listener); + $this->assertFalse($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('notExists', $this->listener); + } + + public function testAddSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]); + } + + public function testAddSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertEquals('preFoo2', $listeners[0][1]); + } + + public function testRemoveSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testRemoveSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testRemoveSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + /** + * @group legacy + */ + public function testLegacyEventReceivesTheDispatcherInstance() + { + $dispatcher = null; + $this->dispatcher->addListener('test', function ($event) use (&$dispatcher) { + $dispatcher = $event->getDispatcher(); + }); + $this->dispatcher->dispatch('test'); + $this->assertSame($this->dispatcher, $dispatcher); + } + + public function testEventReceivesTheDispatcherInstanceAsArgument() + { + $listener = new TestWithDispatcher(); + $this->dispatcher->addListener('test', array($listener, 'foo')); + $this->assertNull($listener->name); + $this->assertNull($listener->dispatcher); + $this->dispatcher->dispatch('test'); + $this->assertEquals('test', $listener->name); + $this->assertSame($this->dispatcher, $listener->dispatcher); + } + + /** + * @see https://bugs.php.net/bug.php?id=62976 + * + * This bug affects: + * - The PHP 5.3 branch for versions < 5.3.18 + * - The PHP 5.4 branch for versions < 5.4.8 + * - The PHP 5.5 branch is not affected + */ + public function testWorkaroundForPhpBug62976() + { + $dispatcher = $this->createEventDispatcher(); + $dispatcher->addListener('bug.62976', new CallableClass()); + $dispatcher->removeListener('bug.62976', function () {}); + $this->assertTrue($dispatcher->hasListeners('bug.62976')); + } + + public function testHasListenersWhenAddedCallbackListenerIsRemoved() + { + $listener = function () {}; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertFalse($this->dispatcher->hasListeners()); + } + + public function testGetListenersWhenAddedCallbackListenerIsRemoved() + { + $listener = function () {}; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertSame(array(), $this->dispatcher->getListeners()); + } + + public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() + { + $this->assertFalse($this->dispatcher->hasListeners('foo')); + $this->assertFalse($this->dispatcher->hasListeners()); + } +} + +class CallableClass +{ + public function __invoke() + { + } +} + +class TestEventListener +{ + public $preFooInvoked = false; + public $postFooInvoked = false; + + /* Listener methods */ + + public function preFoo(Event $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(Event $e) + { + $this->postFooInvoked = true; + + $e->stopPropagation(); + } +} + +class TestWithDispatcher +{ + public $name; + public $dispatcher; + + public function foo(Event $e, $name, $dispatcher) + { + $this->name = $name; + $this->dispatcher = $dispatcher; + } +} + +class TestEventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo'); + } +} + +class TestEventSubscriberWithPriorities implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'pre.foo' => array('preFoo', 10), + 'post.foo' => array('postFoo'), + ); + } +} + +class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => array( + array('preFoo1'), + array('preFoo2', 10), + )); + } +} diff --git a/source/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php b/source/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php new file mode 100644 index 0000000..224a292 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Scope; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + $container = new Container(); + + return new ContainerAwareEventDispatcher($container); + } + + public function testAddAListenerService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testAddASubscriberService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\SubscriberService')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $service + ->expects($this->once()) + ->method('onEventWithPriority') + ->with($event) + ; + + $service + ->expects($this->once()) + ->method('onEventNested') + ->with($event) + ; + + $container = new Container(); + $container->set('service.subscriber', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $dispatcher->dispatch('onEvent', $event); + $dispatcher->dispatch('onEventWithPriority', $event); + $dispatcher->dispatch('onEventNested', $event); + } + + public function testPreventDuplicateListenerService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10); + + $dispatcher->dispatch('onEvent', $event); + } + + /** + * @expectedException \InvalidArgumentException + * @group legacy + */ + public function testTriggerAListenerServiceOutOfScope() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $scope = new Scope('scope'); + $container = new Container(); + $container->addScope($scope); + $container->enterScope('scope'); + + $container->set('service.listener', $service, 'scope'); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $container->leaveScope('scope'); + $dispatcher->dispatch('onEvent'); + } + + /** + * @group legacy + */ + public function testReEnteringAScope() + { + $event = new Event(); + + $service1 = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $service1 + ->expects($this->exactly(2)) + ->method('onEvent') + ->with($event) + ; + + $scope = new Scope('scope'); + $container = new Container(); + $container->addScope($scope); + $container->enterScope('scope'); + + $container->set('service.listener', $service1, 'scope'); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + $dispatcher->dispatch('onEvent', $event); + + $service2 = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $service2 + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container->enterScope('scope'); + $container->set('service.listener', $service2, 'scope'); + + $dispatcher->dispatch('onEvent', $event); + + $container->leaveScope('scope'); + + $dispatcher->dispatch('onEvent'); + } + + public function testHasListenersOnLazyLoad() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $event->setDispatcher($dispatcher); + $event->setName('onEvent'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $this->assertTrue($dispatcher->hasListeners()); + + if ($dispatcher->hasListeners('onEvent')) { + $dispatcher->dispatch('onEvent'); + } + } + + public function testGetListenersOnLazyLoad() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $listeners = $dispatcher->getListeners(); + + $this->assertArrayHasKey('onEvent', $listeners); + + $this->assertCount(1, $dispatcher->getListeners('onEvent')); + } + + public function testRemoveAfterDispatch() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', new Event()); + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } + + public function testRemoveBeforeDispatch() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } +} + +class Service +{ + public function onEvent(Event $e) + { + } +} + +class SubscriberService implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'onEvent' => 'onEvent', + 'onEventWithPriority' => array('onEventWithPriority', 10), + 'onEventNested' => array(array('onEventNested')), + ); + } + + public function onEvent(Event $e) + { + } + + public function onEventWithPriority(Event $e) + { + } + + public function onEventNested(Event $e) + { + } +} diff --git a/source/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/source/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 0000000..ffdda38 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\Debug\WrappedListener; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +class TraceableEventDispatcherTest extends TestCase +{ + public function testAddRemoveListener() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () {}); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame($listener, $listeners[0]); + + $tdispatcher->removeListener('foo', $listener); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () {}); + $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo')); + } + + public function testHasListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $this->assertFalse($dispatcher->hasListeners('foo')); + $this->assertFalse($tdispatcher->hasListeners('foo')); + + $tdispatcher->addListener('foo', $listener = function () {}); + $this->assertTrue($dispatcher->hasListeners('foo')); + $this->assertTrue($tdispatcher->hasListeners('foo')); + } + + public function testGetListenerPriority() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', function () {}, 123); + + $listeners = $dispatcher->getListeners('foo'); + $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0])); + + // Verify that priority is preserved when listener is removed and re-added + // in preProcess() and postProcess(). + $tdispatcher->dispatch('foo', new Event()); + $listeners = $dispatcher->getListeners('foo'); + $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0])); + } + + public function testGetListenerPriorityReturnsZeroWhenWrappedMethodDoesNotExist() + { + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $traceableEventDispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $traceableEventDispatcher->addListener('foo', function () {}, 123); + $listeners = $traceableEventDispatcher->getListeners('foo'); + + $this->assertSame(0, $traceableEventDispatcher->getListenerPriority('foo', $listeners[0])); + } + + public function testAddRemoveSubscriber() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $subscriber = new EventSubscriber(); + + $tdispatcher->addSubscriber($subscriber); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame(array($subscriber, 'call'), $listeners[0]); + + $tdispatcher->removeSubscriber($subscriber); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + /** + * @dataProvider isWrappedDataProvider + * + * @param bool $isWrapped + */ + public function testGetCalledListeners($isWrapped) + { + $dispatcher = new EventDispatcher(); + $stopWatch = new Stopwatch(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, $stopWatch); + + $listener = function () {}; + if ($isWrapped) { + $listener = new WrappedListener($listener, 'foo', $stopWatch, $dispatcher); + } + + $tdispatcher->addListener('foo', $listener, 5); + + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 5)), $tdispatcher->getNotCalledListeners()); + + $tdispatcher->dispatch('foo'); + + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 5)), $tdispatcher->getCalledListeners()); + $this->assertEquals(array(), $tdispatcher->getNotCalledListeners()); + } + + public function isWrappedDataProvider() + { + return array( + array(false), + array(true), + ); + } + + public function testGetCalledListenersNested() + { + $tdispatcher = null; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) { + $tdispatcher = $dispatcher; + $dispatcher->dispatch('bar'); + }); + $dispatcher->addListener('bar', function (Event $event) {}); + $dispatcher->dispatch('foo'); + $this->assertSame($dispatcher, $tdispatcher); + $this->assertCount(2, $dispatcher->getCalledListeners()); + } + + public function testLogger() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function () {}); + $tdispatcher->addListener('foo', $listener2 = function () {}); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".'); + $logger->expects($this->at(1))->method('debug')->with('Notified event "foo" to listener "closure".'); + + $tdispatcher->dispatch('foo'); + } + + public function testLoggerWithStoppedEvent() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); }); + $tdispatcher->addListener('foo', $listener2 = function () {}); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".'); + $logger->expects($this->at(1))->method('debug')->with('Listener "closure" stopped propagation of the event "foo".'); + $logger->expects($this->at(2))->method('debug')->with('Listener "closure" was not called for event "foo".'); + + $tdispatcher->dispatch('foo'); + } + + public function testDispatchCallListeners() + { + $called = array(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10); + $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20); + + $tdispatcher->dispatch('foo'); + + $this->assertSame(array('foo2', 'foo1'), $called); + } + + public function testDispatchNested() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $loop = 1; + $dispatchedEvents = 0; + $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) { + ++$loop; + if (2 == $loop) { + $dispatcher->dispatch('foo'); + } + }); + $dispatcher->addListener('foo', function () use (&$dispatchedEvents) { + ++$dispatchedEvents; + }); + + $dispatcher->dispatch('foo'); + + $this->assertSame(2, $dispatchedEvents); + } + + public function testDispatchReusedEventNested() + { + $nestedCall = false; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) { + $dispatcher->dispatch('bar', $e); + }); + $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) { + $nestedCall = true; + }); + + $this->assertFalse($nestedCall); + $dispatcher->dispatch('foo'); + $this->assertTrue($nestedCall); + } + + public function testListenerCanRemoveItselfWhenExecuted() + { + $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) { + $dispatcher->removeListener('foo', $listener1); + }; + $eventDispatcher->addListener('foo', $listener1); + $eventDispatcher->addListener('foo', function () {}); + $eventDispatcher->dispatch('foo'); + + $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); + } +} + +class EventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('foo' => 'call'); + } +} diff --git a/source/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/source/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php new file mode 100644 index 0000000..7490d0d --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; + +class RegisterListenersPassTest extends TestCase +{ + /** + * Tests that event subscribers not implementing EventSubscriberInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testEventSubscriberWithoutInterface() + { + $builder = new ContainerBuilder(); + $builder->register('event_dispatcher'); + $builder->register('my_event_subscriber', 'stdClass') + ->addTag('kernel.event_subscriber'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + public function testValidEventSubscriber() + { + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $builder = new ContainerBuilder(); + $eventDispatcherDefinition = $builder->register('event_dispatcher'); + $builder->register('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService') + ->addTag('kernel.event_subscriber'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + + $this->assertEquals(array(array('addSubscriberService', array('my_event_subscriber', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'))), $eventDispatcherDefinition->getMethodCalls()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must be public as event listeners are lazy-loaded. + */ + public function testPrivateEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must be public as event subscribers are lazy-loaded. + */ + public function testPrivateEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must not be abstract as event listeners are lazy-loaded. + */ + public function testAbstractEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must not be abstract as event subscribers are lazy-loaded. + */ + public function testAbstractEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + public function testEventSubscriberResolvableClassName() + { + $container = new ContainerBuilder(); + + $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expected_calls = array( + array( + 'addSubscriberService', + array( + 'foo', + 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService', + ), + ), + ); + $this->assertSame($expected_calls, $definition->getMethodCalls()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class" + */ + public function testEventSubscriberUnresolvableClassName() + { + $container = new ContainerBuilder(); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } +} + +class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + } +} diff --git a/source/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/source/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php new file mode 100644 index 0000000..5faa5c8 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\EventDispatcher; + +class EventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + return new EventDispatcher(); + } +} diff --git a/source/vendor/symfony/event-dispatcher/Tests/EventTest.php b/source/vendor/symfony/event-dispatcher/Tests/EventTest.php new file mode 100644 index 0000000..bdc14ab --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Tests/EventTest.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\EventDispatcher\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; + +/** + * Test class for Event. + */ +class EventTest extends TestCase +{ + /** + * @var \Symfony\Component\EventDispatcher\Event + */ + protected $event; + + /** + * @var \Symfony\Component\EventDispatcher\EventDispatcher + */ + protected $dispatcher; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->event = new Event(); + $this->dispatcher = new EventDispatcher(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + $this->event = null; + $this->dispatcher = null; + } + + public function testIsPropagationStopped() + { + $this->assertFalse($this->event->isPropagationStopped()); + } + + public function testStopPropagationAndIsPropagationStopped() + { + $this->event->stopPropagation(); + $this->assertTrue($this->event->isPropagationStopped()); + } + + /** + * @group legacy + */ + public function testLegacySetDispatcher() + { + $this->event->setDispatcher($this->dispatcher); + $this->assertSame($this->dispatcher, $this->event->getDispatcher()); + } + + /** + * @group legacy + */ + public function testLegacyGetDispatcher() + { + $this->assertNull($this->event->getDispatcher()); + } + + /** + * @group legacy + */ + public function testLegacyGetName() + { + $this->assertNull($this->event->getName()); + } + + /** + * @group legacy + */ + public function testLegacySetName() + { + $this->event->setName('foo'); + $this->assertEquals('foo', $this->event->getName()); + } +} diff --git a/source/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php b/source/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php new file mode 100644 index 0000000..b63f69d --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Tests/GenericEventTest.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\EventDispatcher\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * Test class for Event. + */ +class GenericEventTest extends TestCase +{ + /** + * @var GenericEvent + */ + private $event; + + private $subject; + + /** + * Prepares the environment before running a test. + */ + protected function setUp() + { + $this->subject = new \stdClass(); + $this->event = new GenericEvent($this->subject, array('name' => 'Event')); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->subject = null; + $this->event = null; + } + + public function testConstruct() + { + $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event'))); + } + + /** + * Tests Event->getArgs(). + */ + public function testGetArguments() + { + // test getting all + $this->assertSame(array('name' => 'Event'), $this->event->getArguments()); + } + + public function testSetArguments() + { + $result = $this->event->setArguments(array('foo' => 'bar')); + $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event); + $this->assertSame($this->event, $result); + } + + public function testSetArgument() + { + $result = $this->event->setArgument('foo2', 'bar2'); + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + $this->assertEquals($this->event, $result); + } + + public function testGetArgument() + { + // test getting key + $this->assertEquals('Event', $this->event->getArgument('name')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetArgException() + { + $this->event->getArgument('nameNotExist'); + } + + public function testOffsetGet() + { + // test getting key + $this->assertEquals('Event', $this->event['name']); + + // test getting invalid arg + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $this->assertFalse($this->event['nameNotExist']); + } + + public function testOffsetSet() + { + $this->event['foo2'] = 'bar2'; + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + } + + public function testOffsetUnset() + { + unset($this->event['name']); + $this->assertAttributeSame(array(), 'arguments', $this->event); + } + + public function testOffsetIsset() + { + $this->assertArrayHasKey('name', $this->event); + $this->assertArrayNotHasKey('nameNotExist', $this->event); + } + + public function testHasArgument() + { + $this->assertTrue($this->event->hasArgument('name')); + $this->assertFalse($this->event->hasArgument('nameNotExist')); + } + + public function testGetSubject() + { + $this->assertSame($this->subject, $this->event->getSubject()); + } + + public function testHasIterator() + { + $data = array(); + foreach ($this->event as $key => $value) { + $data[$key] = $value; + } + $this->assertEquals(array('name' => 'Event'), $data); + } +} diff --git a/source/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php b/source/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php new file mode 100644 index 0000000..04f2861 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.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\EventDispatcher\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; + +/** + * @author Bernhard Schussek + */ +class ImmutableEventDispatcherTest extends TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $innerDispatcher; + + /** + * @var ImmutableEventDispatcher + */ + private $dispatcher; + + protected function setUp() + { + $this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher); + } + + public function testDispatchDelegates() + { + $event = new Event(); + + $this->innerDispatcher->expects($this->once()) + ->method('dispatch') + ->with('event', $event) + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->dispatch('event', $event)); + } + + public function testGetListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('getListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->getListeners('event')); + } + + public function testHasListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('hasListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->hasListeners('event')); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddListenerDisallowed() + { + $this->dispatcher->addListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddSubscriberDisallowed() + { + $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock(); + + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveListenerDisallowed() + { + $this->dispatcher->removeListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveSubscriberDisallowed() + { + $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock(); + + $this->dispatcher->removeSubscriber($subscriber); + } +} diff --git a/source/vendor/symfony/event-dispatcher/composer.json b/source/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 0000000..14fc24b --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Symfony EventDispatcher Component", + "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": ">=5.3.9" + }, + "require-dev": { + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/source/vendor/symfony/event-dispatcher/phpunit.xml.dist b/source/vendor/symfony/event-dispatcher/phpunit.xml.dist new file mode 100644 index 0000000..f2eb169 --- /dev/null +++ b/source/vendor/symfony/event-dispatcher/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/source/vendor/topthink/think-installer/.gitignore b/source/vendor/topthink/think-installer/.gitignore new file mode 100644 index 0000000..8f4c02d --- /dev/null +++ b/source/vendor/topthink/think-installer/.gitignore @@ -0,0 +1,3 @@ +/.idea +composer.lock +/vendor \ No newline at end of file diff --git a/source/vendor/topthink/think-installer/composer.json b/source/vendor/topthink/think-installer/composer.json new file mode 100644 index 0000000..4005de2 --- /dev/null +++ b/source/vendor/topthink/think-installer/composer.json @@ -0,0 +1,25 @@ +{ + "name": "topthink/think-installer", + "type": "composer-plugin", + "require": { + "composer-plugin-api": "^1.0" + }, + "require-dev": { + "composer/composer": "1.0.*@dev" + }, + "license": "Apache-2.0", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "autoload": { + "psr-4": { + "think\\composer\\": "src" + } + }, + "extra": { + "class": "think\\composer\\Plugin" + } +} diff --git a/source/vendor/topthink/think-installer/src/Plugin.php b/source/vendor/topthink/think-installer/src/Plugin.php new file mode 100644 index 0000000..757c30f --- /dev/null +++ b/source/vendor/topthink/think-installer/src/Plugin.php @@ -0,0 +1,26 @@ +getInstallationManager(); + + //框架核心 + $manager->addInstaller(new ThinkFramework($io, $composer)); + + //单元测试 + $manager->addInstaller(new ThinkTesting($io, $composer)); + + //扩展 + $manager->addInstaller(new ThinkExtend($io, $composer)); + + } +} \ No newline at end of file diff --git a/source/vendor/topthink/think-installer/src/ThinkExtend.php b/source/vendor/topthink/think-installer/src/ThinkExtend.php new file mode 100644 index 0000000..d78f118 --- /dev/null +++ b/source/vendor/topthink/think-installer/src/ThinkExtend.php @@ -0,0 +1,77 @@ + +// +---------------------------------------------------------------------- + +namespace think\composer; + +use Composer\Installer\LibraryInstaller; +use Composer\Package\PackageInterface; +use Composer\Repository\InstalledRepositoryInterface; + +class ThinkExtend extends LibraryInstaller +{ + + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + parent::install($repo, $package); + $this->copyExtraFiles($package); + } + + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + parent::update($repo, $initial, $target); + $this->copyExtraFiles($target); + + } + + protected function copyExtraFiles(PackageInterface $package) + { + if ($this->composer->getPackage()->getType() == 'project') { + + $extra = $package->getExtra(); + + if (!empty($extra['think-config'])) { + + $composerExtra = $this->composer->getPackage()->getExtra(); + + $appDir = !empty($composerExtra['app-path']) ? $composerExtra['app-path'] : 'application'; + + if (is_dir($appDir)) { + + $extraDir = $appDir . DIRECTORY_SEPARATOR . 'extra'; + $this->filesystem->ensureDirectoryExists($extraDir); + + //配置文件 + foreach ((array) $extra['think-config'] as $name => $config) { + $target = $extraDir . DIRECTORY_SEPARATOR . $name . '.php'; + $source = $this->getInstallPath($package) . DIRECTORY_SEPARATOR . $config; + + if (is_file($target)) { + $this->io->write("File {$target} exist!"); + continue; + } + + if (!is_file($source)) { + $this->io->write("File {$target} not exist!"); + continue; + } + + copy($source, $target); + } + } + } + } + } + + public function supports($packageType) + { + return 'think-extend' === $packageType; + } +} \ No newline at end of file diff --git a/source/vendor/topthink/think-installer/src/ThinkFramework.php b/source/vendor/topthink/think-installer/src/ThinkFramework.php new file mode 100644 index 0000000..cdb7d84 --- /dev/null +++ b/source/vendor/topthink/think-installer/src/ThinkFramework.php @@ -0,0 +1,59 @@ +composer->getPackage()->getType() == 'project' && $package->getInstallationSource() != 'source') { + //remove tests dir + $this->filesystem->removeDirectory($this->getInstallPath($package) . DIRECTORY_SEPARATOR . 'tests'); + } + } + + /** + * {@inheritDoc} + */ + public function getInstallPath(PackageInterface $package) + { + if ('topthink/framework' !== $package->getPrettyName()) { + throw new \InvalidArgumentException('Unable to install this library!'); + } + + if ($this->composer->getPackage()->getType() !== 'project') { + return parent::getInstallPath($package); + } + + if ($this->composer->getPackage()) { + $extra = $this->composer->getPackage()->getExtra(); + if (!empty($extra['think-path'])) { + return $extra['think-path']; + } + } + + return 'thinkphp'; + } + + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + parent::update($repo, $initial, $target); + if ($this->composer->getPackage()->getType() == 'project' && $target->getInstallationSource() != 'source') { + //remove tests dir + $this->filesystem->removeDirectory($this->getInstallPath($target) . DIRECTORY_SEPARATOR . 'tests'); + } + } + + /** + * {@inheritDoc} + */ + public function supports($packageType) + { + return 'think-framework' === $packageType; + } +} \ No newline at end of file diff --git a/source/vendor/topthink/think-installer/src/ThinkTesting.php b/source/vendor/topthink/think-installer/src/ThinkTesting.php new file mode 100644 index 0000000..bf27f72 --- /dev/null +++ b/source/vendor/topthink/think-installer/src/ThinkTesting.php @@ -0,0 +1,68 @@ + +// +---------------------------------------------------------------------- + +namespace think\composer; + + +use Composer\Installer\LibraryInstaller; +use Composer\Package\PackageInterface; +use Composer\Repository\InstalledRepositoryInterface; + +class ThinkTesting extends LibraryInstaller +{ + /** + * {@inheritDoc} + */ + public function getInstallPath(PackageInterface $package) + { + if ('topthink/think-testing' !== $package->getPrettyName()) { + throw new \InvalidArgumentException('Unable to install this library!'); + } + + return parent::getInstallPath($package); + } + + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + parent::install($repo, $package); + + $this->copyTestDir($package); + + + } + + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + parent::update($repo, $initial, $target); + + $this->copyTestDir($target); + + } + + private function copyTestDir(PackageInterface $package) + { + $appDir = dirname($this->vendorDir); + $source = $this->getInstallPath($package) . DIRECTORY_SEPARATOR . 'example'; + if (!is_file($appDir . DIRECTORY_SEPARATOR . 'phpunit.xml')) { + $this->filesystem->copyThenRemove($source, $appDir); + } else { + $this->filesystem->removeDirectoryPhp($source); + } + } + + /** + * {@inheritDoc} + */ + public function supports($packageType) + { + return 'think-testing' === $packageType; + } +} \ No newline at end of file diff --git a/version.json b/version.json new file mode 100644 index 0000000..c44071b --- /dev/null +++ b/version.json @@ -0,0 +1,3 @@ +{ + "version": "1.1.38" +} diff --git a/web/assets/admin/css/app.css b/web/assets/admin/css/app.css new file mode 100644 index 0000000..e93b8bd --- /dev/null +++ b/web/assets/admin/css/app.css @@ -0,0 +1,1170 @@ +@charset "UTF-8"; +::-moz-selection { + background: #009688; + color: #FFF; } + +::selection { + background: #159b76; + color: #FFF; } + +::-webkit-scrollbar { + width: 6px; + height: 6px; } + +::-webkit-scrollbar-thumb { + border-radius: 10px; + background-color: #009688; + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); } + +html, +body, +.tpl-g { + height: 100%; } + +body { + background-color: #eceef3; + font-family: "Monospaced Number", "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; } + +ul, +li { + list-style: none; + padding: 0; + margin: 0; } + +a:focus { + outline: none; } + +.icon { + width: 1em; + height: 1em; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; } + +.tpl-header { + z-index: 1000; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + background: #fff; + position: fixed; + top: 0; + width: 100%; + transition: all 0.4s ease-in-out; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + +.tpl-header-logo { + width: 240px; + height: 57px; + display: table; + text-align: center; + position: relative; + z-index: 1300; } + .tpl-header-logo a { + display: table-cell; + vertical-align: middle; } + .tpl-header-logo img { + width: 170px; } + +.tpl-header-fluid { + height: 50px; + max-width: 1200px; + margin: 0 auto; } + +.tpl-header-button { + float: left; + color: #333; + margin: 0 0 0 -20px; + border: 0; + border-radius: 0; + padding: 0 22px; + line-height: 50px; + background: #fff; + cursor: pointer; } + .tpl-header-button:hover { + background: #fff; + color: #999; + outline: none; } + +.tpl-header-navbar { + color: #fff; } + .tpl-header-navbar li { + float: left; } + .tpl-header-navbar a { + line-height: 50px; + display: block; + padding: 0 16px; + position: relative; + color: #333; + -webkit-transition: all .3s; + transition: all .3s; } + .tpl-header-navbar a:hover { + background: rgba(0, 0, 0, 0.025); } + .tpl-header-navbar a .item-feed-badge { + position: absolute; + top: 8px; + left: 25px; + padding: .25em .42em; } + +ul.tpl-dropdown-content { + padding: 10px; + margin-top: 0; + width: 300px; + border-radius: 0; + background: #fff; + border: 1px solid #ddd; } + ul.tpl-dropdown-content li { + float: none; } + ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-title { + font-size: 12px; + float: left; + color: rgba(255, 255, 255, 0.7); + color: #616161; } + ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-time { + float: right; + text-align: right; + color: rgba(255, 255, 255, 0.7); + font-size: 11px; + width: 50px; + margin-left: 10px; } + ul.tpl-dropdown-content .tpl-dropdown-menu-notifications:last-child .tpl-dropdown-menu-notifications-item { + text-align: center; + border: none; + font-size: 12px; } + ul.tpl-dropdown-content .tpl-dropdown-menu-notifications:last-child .tpl-dropdown-menu-notifications-item i { + margin-left: -6px; } + ul.tpl-dropdown-content .tpl-dropdown-menu-messages:last-child .tpl-dropdown-menu-messages-item { + text-align: center; + border: none; + font-size: 12px; } + ul.tpl-dropdown-content .tpl-dropdown-menu-messages:last-child .tpl-dropdown-menu-messages-item i { + margin-left: -6px; } + ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-content .menu-messages-content-time { + color: #96a5aa; } + ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item:hover { + background-color: #f5f5f5; } + +ul.tpl-dropdown-content:before, +ul.tpl-dropdown-content:after { + display: none; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item { + padding: 12px; + line-height: 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.15); + border-bottom: 1px solid #eee; + color: #999; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item:hover, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item:hover, +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item:focus, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item:focus { + background-color: #465154; + color: #fff; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-ico, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-ico { + line-height: initial; + float: left; + width: 35px; + height: 35px; + border-radius: 50%; + margin-right: 10px; + margin-top: 6px; + overflow: hidden; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-ico img, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-ico img { + width: 100%; + height: auto; + vertical-align: middle; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-time, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-time { + float: right; + text-align: right; + color: rgba(255, 255, 255, 0.7); + font-size: 11px; + width: 40px; + margin-left: 10px; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-content, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-content { + display: block; + font-size: 13px; + margin-left: 45px; + margin-right: 50px; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-content .menu-messages-content-time, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-content .menu-messages-content-time { + margin-top: 3px; + color: rgba(255, 255, 255, 0.7); + font-size: 11px; } + +.am-dimmer { + z-index: 1200; } + +.am-modal { + z-index: 1300; } + +.am-datepicker-dropdown { + z-index: 1400; } + +.tpl-content-wrapper { + max-width: 1200px; + margin: 80px auto 0 auto; + transition: all 0.4s ease-in-out; + position: relative; } + .tpl-content-wrapper .left-sidebar { + width: 200px; + flex-basis: 200px; + padding: 20px 0; + background: #fff; + border-radius: 3px; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link { + min-height: 48px; + line-height: 48px; + font-size: 14px; + position: relative; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link a { + display: block; + padding-left: 25px; + color: #777; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link a:hover { + color: #6af; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link a.sidebar-nav-link-disabled { + cursor: default; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link a.sidebar-nav-link-disabled:hover { + color: #777; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link .sidebar-nav-link-logo { + margin-right: 3px; + width: 20px; + font-size: 16px; + display: inline-block; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link .sidebar-third-nav-sub { + margin: 0; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link .sidebar-third-nav-sub a { + padding-left: 55px; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link.active { + background: #f4f8ff; } + .tpl-content-wrapper .left-sidebar li.sidebar-nav-link.active:before { + content: " "; + top: 0; + left: 0; + bottom: 0; + width: 4px; + background: #66a2ff; + position: absolute; } + .tpl-content-wrapper .row-content { + flex-basis: calc(100% - 200px); + padding: 0 15px 0 15px; } + +.page-header { + background: #424b4f; + margin-top: 0; + margin-bottom: 0; + padding: 40px 0; + border-bottom: 0; } + +.container-fluid { + margin-top: 0; + margin-bottom: 0; + border-bottom: 0; + padding-left: 20px; + padding-right: 20px; } + +.row { + margin-right: -10px; + margin-left: -10px; } + +.page-header-description { + margin-top: 4px; + margin-bottom: 0; + font-size: 14px; + color: #666; } + +.page-header-heading { + font-size: 20px; + font-weight: 400; + color: #666; } + .page-header-heading .page-header-heading-ico { + font-size: 28px; + position: relative; + top: 3px; } + .page-header-heading small { + font-weight: normal; + line-height: 1; + color: #b3b3b3; } + +.widget { + width: 100%; + min-height: 148px; + position: relative; + padding: 10px 20px 13px; + background-color: #fff; + color: #333; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + border-radius: 3px; } + +.widget-body { + padding: 0 15px; + width: 100%; } + +.widget-head { + width: 100%; + padding: 12px 20px; + border-bottom: 1px solid #eef1f5; + margin-top: 10px; + margin-bottom: 20px; } + .widget-head:not(:first-child) { + margin-top: 40px; } + .widget-head .widget-title { + position: relative; + font-size: 1.5rem; } + .widget-head .widget-title::before { + content: ''; + position: absolute; + width: 4px; + height: 14px; + background: #00aeff; + top: 6px; + left: -12px; } + +.tpl-table-black-operation a { + display: inline-block; + padding: 5px 6px; + font-size: 12px; + line-height: 12px; + border: 1px solid #36c6d3; + color: #36c6d3; } + .tpl-table-black-operation a:hover { + background: #36c6d3; + color: #fff; } + .tpl-table-black-operation a.tpl-table-black-operation-del { + border: 1px solid #e7505a; + color: #e7505a; } + .tpl-table-black-operation a.tpl-table-black-operation-del:hover { + background: #e7505a; + color: #fff; } + .tpl-table-black-operation a.tpl-table-black-operation-green { + border: 1px solid #5eb95e; + color: #5eb95e; } + .tpl-table-black-operation a.tpl-table-black-operation-green:hover { + background: #5eb95e; + color: #fff; } + +.tpl-page-state { + width: 100%; } + +.tpl-page-state-title { + font-size: 40px; + font-weight: bold; + color: #838fa1; } + +.tpl-page-state-content { + padding: 10px 0; } + +.tpl-login { + width: 100%; } + +.tpl-login-logo { + max-width: 159px; + height: 205px; + margin: 0 auto 20px auto; } + +.tpl-login-title { + width: 100%; + font-size: 24px; + color: #697882; } + .tpl-login-title strong { + color: #39bae4; } + +.tpl-login-content { + margin: 12% auto 0; + width: 500px; + padding: 40px 40px 25px; + background-color: #fff; + border-radius: 4px; } + +.tpl-login-remember-me { + color: #b3b3b3; + font-size: 14px; } + .tpl-login-remember-me label { + position: relative; + top: -2px; } + +.tpl-login-content-info { + color: #b3b3b3; + font-size: 14px; } + +.cl-p { + padding: 0 !important; } + +.tpl-table-line-img { + max-width: 100px; + padding: 2px; + border: 1px solid #ddd; } + +.tpl-table-list-select { + text-align: right; } + +.fc-button-group, +.fc button { + display: block; } + +.tpl-header-search-box:hover, +.tpl-header-search-box:active .tpl-error-title { + color: #848c90; } + +.tpl-error-title-info { + line-height: 30px; + font-size: 21px; + margin-top: 20px; + text-align: center; + color: #dce2ec; } + +.tpl-error-btn { + background: #03a9f3; + border: 1px solid #03a9f3; + border-radius: 30px; + padding: 6px 20px 8px; } + +.tpl-error-content { + margin-top: 20px; + margin-bottom: 20px; + font-size: 16px; + text-align: center; + color: #96a2b4; } + +.tpl-calendar-box { + background: #fff; + border-radius: 4px; + padding: 20px; } + .tpl-calendar-box .fc-event { + border-radius: 0; + background: #03a9f3; + border: 1px solid #14b0f6; } + .tpl-calendar-box .fc-axis { + color: #868e8e; } + .tpl-calendar-box .fc-unthemed .fc-today { + background: #eee; } + .tpl-calendar-box .fc-more { + color: #868e8e; } + .tpl-calendar-box .fc th { + color: #868e8e; + font-weight: normal; + font-size: 14px; + padding: 6px 0; } + .tpl-calendar-box .fc th.fc-widget-header { + background: #32c5d2 !important; + color: #ffffff; + font-size: 14px; + line-height: 20px; + padding: 7px 0; + text-transform: uppercase; + border: none !important; } + .tpl-calendar-box .fc th.fc-widget-header a { + color: #fff; } + .tpl-calendar-box .fc-center h2 { + color: #868e8e; } + .tpl-calendar-box .fc-state-default { + background: #fff; + font-size: 14px; + color: #868e8e; } + .tpl-calendar-box .fc-day-number { + color: #868e8e; + padding-right: 6px; } + +.tpl-calendar-box .fc th, +.tpl-calendar-box .fc td, +.tpl-calendar-box .fc hr, +.tpl-calendar-box .fc thead, +.tpl-calendar-box .fc tbody, +.tpl-calendar-box .fc-row { + border-color: #eee !important; } + +.tpl-pagination .am-disabled a, +.tpl-pagination li a { + color: #23abf0; + border-radius: 3px; + padding: 6px 12px; } + +.tpl-pagination .am-active a { + background: #23abf0; + color: #fff; + border: 1px solid #23abf0; + padding: 6px 12px; } + +.tpl-login-btn { + background-color: #32c5d2; + border: none; + padding: 10px 16px; + font-size: 14px; + line-height: 14px; + outline: none; } + +.tpl-login-btn:hover, +.tpl-login-btn:active { + background: #22b2e1; + color: #fff; } + +.tpl-form-border-form input[type=number]:focus, +.tpl-form-border-form input[type=search]:focus, +.tpl-form-border-form input[type=text]:focus, +.tpl-form-border-form input[type=password]:focus, +.tpl-form-border-form input[type=datetime]:focus, +.tpl-form-border-form input[type=datetime-local]:focus, +.tpl-form-border-form input[type=date]:focus, +.tpl-form-border-form input[type=month]:focus, +.tpl-form-border-form input[type=time]:focus, +.tpl-form-border-form input[type=week]:focus, +.tpl-form-border-form input[type=email]:focus, +.tpl-form-border-form input[type=url]:focus, +.tpl-form-border-form input[type=tel]:focus, +.tpl-form-border-form input[type=color]:focus, +.tpl-form-border-form select:focus, +.tpl-form-border-form textarea:focus, +.am-form-field:focus { + -webkit-box-shadow: none; + box-shadow: none; } + +.tpl-form-border-form input[type=number], +.tpl-form-border-form input[type=search], +.tpl-form-border-form input[type=text], +.tpl-form-border-form input[type=password], +.tpl-form-border-form input[type=datetime], +.tpl-form-border-form input[type=datetime-local], +.tpl-form-border-form input[type=date], +.tpl-form-border-form input[type=month], +.tpl-form-border-form input[type=time], +.tpl-form-border-form input[type=week], +.tpl-form-border-form input[type=email], +.tpl-form-border-form input[type=url], +.tpl-form-border-form input[type=tel], +.tpl-form-border-form input[type=color], +.tpl-form-border-form select, +.tpl-form-border-form textarea, +.am-form-field { + display: block; + width: 100%; + line-height: 1.42857; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + background: 0 0; + border: 1px solid #c2cad8; + text-indent: .5em; + border-radius: 0; + color: #555; + box-shadow: none; + padding-left: 0; + padding-right: 0; + font-size: 14px; } + +.tpl-form-border-form .am-checkbox, +.tpl-form-border-form .am-checkbox-inline, +.tpl-form-border-form .am-form-label, +.tpl-form-border-form .am-radio, +.tpl-form-border-form .am-radio-inline { + margin-top: 0; + margin-bottom: 0; } + +.tpl-form-border-form .am-form-group:after { + clear: both; } +.tpl-form-border-form .am-form-label { + padding-top: 5px; + font-size: 16px; + color: #888; + font-weight: inherit; + text-align: right; } + .tpl-form-border-form .am-form-label .tpl-form-line-small-title { + color: #999; + font-size: 12px; } + +.tpl-form-border-form .am-form-group:after, +.tpl-form-border-form .am-form-group:before { + content: " "; + display: table; } + +.tpl-form-line-form input[type=number]:focus, +.tpl-form-line-form input[type=search]:focus, +.tpl-form-line-form input[type=text]:focus, +.tpl-form-line-form input[type=password]:focus, +.tpl-form-line-form input[type=datetime]:focus, +.tpl-form-line-form input[type=datetime-local]:focus, +.tpl-form-line-form input[type=date]:focus, +.tpl-form-line-form input[type=month]:focus, +.tpl-form-line-form input[type=time]:focus, +.tpl-form-line-form input[type=week]:focus, +.tpl-form-line-form input[type=email]:focus, +.tpl-form-line-form input[type=url]:focus, +.tpl-form-line-form input[type=tel]:focus, +.tpl-form-line-form input[type=color]:focus, +.tpl-form-line-form select:focus, +.tpl-form-line-form textarea:focus, +.am-form-field:focus { + -webkit-box-shadow: none; + box-shadow: none; } + +.tpl-form-line-form input[type=number], +.tpl-form-line-form input[type=search], +.tpl-form-line-form input[type=text], +.tpl-form-line-form input[type=password], +.tpl-form-line-form input[type=datetime], +.tpl-form-line-form input[type=datetime-local], +.tpl-form-line-form input[type=date], +.tpl-form-line-form input[type=month], +.tpl-form-line-form input[type=time], +.tpl-form-line-form input[type=week], +.tpl-form-line-form input[type=email], +.tpl-form-line-form input[type=url], +.tpl-form-line-form input[type=tel], +.tpl-form-line-form input[type=color], +.tpl-form-line-form select, +.tpl-form-line-form textarea, +.am-form-field { + display: block; + width: 100%; + padding: 6px 5px; + line-height: 1.42857; + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + border: 0; + border-bottom: 1px solid #c2cad8; + color: #555; + box-shadow: none; + font-size: 14px; } + +.am-form input[type=text][readonly] { + background: #f7f7f7 !important; + cursor: text; } + +.tpl-form-line-form .am-checkbox, +.tpl-form-line-form .am-checkbox-inline, +.tpl-form-line-form .am-form-label, +.tpl-form-line-form .am-radio, +.tpl-form-line-form .am-radio-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: .8rem; } + +.am-checkbox, .am-checkbox-inline, .am-radio, .am-radio-inline { + user-select: none; } + +.tpl-form-line-form .am-checkbox .am-ucheck-icons, +.tpl-form-line-form .am-checkbox-inline .am-ucheck-icons, +.tpl-form-line-form .am-form-label .am-ucheck-icons, +.tpl-form-line-form .am-radio .am-ucheck-icons, +.tpl-form-line-form .am-radio-inline .am-ucheck-icons { + line-height: 40px; } + +.am-ucheck-checkbox:checked + .am-ucheck-icons, +.am-ucheck-radio:checked + .am-ucheck-icons { + color: #5bb9ff; } + +.tpl-form-line-form .am-form-group:after { + clear: both; } +.tpl-form-line-form .am-form-label { + padding-top: .8rem; + font-size: 1.34rem; + color: #656565; + font-weight: inherit; + text-align: right; } + .tpl-form-line-form .am-form-label .tpl-form-line-small-title { + color: #8c8c8c; + font-size: 12px; } +.tpl-form-line-form .am-form-error .am-form-label { + color: #dd514c; } + +.tpl-form-line-form .am-form-group:after, +.tpl-form-line-form .am-form-group:before { + content: " "; + display: table; } + +.tpl-amendment-echarts { + left: -17px; } + +.tpl-user-card { + border: 1px solid #3598dc; + border-top: 2px solid #3598dc; + background: #3598dc; + color: #ffffff; + border-radius: 4px; } + +.tpl-user-card-title { + font-size: 26px; + font-weight: 300; + margin-top: 25px; + margin-bottom: 10px; } + +.achievement-subheading { + font-size: 12px; + margin-top: 0; + margin-bottom: 15px; } + +.achievement-image { + border-radius: 50%; + margin-bottom: 22px; } + +.achievement-description { + margin: 0; + font-size: 12px; } + +.tpl-table-black { + color: #6d7279; } + .tpl-table-black thead > tr > th { + font-size: 1.3rem; + padding: 6px; } + .tpl-table-black tbody > tr > td { + font-size: 1.3rem; + padding: 7px 6px; } + .tpl-table-black tfoot > tr > th { + font-size: 14px; + padding: 6px 0; } + +.am-progress { + height: 12px; } + +.am-progress-title { + font-size: 14px; + margin-bottom: 8px; } + +.widget-fluctuation-tpl-btn { + margin-top: 6px; + display: block; + color: #fff; + font-size: 12px; + padding: 8px 14px; + outline: none; + background-color: #e7505a; + border: 1px solid #e7505a; } + .widget-fluctuation-tpl-btn:hover { + background: transparent; + color: #e7505a; } + +.text-success { + color: #5eb95e; } + +.widget-function a { + color: #838fa1; } + .widget-function a:hover { + color: #a7bdcd; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item:hover, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item:hover { + background-color: #f5f5f5; } + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .tpl-dropdown-menu-notifications-time, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .tpl-dropdown-menu-notifications-time { + color: #999; } + +.tpl-header.active { + padding-left: 0; } + +.tpl-header-logo { + background: #fff; + border-bottom: 1px solid #eee; } + +.widget-color-green { + border: 1px solid #32c5d2; + border-top: 2px solid #32c5d2; + background: #32c5d2; + color: #ffffff; } + .widget-color-green .widget-fluctuation-period-text { + color: #fff; } + .widget-color-green .widget-head { + border-bottom: 1px solid #2bb8c4; } + .widget-color-green .widget-fluctuation-description-text { + color: #bbe7f6; } + .widget-color-green .widget-function a { + color: #42bde5; } + .widget-color-green .widget-function a:hover { + color: #fff; } + +@media screen and (max-width: 1024px) { + .left-sidebar { + left: -320px; + top: 50px; } + + .tpl-content-wrapper { + margin-left: 0 !important; } + + .tpl-sidebar-user-panel { + border-top: 1px solid #eee; } + + .tpl-header { + padding-left: 0; } } +@media screen and (min-width: 641px) { + [class*=am-u-] { + padding-left: 10px; + padding-right: 10px; } } +@media screen and (max-width: 641px) { + .tpl-error-title, + .tpl-login-title { + font-size: 20px; } + + .tpl-login-content { + width: 86%; + padding: 22px 30px 25px; } + + .tpl-header-search { + display: none; } + + ul.tpl-dropdown-content { + position: fixed; + width: 100%; + left: 0; + top: 112px; + right: 0; } } +/* table */ +.am-table { + border-collapse: collapse; + font-size: 1.4rem; } + .am-table > thead > tr > th { + vertical-align: middle; } + .am-table .item-title { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + text-align: left !important; + margin: 0; + white-space: normal; } + .am-table p { + margin: 0; } + +.am-btn-toolbar .am-btn-group > .am-btn { + border-radius: 2px !important; + margin-right: 10px; } + +/* 工具栏 */ +.page_toolbar .am-form-group { + max-width: 300px; + margin-left: .5rem; } + .page_toolbar .am-form-group .am-form-field, .page_toolbar .am-form-group .am-btn { + border-radius: 2px; + outline: 0; } + .page_toolbar .am-form-group span, .page_toolbar .am-form-group .am-selected-list { + font-size: 1.4rem; } +.page_toolbar .am-input-group .am-form-field { + border-bottom-right-radius: 0; + border-top-right-radius: 0; } +.page_toolbar .am-input-group .am-input-group-btn .am-btn { + background: #fff; + border-bottom-left-radius: 0; + border-top-left-radius: 0; } + .page_toolbar .am-input-group .am-input-group-btn .am-btn:hover { + color: #2589ff; } + +/* 文字颜色 */ +.x-color-red { + color: #f00 !important; } + +.x-color-green { + color: #4db14d !important; } + +.x-color-yellow { + color: #fcb500 !important; } + +.x-color-blue { + color: #259fdc !important; } + +.x-color-c-gray-5f { + color: #828282; } + +/* flex布局 */ +.dis-flex { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; } + +.flex-box { + flex: 1; } + +.flex-dir-row { + flex-direction: row; } + +.flex-dir-column { + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; } + +.flex-x-center { + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; } + +.flex-x-between { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; } + +.flex-x-around { + justify-content: space-around; } + +.flex-x-end { + -webkit-box-pack: end; + -ms-flex-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; } + +.flex-y-center { + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; } + +.tips .pre { + padding: 1rem; + background-color: #fbfdff; + border: 1px solid #dfeffd; + white-space: normal; + font-size: 1.22rem; + line-height: 1.6; + color: #259fdc; } + .tips .pre p { + margin: .5rem 0; } + .tips .pre a { + color: #00669a; } + .tips .pre a:hover { + color: #000000; } + +/* input滑块 */ +input[type=range] { + outline: none; + -webkit-appearance: none; + background: -webkit-linear-gradient(#61bd12, #61bd12) no-repeat, #ddd; + height: 3px; + border-radius: 5px; } + +input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none; + position: relative; + height: 15px; + width: 15px; + border: 1px solid #d3d3d3; + border-radius: 50%; + background: #fff; + cursor: pointer; } + +/* input滑块显示值 */ +.display-value { + display: inline-block; + margin-left: .8rem; } + +/*分页*/ +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; } + .pagination > li { + display: inline; } + +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 4px 12px; + line-height: 1.42857143; + color: #23abf0; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 3px; + margin: 0 2px; } + +.theme-black .pagination > li > a, +.theme-black .pagination > li > span { + color: #fff; + padding: 6px 12px; + background: #3f4649; + border: none; } + +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; } + +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; } + +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; } + +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #23abf0; + border-color: #23abf0; } + +.theme-black .pagination > .active > a, +.theme-black .pagination > .active > span, +.theme-black .pagination > .active > a:hover, +.theme-black .pagination > .active > span:hover, +.theme-black .pagination > .active > a:focus, +.theme-black .pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #23abf0; + border-color: #23abf0; } + +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; } + +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; } + +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; } + +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; } + +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; } + +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; } + +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; } + +.pagination-total { + height: 32px; + margin-top: 20px; + margin-bottom: 20px; } + .pagination-total .am-vertical-align-middle { + font-size: 1.4rem; } + +/* 搜索表单*/ +.search-form { + margin-bottom: 20px; } + .search-form label { + padding: 0 10px !important; + font-weight: normal; + font-size: 1.4rem; + text-align: left !important; + height: 32px; + line-height: 32px; + width: 90px; } + .search-form input { + width: auto !important; + padding: 4px 8px !important; + line-height: 1.42857 !important; + font-size: 1.4rem; } + .search-form input::placeholder { + font-size: 1.4rem; } + .search-form .item { + float: left; + margin-right: 20px; } + .search-form .item:last-child { + background-color: #0c7cb5; + float: right; } + .search-form .am-selected { + float: left; } + .search-form .am-selected .am-btn-default { + border: 1px solid #ccc; } + +/* 表单页面*/ +.am-form .form-require::after { + content: "*"; + color: #f00; + line-height: 18px; } +.am-form .form-tab-group { + display: none; } + .am-form .form-tab-group.active { + display: block; } +.am-form .am-form-file { + padding-top: .4rem; } + .am-form .am-form-file .upload-file { + font-size: 1.22rem; + padding: .5rem .9rem; } +.am-form input[type=color] { + -webkit-appearance: square-button; + width: 5rem; + height: 2.2rem; + background-color: buttonface; + cursor: pointer; + border: 1px solid gainsboro; + border-image: initial; + padding: 0 2px; } +.am-form input[type=text][disabled] { + background-color: initial; } +.am-form .am-form-success .am-form-file [class*=icon-] { + color: #fff; } +.am-form .am-input-group .am-input-group-label { + background: none; + border: none; } +.am-form .am-form-file input[type=file] { + width: auto; + font-size: unset; + background: #000; + line-height: 110px; } + +.am-field-valid:focus, +.am-form-success .am-form-field:focus { + box-shadow: none !important; + -webkit-box-shadow: none !important; } + +.am-form-success label { + color: #656565 !important; } + +.am-field-valid + .am-ucheck-icons { + color: #999; } + +.am-form small, +.help-block small { + color: #838fa1; + font-size: 1.2rem; } + +/*# sourceMappingURL=app.css.map */ diff --git a/web/assets/admin/css/login/style.css b/web/assets/admin/css/login/style.css new file mode 100644 index 0000000..7f7dc24 --- /dev/null +++ b/web/assets/admin/css/login/style.css @@ -0,0 +1,231 @@ +html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; + /*outline-style: none*/ +} + +@keyframes fade-in { + 0% { + opacity: 0; + } + + /*初始状态 透明度为0*/ + 40% { + opacity: 0; + } + + /*过渡状态 透明度为0*/ + 100% { + opacity: 1; + } + + /*结束状态 透明度为1*/ +} + +@-webkit-keyframes fade-in { + + /*针对webkit内核*/ + 0% { + opacity: 0; + } + 40% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +#wrapper { + +} + +body { + /*line-height: 1;*/ + font: 14px/1.5 "Segoe UI", "Lucida Grande", Helvetica, Arial, "Microsoft YaHei", FreeSans, Arimo, "Droid Sans", "wenquanyi micro hei", "Hiragino Sans GB", "Hiragino Sans GB W3", Roboto, Arial, sans-serif; +} + +a { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent +} + +a:hover, a:focus { + text-decoration: none; + bblr: expression(this.onFocus=this.blur()); + outline-style: none +} + +table { + border-collapse: collapse; + border-spacing: 0 +} + +input, select { + vertical-align: middle +} + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.clearfix::before, .clearfix::after { + content: ""; + height: 0; + line-height: 0; + display: block; + visibility: hidden; + clear: both +} + +.clearfix:after { + clear: both +} + +.clearfix { + *zoom: 1 +} + +/* login */ +html, body { + height: 100%; +} + +.page-login-v3:before { + position: fixed; + top: 0; + left: 0; + z-index: -1; + width: 100%; + height: 100%; + content: ''; + background: #62a8ea; + background: url(../../img/login_bg.jpg) center center/cover no-repeat !important; +} + +.container { + text-align: center; + height: 100%; +} + +.container:before { + display: inline-block; + height: 100%; + vertical-align: middle; + content: ""; +} + +.login-body { + display: inline-block; + vertical-align: middle; + width: 400px; + margin: 75px 0; + background: #fff; + border-radius: 4px; + padding: 50px 40px 40px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + box-shadow: rgba(199, 199, 199, 0.18) 0 0 70px 8px; + + animation: fade-in; /*动画名称*/ + animation-duration: .6s; /*动画持续时间*/ + -webkit-animation: fade-in .6s; /*针对webkit内核*/ +} + +.login-content { +} + +.login-content .brand .brand-img { + width: 50px; +} + +.login-content .brand .brand-text { + margin-top: 20px; + margin-bottom: 11px; + font-size: 20px !important; + /*font-family: "Microsoft YaHei";*/ + text-shadow: rgba(0, 0, 0, .15) 0 0 1px; + font-weight: 400; + color: #2e73ff; +} + +.login-form { + margin: 45px 0 30px; +} + +.login-form .form-group { + margin: 30px 0; +} + +.login-form input { + display: block; + border: 0; + border-radius: 0; + -webkit-box-shadow: none; + box-shadow: none; + width: 100%; + border-bottom: 1px solid #E4EAEC; + /*font-size: 14px;*/ + height: 42px; + /*line-height: 1.5;*/ + outline: none; + padding: 0 5px; + color: #a3afb7; + font: 14px/1.5 "Segoe UI", "Lucida Grande", Helvetica, Arial, "Microsoft YaHei", FreeSans, Arimo, "Droid Sans", "wenquanyi micro hei", "Hiragino Sans GB", "Hiragino Sans GB W3", Roboto, Arial, sans-serif; + transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -webkit-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; +} + +.login-form input:focus { + border-bottom: 1px solid #2e73ff; +} + +.login-form button { + width: 100%; + margin-top: 20px; + padding: 10px 18px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 30px; + white-space: normal; + -webkit-transition: border .2s linear, color .2s linear, width .2s linear, background-color .2s linear; + -o-transition: border .2s linear, color .2s linear, width .2s linear, background-color .2s linear; + transition: border .2s linear, color .2s linear, width .2s linear, background-color .2s linear; + -webkit-font-smoothing: subpixel-antialiased; + color: #fff; + background-color: #2e73ff; + /*border-color: #2e73ff;*/ + /*background-image: none;*/ + border: 1px solid transparent; + cursor: pointer; + outline: none; + + box-shadow: rgba(152, 22, 244, 0.19) 0px 5px 10px 2px; +} + +.login-form button[disabled], .login-form button[disabled]:hover { + color: #fff; + background-color: #a2caee; + border-color: #a2caee; + cursor: not-allowed; + opacity: .65; +} + +.login-form button:hover { + background-color: #558dff; + border-color: #558dff; +} diff --git a/web/assets/admin/img/login_bg.jpg b/web/assets/admin/img/login_bg.jpg new file mode 100644 index 0000000..eb92779 Binary files /dev/null and b/web/assets/admin/img/login_bg.jpg differ diff --git a/web/assets/admin/img/login_bg1.jpg b/web/assets/admin/img/login_bg1.jpg new file mode 100644 index 0000000..aa2b785 Binary files /dev/null and b/web/assets/admin/img/login_bg1.jpg differ diff --git a/web/assets/admin/js/app.js b/web/assets/admin/js/app.js new file mode 100644 index 0000000..2bd5a02 --- /dev/null +++ b/web/assets/admin/js/app.js @@ -0,0 +1,153 @@ +/** + * jquery全局函数封装 + */ +(function ($) { + /** + * Jquery类方法 + */ + $.fn.extend({ + + superForm: function (option) { + // 默认选项 + var defaultOption = { + buildData: function () { + return {}; + }, + validation: function () { + return true; + } + }; + option = $.extend(true, {}, defaultOption, option); + + var $form = $(this) + , btn_submit = $('.j-submit'); + $form.validator({ + onValid: function (validity) { + $(validity.field).next('.am-alert').hide(); + }, + /** + * 显示错误信息 + * @param validity + */ + onInValid: function (validity) { + var $field = $(validity.field) + , $group = $field.parent() + , $alert = $group.find('.am-alert'); + + if ($field.data('validationMessage') !== undefined) { + // 使用自定义的提示信息 或 插件内置的提示信息 + var msg = $field.data('validationMessage') || this.getValidationMessage(validity); + if (!$alert.length) { + $alert = $('
    ').hide().appendTo($group); + } + $alert.html(msg).show(); + } + }, + submit: function () { + if (this.isFormValid() === true) { + // 自定义验证 + if (!option.validation()) + return false; + // 禁用按钮, 防止二次提交 + btn_submit.attr('disabled', true); + // 表单提交 + $form.ajaxSubmit({ + type: "post", + dataType: "json", + data: option.buildData(), + success: function (result) { + result.code === 1 ? $.show_success(result.msg, result.url) + : $.show_error(result.msg); + btn_submit.attr('disabled', false); + } + }); + } + return false; + } + }); + }, + + /** + * 删除元素 + */ + delete: function (index, url, msg) { + $(this).click(function () { + var param = {}; + param[index] = $(this).attr('data-id'); + layer.confirm(msg ? msg : '确定要删除吗?', {title: '友情提示'} + , function (index) { + $.post(url, param, function (result) { + result.code === 1 ? $.show_success(result.msg, result.url) + : $.show_error(result.msg); + }); + layer.close(index); + } + ); + }); + }, + + }); + + /** + * Jquery全局函数 + */ + $.extend({ + + /** + * 对象转URL + */ + urlEncode: function (data) { + var _result = []; + for (var key in data) { + var value = null; + if (data.hasOwnProperty(key)) value = data[key]; + if (value.constructor === Array) { + value.forEach(function (_value) { + _result.push(key + "=" + _value); + }); + } else { + _result.push(key + '=' + value); + } + } + return _result.join('&'); + }, + + /** + * 操作成功弹框提示 + * @param msg + * @param url + */ + show_success: function (msg, url) { + layer.msg(msg, { + icon: 1 + , time: 1200 + // , anim: 1 + , shade: 0.5 + , end: function () { + (url !== undefined && url.length > 0) ? window.location = url : window.location.reload(); + } + }); + }, + + /** + * 操作失败弹框提示 + * @param msg + * @param reload + */ + show_error: function (msg, reload) { + var time = reload ? 1200 : 0; + layer.alert(msg, { + title: '提示' + , icon: 2 + , time: time + , anim: 6 + , end: function () { + reload && window.location.reload(); + } + }); + } + + }); + +})(jQuery); + diff --git a/web/assets/admin/scss/app.scss b/web/assets/admin/scss/app.scss new file mode 100644 index 0000000..60f1a5a --- /dev/null +++ b/web/assets/admin/scss/app.scss @@ -0,0 +1,1525 @@ +@charset "utf-8"; + +@mixin transition($transition) { + -webkit-transition: $transition; + -moz-transition: $transition; + -o-transition: $transition; + transition: $transition; +} + +::-moz-selection { + background: #009688; + color: #FFF; +} + +::selection { + background: #159b76; + color: #FFF; +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-thumb { + border-radius: 10px; + background-color: #009688; + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); +} + +html, +body, +.tpl-g { + height: 100%; +} + +body { + background-color: #eceef3; + font-family: "Monospaced Number", "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +ul, +li { + list-style: none; + padding: 0; + margin: 0; +} + +a { + &:focus { + outline: none; + } +} + +.icon { + width: 1em; + height: 1em; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; +} + +.tpl-header { + z-index: 1000; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05); + background: #fff; + position: fixed; + top: 0; + width: 100%; + transition: all 0.4s ease-in-out; + //padding-left: 160px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.tpl-header-logo { + width: 240px; + height: 57px; + display: table; + text-align: center; + position: relative; + z-index: 1300; + a { + display: table-cell; + vertical-align: middle; + } + img { + width: 170px; + } +} + +.tpl-header-fluid { + height: 50px; + max-width: 1200px; + margin: 0 auto; +} + +.tpl-header-button { + float: left; + color: #333; + margin: 0 0 0 -20px; + border: 0; + border-radius: 0; + padding: 0 22px; + line-height: 50px; + background: #fff; + cursor: pointer; + &:hover { + background: #fff; + color: #999; + outline: none; + } +} + +.tpl-header-navbar { + color: #fff; + li { + float: left; + } + a { + line-height: 50px; + display: block; + padding: 0 16px; + position: relative; + color: #333; + -webkit-transition: all .3s; + transition: all .3s; + &:hover { + background: rgba(0, 0, 0, .025); + } + .item-feed-badge { + position: absolute; + top: 8px; + left: 25px; + padding: .25em .42em; + } + } +} + +ul { + &.tpl-dropdown-content { + padding: 10px; + margin-top: 0; + width: 300px; + //background-color: #2f3638; + //border: 1px solid #525e62; + border-radius: 0; + background: #fff; + border: 1px solid #ddd; + li { + float: none; + } + .tpl-dropdown-menu-notifications-title { + font-size: 12px; + float: left; + color: rgba(255, 255, 255, 0.7); + color: #616161; + } + .tpl-dropdown-menu-notifications-time { + float: right; + text-align: right; + color: rgba(255, 255, 255, 0.7); + font-size: 11px; + width: 50px; + margin-left: 10px; + } + .tpl-dropdown-menu-notifications { + &:last-child { + .tpl-dropdown-menu-notifications-item { + text-align: center; + border: none; + font-size: 12px; + i { + margin-left: -6px; + } + } + } + } + .tpl-dropdown-menu-messages { + &:last-child { + .tpl-dropdown-menu-messages-item { + text-align: center; + border: none; + font-size: 12px; + i { + margin-left: -6px; + } + } + } + } + .tpl-dropdown-menu-messages-item { + .menu-messages-content { + .menu-messages-content-time { + color: #96a5aa; + } + } + &:hover { + background-color: #f5f5f5; + } + } + } +} + +ul.tpl-dropdown-content:before, +ul.tpl-dropdown-content:after { + display: none; +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item { + padding: 12px; + //color: #fff; + line-height: 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.15); + border-bottom: 1px solid #eee; + color: #999; +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item:hover, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item:hover, +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item:focus, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item:focus { + background-color: #465154; + color: #fff; +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-ico, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-ico { + line-height: initial; + float: left; + width: 35px; + height: 35px; + border-radius: 50%; + margin-right: 10px; + margin-top: 6px; + overflow: hidden; +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-ico img, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-ico img { + width: 100%; + height: auto; + vertical-align: middle; +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-time, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-time { + float: right; + text-align: right; + color: rgba(255, 255, 255, 0.7); + font-size: 11px; + width: 40px; + margin-left: 10px; +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-content, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-content { + display: block; + font-size: 13px; + margin-left: 45px; + margin-right: 50px; +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .menu-messages-content .menu-messages-content-time, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .menu-messages-content .menu-messages-content-time { + margin-top: 3px; + color: rgba(255, 255, 255, 0.7); + font-size: 11px; +} + +.am-dimmer { + z-index: 1200; +} + +.am-modal { + z-index: 1300; +} + +.am-datepicker-dropdown { + z-index: 1400; +} + +.tpl-content-wrapper { + max-width: 1200px; + margin: 80px auto 0 auto; + transition: all 0.4s ease-in-out; + position: relative; + + // 左侧菜单栏 + .left-sidebar { + width: 200px; + flex-basis: 200px; + padding: 20px 0; + background: #fff; + border-radius: 3px; + li.sidebar-nav-link { + min-height: 48px; + line-height: 48px; + font-size: 14px; + position: relative; + a { + display: block; + padding-left: 25px; + color: #777; + &:hover { + color: #6af; + } + &.sidebar-nav-link-disabled { + cursor: default; + &:hover { + color: #777; + } + } + } + .sidebar-nav-link-logo { + margin-right: 3px; + width: 20px; + font-size: 16px; + display: inline-block; + } + .sidebar-third-nav-sub { + margin: 0; + a { + padding-left: 55px; + } + } + + &.active { + background: #f4f8ff; + &:before { + content: " "; + top: 0; + left: 0; + bottom: 0; + width: 4px; + background: #66a2ff; + position: absolute; + } + } + } + } + + // 内容区 + .row-content { + flex-basis: calc(100% - 200px); + padding: 0 15px 0 15px; + } + +} + +.page-header { + background: #424b4f; + margin-top: 0; + margin-bottom: 0; + padding: 40px 0; + border-bottom: 0; +} + +.container-fluid { + margin-top: 0; + margin-bottom: 0; + //padding: 40px 0; + border-bottom: 0; + padding-left: 20px; + padding-right: 20px; +} + +.row { + margin-right: -10px; + margin-left: -10px; +} + +.page-header-description { + margin-top: 4px; + margin-bottom: 0; + font-size: 14px; + //color: #e6e6e6; + color: #666; +} + +.page-header-heading { + font-size: 20px; + font-weight: 400; + color: #666; + .page-header-heading-ico { + font-size: 28px; + position: relative; + top: 3px; + } + small { + font-weight: normal; + line-height: 1; + color: #b3b3b3; + } +} + +.widget { + width: 100%; + min-height: 148px; + position: relative; + padding: 10px 20px 13px; + background-color: #fff; + color: #333; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05); + border-radius: 3px; +} + +.widget-body { + padding: 0 15px; + width: 100%; +} + +.widget-head { + width: 100%; + padding: 12px 20px; + border-bottom: 1px solid #eef1f5; + margin-top: 10px; + margin-bottom: 20px; + &:not(:first-child) { + margin-top: 40px; + } + .widget-title { + position: relative; + font-size: 1.5rem; + &::before { + content: ''; + position: absolute; + width: 4px; + height: 14px; + background: #00aeff; + top: 6px; + left: -12px; + } + } +} + +.tpl-table-black-operation { + a { + display: inline-block; + padding: 5px 6px; + font-size: 12px; + line-height: 12px; + border: 1px solid #36c6d3; + color: #36c6d3; + &:hover { + background: #36c6d3; + color: #fff; + } + &.tpl-table-black-operation-del { + border: 1px solid #e7505a; + color: #e7505a; + &:hover { + background: #e7505a; + color: #fff; + } + } + &.tpl-table-black-operation-green { + border: 1px solid #5eb95e; + color: #5eb95e; + &:hover { + background: #5eb95e; + color: #fff; + } + } + + } +} + +.tpl-page-state { + width: 100%; +} + +.tpl-page-state-title { + font-size: 40px; + font-weight: bold; + color: #838fa1; +} + +.tpl-page-state-content { + padding: 10px 0; +} + +.tpl-login { + width: 100%; +} + +.tpl-login-logo { + max-width: 159px; + height: 205px; + margin: 0 auto 20px auto; + //background: url(../img/logo.png) center no-repeat; +} + +.tpl-login-title { + width: 100%; + font-size: 24px; + color: #697882; + strong { + color: #39bae4; + } +} + +.tpl-login-content { + //width: 300px; + margin: 12% auto 0; + width: 500px; + padding: 40px 40px 25px; + background-color: #fff; + border-radius: 4px; +} + +.tpl-login-remember-me { + color: #b3b3b3; + font-size: 14px; + label { + position: relative; + top: -2px; + } +} + +.tpl-login-content-info { + color: #b3b3b3; + font-size: 14px; +} + +.cl-p { + padding: 0 !important; +} + +.tpl-table-line-img { + max-width: 100px; + padding: 2px; + border: 1px solid #ddd; +} + +.tpl-table-list-select { + text-align: right; +} + +.fc-button-group, +.fc button { + display: block; +} + +.tpl-header-search-box:hover, +.tpl-header-search-box:active .tpl-error-title { + color: #848c90; +} + +.tpl-error-title-info { + line-height: 30px; + font-size: 21px; + margin-top: 20px; + text-align: center; + color: #dce2ec; +} + +.tpl-error-btn { + background: #03a9f3; + border: 1px solid #03a9f3; + border-radius: 30px; + padding: 6px 20px 8px; +} + +.tpl-error-content { + margin-top: 20px; + margin-bottom: 20px; + font-size: 16px; + text-align: center; + color: #96a2b4; +} + +.tpl-calendar-box { + background: #fff; + border-radius: 4px; + padding: 20px; + .fc-event { + border-radius: 0; + background: #03a9f3; + border: 1px solid #14b0f6; + } + .fc-axis { + color: #868e8e; + } + .fc-unthemed { + .fc-today { + background: #eee; + } + } + .fc-more { + color: #868e8e; + } + .fc { + th { + color: #868e8e; + font-weight: normal; + font-size: 14px; + padding: 6px 0; + &.fc-widget-header { + background: #32c5d2 !important; + color: #ffffff; + font-size: 14px; + line-height: 20px; + padding: 7px 0; + text-transform: uppercase; + border: none !important; + a { + color: #fff; + } + } + } + } + .fc-center { + h2 { + color: #868e8e; + } + } + .fc-state-default { + background: #fff; + font-size: 14px; + color: #868e8e; + } + .fc-day-number { + color: #868e8e; + padding-right: 6px; + } +} + +.tpl-calendar-box .fc th, +.tpl-calendar-box .fc td, +.tpl-calendar-box .fc hr, +.tpl-calendar-box .fc thead, +.tpl-calendar-box .fc tbody, +.tpl-calendar-box .fc-row { + border-color: #eee !important; +} + +.tpl-pagination .am-disabled a, +.tpl-pagination li a { + color: #23abf0; + border-radius: 3px; + padding: 6px 12px; +} + +.tpl-pagination { + .am-active { + a { + background: #23abf0; + color: #fff; + border: 1px solid #23abf0; + padding: 6px 12px; + } + } +} + +.tpl-login-btn { + background-color: #32c5d2; + border: none; + padding: 10px 16px; + font-size: 14px; + line-height: 14px; + outline: none; +} + +.tpl-login-btn:hover, +.tpl-login-btn:active { + background: #22b2e1; + color: #fff; +} + +.tpl-form-line-form, +.tpl-form-border-form { +} + +.tpl-form-border-form input[type=number]:focus, +.tpl-form-border-form input[type=search]:focus, +.tpl-form-border-form input[type=text]:focus, +.tpl-form-border-form input[type=password]:focus, +.tpl-form-border-form input[type=datetime]:focus, +.tpl-form-border-form input[type=datetime-local]:focus, +.tpl-form-border-form input[type=date]:focus, +.tpl-form-border-form input[type=month]:focus, +.tpl-form-border-form input[type=time]:focus, +.tpl-form-border-form input[type=week]:focus, +.tpl-form-border-form input[type=email]:focus, +.tpl-form-border-form input[type=url]:focus, +.tpl-form-border-form input[type=tel]:focus, +.tpl-form-border-form input[type=color]:focus, +.tpl-form-border-form select:focus, +.tpl-form-border-form textarea:focus, +.am-form-field:focus { + -webkit-box-shadow: none; + box-shadow: none; +} + +.tpl-form-border-form input[type=number], +.tpl-form-border-form input[type=search], +.tpl-form-border-form input[type=text], +.tpl-form-border-form input[type=password], +.tpl-form-border-form input[type=datetime], +.tpl-form-border-form input[type=datetime-local], +.tpl-form-border-form input[type=date], +.tpl-form-border-form input[type=month], +.tpl-form-border-form input[type=time], +.tpl-form-border-form input[type=week], +.tpl-form-border-form input[type=email], +.tpl-form-border-form input[type=url], +.tpl-form-border-form input[type=tel], +.tpl-form-border-form input[type=color], +.tpl-form-border-form select, +.tpl-form-border-form textarea, +.am-form-field { + display: block; + width: 100%; + line-height: 1.42857; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + background: 0 0; + border: 1px solid #c2cad8; + text-indent: .5em; + border-radius: 0; + color: #555; + box-shadow: none; + padding-left: 0; + padding-right: 0; + font-size: 14px; +} + +.tpl-form-border-form .am-checkbox, +.tpl-form-border-form .am-checkbox-inline, +.tpl-form-border-form .am-form-label, +.tpl-form-border-form .am-radio, +.tpl-form-border-form .am-radio-inline { + margin-top: 0; + margin-bottom: 0; +} + +.tpl-form-border-form { + .am-form-group { + &:after { + clear: both; + } + } + .am-form-label { + padding-top: 5px; + font-size: 16px; + color: #888; + font-weight: inherit; + text-align: right; + .tpl-form-line-small-title { + color: #999; + font-size: 12px; + } + } +} + +.tpl-form-border-form .am-form-group:after, +.tpl-form-border-form .am-form-group:before { + content: " "; + display: table; +} + +.tpl-form-line-form input[type=number]:focus, +.tpl-form-line-form input[type=search]:focus, +.tpl-form-line-form input[type=text]:focus, +.tpl-form-line-form input[type=password]:focus, +.tpl-form-line-form input[type=datetime]:focus, +.tpl-form-line-form input[type=datetime-local]:focus, +.tpl-form-line-form input[type=date]:focus, +.tpl-form-line-form input[type=month]:focus, +.tpl-form-line-form input[type=time]:focus, +.tpl-form-line-form input[type=week]:focus, +.tpl-form-line-form input[type=email]:focus, +.tpl-form-line-form input[type=url]:focus, +.tpl-form-line-form input[type=tel]:focus, +.tpl-form-line-form input[type=color]:focus, +.tpl-form-line-form select:focus, +.tpl-form-line-form textarea:focus, +.am-form-field:focus { + -webkit-box-shadow: none; + box-shadow: none; +} + +.tpl-form-line-form input[type=number], +.tpl-form-line-form input[type=search], +.tpl-form-line-form input[type=text], +.tpl-form-line-form input[type=password], +.tpl-form-line-form input[type=datetime], +.tpl-form-line-form input[type=datetime-local], +.tpl-form-line-form input[type=date], +.tpl-form-line-form input[type=month], +.tpl-form-line-form input[type=time], +.tpl-form-line-form input[type=week], +.tpl-form-line-form input[type=email], +.tpl-form-line-form input[type=url], +.tpl-form-line-form input[type=tel], +.tpl-form-line-form input[type=color], +.tpl-form-line-form select, +.tpl-form-line-form textarea, +.am-form-field { + display: block; + width: 100%; + padding: 6px 5px; + line-height: 1.42857; + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + border: 0; + border-bottom: 1px solid #c2cad8; + color: #555; + box-shadow: none; + font-size: 14px; +} + +.am-form { + input[type=text][readonly] { + background: #f7f7f7 !important; + cursor: text; + } +} + +.tpl-form-line-form .am-checkbox, +.tpl-form-line-form .am-checkbox-inline, +.tpl-form-line-form .am-form-label, +.tpl-form-line-form .am-radio, +.tpl-form-line-form .am-radio-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: .8rem; + //color: #656565; +} + +.am-checkbox, .am-checkbox-inline, .am-radio, .am-radio-inline { + user-select: none; +} + +.tpl-form-line-form .am-checkbox .am-ucheck-icons, +.tpl-form-line-form .am-checkbox-inline .am-ucheck-icons, +.tpl-form-line-form .am-form-label .am-ucheck-icons, +.tpl-form-line-form .am-radio .am-ucheck-icons, +.tpl-form-line-form .am-radio-inline .am-ucheck-icons { + line-height: 40px; +} + +.am-ucheck-checkbox:checked + .am-ucheck-icons, +.am-ucheck-radio:checked + .am-ucheck-icons { + color: #5bb9ff; +} + +.tpl-form-line-form { + .am-form-group { + &:after { + clear: both; + } + } + .am-form-label { + padding-top: .8rem; + font-size: 1.34rem; + color: #656565; + font-weight: inherit; + text-align: right; + .tpl-form-line-small-title { + color: #8c8c8c; + font-size: 12px; + } + } + .am-form-error { + .am-form-label { + color: #dd514c; + } + } +} + +.tpl-form-line-form .am-form-group:after, +.tpl-form-line-form .am-form-group:before { + content: " "; + display: table; +} + +.tpl-amendment-echarts { + left: -17px; +} + +.tpl-user-card { + border: 1px solid #3598dc; + border-top: 2px solid #3598dc; + background: #3598dc; + color: #ffffff; + border-radius: 4px; +} + +.tpl-user-card-title { + font-size: 26px; + font-weight: 300; + margin-top: 25px; + margin-bottom: 10px; +} + +.achievement-subheading { + font-size: 12px; + margin-top: 0; + margin-bottom: 15px; +} + +.achievement-image { + border-radius: 50%; + margin-bottom: 22px; +} + +.achievement-description { + margin: 0; + font-size: 12px; +} + +.tpl-table-black { + color: #6d7279; + thead { + & > tr { + & > th { + font-size: 1.3rem; + padding: 6px; + } + } + } + tbody { + & > tr { + & > td { + font-size: 1.3rem; + padding: 7px 6px; + } + } + } + tfoot { + & > tr { + & > th { + font-size: 14px; + padding: 6px 0; + } + } + } +} + +.am-progress { + height: 12px; +} + +.am-progress-title { + font-size: 14px; + margin-bottom: 8px; +} + +.widget-fluctuation-tpl-btn { + margin-top: 6px; + display: block; + color: #fff; + font-size: 12px; + padding: 8px 14px; + outline: none; + background-color: #e7505a; + border: 1px solid #e7505a; + &:hover { + background: transparent; + color: #e7505a; + } +} + +.text-success { + color: #5eb95e; +} + +.widget-function { + a { + color: #838fa1; + &:hover { + color: #a7bdcd; + } + } +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item:hover, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item:hover { + background-color: #f5f5f5; +} + +ul.tpl-dropdown-content .tpl-dropdown-menu-notifications-item .tpl-dropdown-menu-notifications-time, +ul.tpl-dropdown-content .tpl-dropdown-menu-messages-item .tpl-dropdown-menu-notifications-time { + color: #999; +} + +.tpl-header { + &.active { + padding-left: 0; + } +} + +.tpl-header-logo { + background: #fff; + border-bottom: 1px solid #eee; +} + +.widget-color-green { + border: 1px solid #32c5d2; + border-top: 2px solid #32c5d2; + background: #32c5d2; + color: #ffffff; + .widget-fluctuation-period-text { + color: #fff; + } + .widget-head { + border-bottom: 1px solid #2bb8c4; + } + .widget-fluctuation-description-text { + color: #bbe7f6; + } + .widget-function { + a { + color: #42bde5; + &:hover { + color: #fff; + } + } + } +} + +@media screen and (max-width: 1024px) { + .left-sidebar { + left: -320px; + top: 50px; + } + .tpl-content-wrapper { + margin-left: 0 !important; + } + .tpl-sidebar-user-panel { + border-top: 1px solid #eee; + } + .tpl-header { + padding-left: 0; + } +} + +@media screen and (min-width: 641px) { + [class*=am-u-] { + padding-left: 10px; + padding-right: 10px; + } +} + +@media screen and (max-width: 641px) { + .tpl-error-title, + .tpl-login-title { + font-size: 20px; + } + .tpl-login-content { + width: 86%; + padding: 22px 30px 25px; + } + .tpl-header-search { + display: none; + } + ul.tpl-dropdown-content { + position: fixed; + width: 100%; + left: 0; + top: 112px; + right: 0; + } +} + +/* table */ +.am-table { + border-collapse: collapse; + font-size: 1.4rem; + & > thead { + & > tr { + & > th { + vertical-align: middle; + } + } + } + .item-title { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + text-align: left !important; + margin: 0; + white-space: normal; + } + p { + margin: 0; + } +} + +.am-btn-toolbar { + .am-btn-group { + & > .am-btn { + border-radius: 2px !important; + margin-right: 10px; + } + } +} + +/* 工具栏 */ +.page_toolbar { + .am-form-group { + max-width: 300px; + margin-left: .5rem; + .am-form-field, .am-btn { + border-radius: 2px; + outline: 0; + } + span, .am-selected-list { + font-size: 1.4rem; + } + } + + .am-input-group { + .am-form-field { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + } + .am-input-group-btn .am-btn { + background: #fff; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + &:hover { + color: #2589ff; + } + } + } +} + +/* 文字颜色 */ +.x-color-red { + color: #f00 !important; +} + +.x-color-green { + color: #4db14d !important; +} + +.x-color-yellow { + color: #fcb500 !important; +} + +.x-color-blue { + color: #259fdc !important; +} + +.x-color-c-gray-5f { + color: #828282; +} + +/* flex布局 */ + +.dis-flex { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.flex-box { + flex: 1; +} + +.flex-dir-row { + flex-direction: row; +} + +.flex-dir-column { + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.flex-x-center { + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.flex-x-between { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.flex-x-around { + justify-content: space-around; +} + +.flex-x-end { + -webkit-box-pack: end; + -ms-flex-pack: end; + -webkit-justify-content: flex-end; + justify-content: flex-end; +} + +.flex-y-center { + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; +} + +// 提示信息 +.tips { + .pre { + padding: 1rem; + background-color: #fbfdff; + border: 1px solid #dfeffd; + white-space: normal; + font-size: 1.22rem; + line-height: 1.6; + color: #259fdc; + p { + margin: .5rem 0; + } + a { + color: #00669a; + &:hover { + color: #000000; + } + } + } +} + +/* input滑块 */ +input[type=range] { + outline: none; + -webkit-appearance: none; + background: -webkit-linear-gradient(#61bd12, #61bd12) no-repeat, #ddd; + height: 3px; + border-radius: 5px; +} + +input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none; + position: relative; + height: 15px; + width: 15px; + border: 1px solid #d3d3d3; + border-radius: 50%; + background: #fff; + cursor: pointer; +} + +/* input滑块显示值 */ +.display-value { + display: inline-block; + margin-left: .8rem; +} + +/*分页*/ +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; + & > li { + display: inline; + } +} + +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 4px 12px; + line-height: 1.42857143; + color: #23abf0; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 3px; + margin: 0 2px; +} + +.theme-black .pagination > li > a, +.theme-black .pagination > li > span { + color: #fff; + padding: 6px 12px; + background: #3f4649; + border: none; +} + +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} + +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #23abf0; + border-color: #23abf0; +} + +.theme-black .pagination > .active > a, +.theme-black .pagination > .active > span, +.theme-black .pagination > .active > a:hover, +.theme-black .pagination > .active > span:hover, +.theme-black .pagination > .active > a:focus, +.theme-black .pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #23abf0; + border-color: #23abf0; +} + +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} + +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} + +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} + +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} + +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} + +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.pagination-total { + height: 32px; + margin-top: 20px; + margin-bottom: 20px; + .am-vertical-align-middle { + font-size: 1.4rem; + } +} + +/* 搜索表单*/ +.search-form { + margin-bottom: 20px; + label { + padding: 0 10px !important; + font-weight: normal; + font-size: 1.4rem; + text-align: left !important; + height: 32px; + line-height: 32px; + width: 90px; + } + input { + width: auto !important; + padding: 4px 8px !important; + line-height: 1.42857 !important; + font-size: 1.4rem; + &::placeholder { + font-size: 1.4rem; + } + } + .item { + float: left; + margin-right: 20px; + &:last-child { + background-color: #0c7cb5; + float: right; + } + } + .am-selected { + float: left; + .am-btn-default { + border: 1px solid #ccc; + } + } +} + +/* 表单页面*/ +.am-form { + .form-require { + &::after { + content: "*"; + color: #f00; + line-height: 18px; + } + } + .form-tab-group { + display: none; + &.active { + display: block; + } + } + .am-form-file { + padding-top: .4rem; + .upload-file { + font-size: 1.22rem; + padding: .5rem .9rem; + } + } + input[type=color] { + -webkit-appearance: square-button; + width: 5rem; + height: 2.2rem; + background-color: buttonface; + cursor: pointer; + border: 1px solid rgb(220, 220, 220); + border-image: initial; + padding: 0 2px; + } + input[type=text][disabled] { + background-color: initial; + } + .am-form-success { + .am-form-file { + [class*=icon-] { + color: #fff; + } + } + } + // 表单组 + .am-input-group { + .am-input-group-label { + background: none; + border: none; + } + } + // 文件上传域 + .am-form-file input[type=file] { + width: auto; + font-size: unset; + background: #000; + line-height: 110px; + } +} + +.am-field-valid:focus, +.am-form-success .am-form-field:focus { + box-shadow: none !important; + -webkit-box-shadow: none !important; +} + +.am-form-success { + label { + color: #656565 !important; + } + [class*=icon-] { + } +} + +.am-field-valid { + & + .am-ucheck-icons { + color: #999; + } +} + +.am-form small, +.help-block small { + color: #838fa1; + font-size: 1.2rem; +} diff --git a/web/assets/api/dealer-bg.png b/web/assets/api/dealer-bg.png new file mode 100644 index 0000000..dcff0af Binary files /dev/null and b/web/assets/api/dealer-bg.png differ diff --git a/web/assets/common/css/amazeui.datatables.min.css b/web/assets/common/css/amazeui.datatables.min.css new file mode 100644 index 0000000..f579a99 --- /dev/null +++ b/web/assets/common/css/amazeui.datatables.min.css @@ -0,0 +1 @@ +.am-datatable-hd{margin-bottom:10px}.am-datatable-hd label{font-weight:400}.am-datatable-filter{text-align:right}.am-datatable-filter input{margin-left:.5em}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after{position:absolute;top:50%;margin-top:-12px;right:8px;display:block;font-weight:400}table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after{position:absolute;top:50%;margin-top:-12px;right:8px;display:block;opacity:.5;font-weight:400}table.dataTable thead .sorting:after{opacity:.2;content:"\f0dc"}table.dataTable thead .sorting_asc:after{content:"\f15d"}table.dataTable thead .sorting_desc:after{content:"\f15e"}div.DTFC_LeftBodyWrapper table.dataTable thead .sorting:after,div.DTFC_LeftBodyWrapper table.dataTable thead .sorting_asc:after,div.DTFC_LeftBodyWrapper table.dataTable thead .sorting_desc:after,div.DTFC_RightBodyWrapper table.dataTable thead .sorting:after,div.DTFC_RightBodyWrapper table.dataTable thead .sorting_asc:after,div.DTFC_RightBodyWrapper table.dataTable thead .sorting_desc:after,div.dataTables_scrollBody table.dataTable thead .sorting:after,div.dataTables_scrollBody table.dataTable thead .sorting_asc:after,div.dataTables_scrollBody table.dataTable thead .sorting_desc:after{display:none}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}table.dataTable thead>tr>th{padding-right:30px}table.dataTable th:active{outline:none}table.dataTable.table-condensed thead>tr>th{padding-right:20px}table.dataTable.table-condensed thead .sorting:after,table.dataTable.table-condensed thead .sorting_asc:after,table.dataTable.table-condensed thead .sorting_desc:after{top:6px;right:6px}div.dataTables_scrollHead table{margin-bottom:0!important;border-bottom-left-radius:0;border-bottom-right-radius:0}div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child,div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child,div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child,div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child,div.dataTables_scrollHead table thead tr:last-child td:first-child,div.dataTables_scrollHead table thead tr:last-child th:first-child{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}div.dataTables_scrollBody table{border-top:none;margin-top:0!important;margin-bottom:0!important}div.DTFC_LeftBodyWrapper tbody tr:first-child td,div.DTFC_LeftBodyWrapper tbody tr:first-child th,div.DTFC_RightBodyWrapper tbody tr:first-child td,div.DTFC_RightBodyWrapper tbody tr:first-child th,div.dataTables_scrollBody tbody tr:first-child td,div.dataTables_scrollBody tbody tr:first-child th{border-top:none}div.dataTables_scrollFoot table{margin-top:0!important;border-top:none}table.table-bordered.dataTable{border-collapse:separate!important}table.table-bordered thead td,table.table-bordered thead th{border-left-width:0;border-top-width:0}table.table-bordered tbody td,table.table-bordered tbody th,table.table-bordered tfoot td,table.table-bordered tfoot th{border-left-width:0;border-bottom-width:0}table.table-bordered td:last-child,table.table-bordered th:last-child{border-right-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}.table.dataTable tbody tr.active td,.table.dataTable tbody tr.active th{background-color:#08c;color:#fff}.table.dataTable tbody tr.active:hover td,.table.dataTable tbody tr.active:hover th{background-color:#0075b0!important}.table.dataTable tbody tr.active td>a,.table.dataTable tbody tr.active th>a{color:#fff}.table-striped.dataTable tbody tr.active:nth-child(odd) td,.table-striped.dataTable tbody tr.active:nth-child(odd) th{background-color:#017ebc}table.DTTT_selectable tbody tr{cursor:pointer}div.DTTT .btn:hover{text-decoration:none!important}ul.DTTT_dropdown.dropdown-menu{z-index:2003}ul.DTTT_dropdown.dropdown-menu a{color:#333!important}ul.DTTT_dropdown.dropdown-menu li{position:relative}ul.DTTT_dropdown.dropdown-menu li:hover a{background-color:#08c;color:#fff!important}div.DTTT_collection_background{z-index:2002}div.DTTT_print_info,div.dataTables_processing{top:50%;left:50%;text-align:center;background-color:#fff}div.DTTT_print_info{color:#333;padding:10px 30px;opacity:.95;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,.5);box-shadow:0 3px 7px rgba(0,0,0,.5);position:fixed;width:400px;height:150px;margin-left:-200px;margin-top:-75px}div.DTTT_print_info h6{font-weight:400;font-size:28px;line-height:28px;margin:1em}div.DTTT_print_info p{font-size:14px;line-height:20px}div.dataTables_processing{position:absolute;width:100%;height:60px;margin-left:-50%;margin-top:-25px;padding-top:20px;padding-bottom:20px;font-size:1.2em;background:-webkit-gradient(linear,left top,right top,color-stop(0%,rgba(255,255,255,0)),color-stop(25%,rgba(255,255,255,.9)),color-stop(75%,rgba(255,255,255,.9)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0%,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%);background:-webkit-gradient(linear,left top,right top,from(rgba(255,255,255,0)),color-stop(25%,rgba(255,255,255,.9)),color-stop(75%,rgba(255,255,255,.9)),to(rgba(255,255,255,0)));background:linear-gradient(to right,rgba(255,255,255,0) 0%,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%)}div.DTFC_LeftHeadWrapper table{background-color:#fff}div.DTFC_LeftFootWrapper table{background-color:#fff;margin-bottom:0}div.DTFC_RightHeadWrapper table{background-color:#fff}div.DTFC_RightFootWrapper table,table.DTFC_Cloned tr.even{background-color:#fff;margin-bottom:0}div.DTFC_LeftHeadWrapper table,div.DTFC_RightHeadWrapper table{border-bottom:none!important;margin-bottom:0!important;border-top-right-radius:0!important;border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}div.DTFC_LeftBodyWrapper table,div.DTFC_RightBodyWrapper table{border-top:none;margin:0!important}div.DTFC_LeftFootWrapper table,div.DTFC_RightFootWrapper table{border-top:none;margin-top:0!important}div.FixedHeader_Cloned table{margin:0!important}.am-datatable-pager{margin-top:0;margin-bottom:0}.am-datatable-info{padding-top:6px;color:#555;font-size:1.4rem}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child{position:relative;padding-left:30px;cursor:pointer}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child:before{top:8px;left:4px;height:16px;width:16px;display:block;position:absolute;color:#fff;border:2px solid #fff;border-radius:16px;text-align:center;line-height:14px;-webkit-box-shadow:0 0 3px #444;box-shadow:0 0 3px #444;-webkit-box-sizing:content-box;box-sizing:content-box;content:'+';background-color:#31b131}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child.dataTables_empty:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child.dataTables_empty:before{display:none}table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td:first-child:before,table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th:first-child:before{content:'-';background-color:#d33333}table.dataTable.dtr-inline.collapsed>tbody>tr.child td:before{display:none}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td:first-child,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th:first-child{padding-left:27px}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td:first-child:before,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th:first-child:before{top:5px;left:4px;height:14px;width:14px;border-radius:14px;line-height:12px}table.dataTable.dtr-column>tbody>tr>td.control,table.dataTable.dtr-column>tbody>tr>th.control{position:relative;cursor:pointer}table.dataTable.dtr-column>tbody>tr>td.control:before,table.dataTable.dtr-column>tbody>tr>th.control:before{top:50%;left:50%;height:16px;width:16px;margin-top:-10px;margin-left:-10px;display:block;position:absolute;color:#fff;border:2px solid #fff;border-radius:16px;text-align:center;line-height:14px;-webkit-box-shadow:0 0 3px #666;box-shadow:0 0 3px #666;-webkit-box-sizing:content-box;box-sizing:content-box;content:'+';background-color:#5eb95e}table.dataTable.dtr-column>tbody>tr.parent td.control:before,table.dataTable.dtr-column>tbody>tr.parent th.control:before{content:'-';background-color:#dd514c}table.dataTable>tbody>tr.child{padding:.5em 1em}table.dataTable>tbody>tr.child:hover{background:0 0!important}table.dataTable>tbody>tr.child ul{display:inline-block;list-style-type:none;margin:0;padding:0}table.dataTable>tbody>tr.child ul li{border-bottom:1px solid #efefef;padding:.5em 0}table.dataTable>tbody>tr.child ul li:first-child{padding-top:0}table.dataTable>tbody>tr.child ul li:last-child{border-bottom:none}table.dataTable>tbody>tr.child span.dtr-title{display:inline-block;min-width:75px;font-weight:700} \ No newline at end of file diff --git a/web/assets/common/css/amazeui.min.css b/web/assets/common/css/amazeui.min.css new file mode 100644 index 0000000..39263eb --- /dev/null +++ b/web/assets/common/css/amazeui.min.css @@ -0,0 +1 @@ +/*! Amaze UI v2.7.2 | by Amaze UI Team | (c) 2016 AllMobilize, Inc. | Licensed under MIT | 2016-08-17T16:17:24+0800 */*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box}body,html{min-height:100%}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],script,template{display:none}a{background-color:transparent}a:focus{outline:thin dotted}a:active,a:hover{outline:0}a,ins{text-decoration:none}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{-webkit-box-sizing:border-box;box-sizing:border-box;vertical-align:middle;border:0}svg:not(:root){overflow:hidden}figure{margin:0}code,kbd,pre,samp{font-family:Monaco,Menlo,Consolas,"Courier New",FontAwesome,monospace;font-size:1em}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}input[type=checkbox],input[type=radio]{cursor:pointer;padding:0;-webkit-box-sizing:border-box;box-sizing:border-box}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top;resize:vertical}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{position:relative;background:#fff;font-family:"Segoe UI","Lucida Grande",Helvetica,Arial,"Microsoft YaHei",FreeSans,Arimo,"Droid Sans","wenquanyi micro hei","Hiragino Sans GB","Hiragino Sans GB W3",FontAwesome,sans-serif;font-weight:400;line-height:1.6;color:#333;font-size:1.6rem}body,button,input,select,textarea{text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-moz-font-feature-settings:"liga","kern"}@media only screen and (max-width:640px){body{word-wrap:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;-moz-hyphens:auto;hyphens:auto}}a{color:#0e90d2}a:focus,a:hover{color:#095f8a}a:focus{outline:thin dotted;outline:1px auto -webkit-focus-ring-color;outline-offset:-2px}ins{background:#ffa;color:#333}mark{background:#ffa;color:#333}abbr[title],dfn[title]{cursor:help}dfn[title]{border-bottom:1px dotted;font-style:normal}address,blockquote,dl,fieldset,figure,hr,ol,p,pre,ul{margin:0 0 1.6rem 0}*+address,*+blockquote,*+dl,*+fieldset,*+figure,*+hr,*+ol,*+p,*+pre,*+ul{margin-top:1.6rem}h1,h2,h3,h4,h5,h6{margin:0 0 1.6rem 0;font-weight:600;font-size:100%}h1{font-size:1.5em}h2{font-size:1.25em}*+h1,*+h2,*+h3,*+h4,*+h5,*+h6{margin-top:2em}ol,ul{padding-left:2em}ol>li>ol,ol>li>ul,ul>li>ol,ul>li>ul{margin:1em 0}dt{font-weight:700}dt+dd{margin-top:.5em}dd{margin-left:0}dd+dt{margin-top:1em}hr{display:block;padding:0;border:0;height:0;border-top:1px solid #eee;-webkit-box-sizing:content-box;box-sizing:content-box}address{font-style:normal}blockquote{padding-top:5px;padding-bottom:5px;padding-left:15px;border-left:4px solid #ddd;font-family:Georgia,"Times New Roman",Times,Kai,"Kaiti SC",KaiTi,BiauKai,FontAwesome,serif}blockquote small{display:block;color:#999;font-family:"Segoe UI","Lucida Grande",Helvetica,Arial,"Microsoft YaHei",FreeSans,Arimo,"Droid Sans","wenquanyi micro hei","Hiragino Sans GB","Hiragino Sans GB W3",FontAwesome,sans-serif;text-align:right}blockquote p:last-of-type{margin-bottom:0}iframe{border:0}button,input:not([type=radio]):not([type=checkbox]),select{vertical-align:middle}.am-scrollbar-measure{width:100px;height:100px;overflow:scroll;position:absolute;top:-9999px}.am-container{-webkit-box-sizing:border-box;box-sizing:border-box;margin-left:auto;margin-right:auto;padding-left:1rem;padding-right:1rem;width:100%;max-width:1000px}.am-container:after,.am-container:before{content:" ";display:table}.am-container:after{clear:both}@media only screen and (min-width:641px){.am-container{padding-left:1.5rem;padding-right:1.5rem}}.am-container>.am-g{width:auto;margin-left:-1rem;margin-right:-1rem}@media only screen and (min-width:641px){.am-container>.am-g{margin-left:-1.5rem;margin-right:-1.5rem}}.am-g{margin:0 auto;width:100%}.am-g:after,.am-g:before{content:" ";display:table}.am-g:after{clear:both}.am-g .am-g{margin-left:-1rem;margin-right:-1rem;width:auto}.am-g .am-g.am-g-collapse{margin-left:0;margin-right:0;width:auto}@media only screen and (min-width:641px){.am-g .am-g{margin-left:-1.5rem;margin-right:-1.5rem}}.am-g.am-g-collapse .am-g{margin-left:0;margin-right:0}.am-g-collapse [class*=am-u-]{padding-left:0;padding-right:0}.am-g-fixed{max-width:1000px}[class*=am-u-]{width:100%;padding-left:1rem;padding-right:1rem;float:left;position:relative}[class*=am-u-]+[class*=am-u-]:last-child{float:right}[class*=am-u-]+[class*=am-u-].am-u-end{float:left}@media only screen and (min-width:641px){[class*=am-u-]{padding-left:1.5rem;padding-right:1.5rem}}[class*=am-u-pull-]{left:auto}[class*=am-u-push-]{right:auto}@media only screen{.am-u-sm-1{width:8.33333333%}.am-u-sm-2{width:16.66666667%}.am-u-sm-3{width:25%}.am-u-sm-4{width:33.33333333%}.am-u-sm-5{width:41.66666667%}.am-u-sm-6{width:50%}.am-u-sm-7{width:58.33333333%}.am-u-sm-8{width:66.66666667%}.am-u-sm-9{width:75%}.am-u-sm-10{width:83.33333333%}.am-u-sm-11{width:91.66666667%}.am-u-sm-12{width:100%}.am-u-sm-pull-0{right:0}.am-u-sm-pull-1{right:8.33333333%}.am-u-sm-pull-2{right:16.66666667%}.am-u-sm-pull-3{right:25%}.am-u-sm-pull-4{right:33.33333333%}.am-u-sm-pull-5{right:41.66666667%}.am-u-sm-pull-6{right:50%}.am-u-sm-pull-7{right:58.33333333%}.am-u-sm-pull-8{right:66.66666667%}.am-u-sm-pull-9{right:75%}.am-u-sm-pull-10{right:83.33333333%}.am-u-sm-pull-11{right:91.66666667%}.am-u-sm-push-0{left:0}.am-u-sm-push-1{left:8.33333333%}.am-u-sm-push-2{left:16.66666667%}.am-u-sm-push-3{left:25%}.am-u-sm-push-4{left:33.33333333%}.am-u-sm-push-5{left:41.66666667%}.am-u-sm-push-6{left:50%}.am-u-sm-push-7{left:58.33333333%}.am-u-sm-push-8{left:66.66666667%}.am-u-sm-push-9{left:75%}.am-u-sm-push-10{left:83.33333333%}.am-u-sm-push-11{left:91.66666667%}.am-u-sm-offset-0{margin-left:0}.am-u-sm-offset-1{margin-left:8.33333333%}.am-u-sm-offset-2{margin-left:16.66666667%}.am-u-sm-offset-3{margin-left:25%}.am-u-sm-offset-4{margin-left:33.33333333%}.am-u-sm-offset-5{margin-left:41.66666667%}.am-u-sm-offset-6{margin-left:50%}.am-u-sm-offset-7{margin-left:58.33333333%}.am-u-sm-offset-8{margin-left:66.66666667%}.am-u-sm-offset-9{margin-left:75%}.am-u-sm-offset-10{margin-left:83.33333333%}.am-u-sm-offset-11{margin-left:91.66666667%}.am-u-sm-reset-order{margin-left:0;margin-right:0;left:auto;right:auto;float:left}[class*=am-u-].am-u-sm-centered{margin-left:auto;margin-right:auto;float:none}[class*=am-u-].am-u-sm-centered:last-child{float:none}[class*=am-u-].am-u-sm-uncentered{margin-left:0;margin-right:0;float:left}[class*=am-u-].am-u-sm-uncentered:last-child{float:left}}@media only screen and (min-width:641px){.am-u-md-1{width:8.33333333%}.am-u-md-2{width:16.66666667%}.am-u-md-3{width:25%}.am-u-md-4{width:33.33333333%}.am-u-md-5{width:41.66666667%}.am-u-md-6{width:50%}.am-u-md-7{width:58.33333333%}.am-u-md-8{width:66.66666667%}.am-u-md-9{width:75%}.am-u-md-10{width:83.33333333%}.am-u-md-11{width:91.66666667%}.am-u-md-12{width:100%}.am-u-md-pull-0{right:0}.am-u-md-pull-1{right:8.33333333%}.am-u-md-pull-2{right:16.66666667%}.am-u-md-pull-3{right:25%}.am-u-md-pull-4{right:33.33333333%}.am-u-md-pull-5{right:41.66666667%}.am-u-md-pull-6{right:50%}.am-u-md-pull-7{right:58.33333333%}.am-u-md-pull-8{right:66.66666667%}.am-u-md-pull-9{right:75%}.am-u-md-pull-10{right:83.33333333%}.am-u-md-pull-11{right:91.66666667%}.am-u-md-push-0{left:0}.am-u-md-push-1{left:8.33333333%}.am-u-md-push-2{left:16.66666667%}.am-u-md-push-3{left:25%}.am-u-md-push-4{left:33.33333333%}.am-u-md-push-5{left:41.66666667%}.am-u-md-push-6{left:50%}.am-u-md-push-7{left:58.33333333%}.am-u-md-push-8{left:66.66666667%}.am-u-md-push-9{left:75%}.am-u-md-push-10{left:83.33333333%}.am-u-md-push-11{left:91.66666667%}.am-u-md-offset-0{margin-left:0}.am-u-md-offset-1{margin-left:8.33333333%}.am-u-md-offset-2{margin-left:16.66666667%}.am-u-md-offset-3{margin-left:25%}.am-u-md-offset-4{margin-left:33.33333333%}.am-u-md-offset-5{margin-left:41.66666667%}.am-u-md-offset-6{margin-left:50%}.am-u-md-offset-7{margin-left:58.33333333%}.am-u-md-offset-8{margin-left:66.66666667%}.am-u-md-offset-9{margin-left:75%}.am-u-md-offset-10{margin-left:83.33333333%}.am-u-md-offset-11{margin-left:91.66666667%}.am-u-md-reset-order{margin-left:0;margin-right:0;left:auto;right:auto;float:left}[class*=am-u-].am-u-md-centered{margin-left:auto;margin-right:auto;float:none}[class*=am-u-].am-u-md-centered:last-child{float:none}[class*=am-u-].am-u-md-uncentered{margin-left:0;margin-right:0;float:left}[class*=am-u-].am-u-md-uncentered:last-child{float:left}}@media only screen and (min-width:1025px){.am-u-lg-1{width:8.33333333%}.am-u-lg-2{width:16.66666667%}.am-u-lg-3{width:25%}.am-u-lg-4{width:33.33333333%}.am-u-lg-5{width:41.66666667%}.am-u-lg-6{width:50%}.am-u-lg-7{width:58.33333333%}.am-u-lg-8{width:66.66666667%}.am-u-lg-9{width:75%}.am-u-lg-10{width:83.33333333%}.am-u-lg-11{width:91.66666667%}.am-u-lg-12{width:100%}.am-u-lg-pull-0{right:0}.am-u-lg-pull-1{right:8.33333333%}.am-u-lg-pull-2{right:16.66666667%}.am-u-lg-pull-3{right:25%}.am-u-lg-pull-4{right:33.33333333%}.am-u-lg-pull-5{right:41.66666667%}.am-u-lg-pull-6{right:50%}.am-u-lg-pull-7{right:58.33333333%}.am-u-lg-pull-8{right:66.66666667%}.am-u-lg-pull-9{right:75%}.am-u-lg-pull-10{right:83.33333333%}.am-u-lg-pull-11{right:91.66666667%}.am-u-lg-push-0{left:0}.am-u-lg-push-1{left:8.33333333%}.am-u-lg-push-2{left:16.66666667%}.am-u-lg-push-3{left:25%}.am-u-lg-push-4{left:33.33333333%}.am-u-lg-push-5{left:41.66666667%}.am-u-lg-push-6{left:50%}.am-u-lg-push-7{left:58.33333333%}.am-u-lg-push-8{left:66.66666667%}.am-u-lg-push-9{left:75%}.am-u-lg-push-10{left:83.33333333%}.am-u-lg-push-11{left:91.66666667%}.am-u-lg-offset-0{margin-left:0}.am-u-lg-offset-1{margin-left:8.33333333%}.am-u-lg-offset-2{margin-left:16.66666667%}.am-u-lg-offset-3{margin-left:25%}.am-u-lg-offset-4{margin-left:33.33333333%}.am-u-lg-offset-5{margin-left:41.66666667%}.am-u-lg-offset-6{margin-left:50%}.am-u-lg-offset-7{margin-left:58.33333333%}.am-u-lg-offset-8{margin-left:66.66666667%}.am-u-lg-offset-9{margin-left:75%}.am-u-lg-offset-10{margin-left:83.33333333%}.am-u-lg-offset-11{margin-left:91.66666667%}.am-u-lg-reset-order{margin-left:0;margin-right:0;left:auto;right:auto;float:left}[class*=am-u-].am-u-lg-centered{margin-left:auto;margin-right:auto;float:none}[class*=am-u-].am-u-lg-centered:last-child{float:none}[class*=am-u-].am-u-lg-uncentered{margin-left:0;margin-right:0;float:left}[class*=am-u-].am-u-lg-uncentered:last-child{float:left}}[class*=am-avg-]{display:block;padding:0;margin:0;list-style:none}[class*=am-avg-]:after,[class*=am-avg-]:before{content:" ";display:table}[class*=am-avg-]:after{clear:both}[class*=am-avg-]>li{display:block;height:auto;float:left}@media only screen{.am-avg-sm-1>li{width:100%}.am-avg-sm-1>li:nth-of-type(n){clear:none}.am-avg-sm-1>li:nth-of-type(1n+1){clear:both}.am-avg-sm-2>li{width:50%}.am-avg-sm-2>li:nth-of-type(n){clear:none}.am-avg-sm-2>li:nth-of-type(2n+1){clear:both}.am-avg-sm-3>li{width:33.33333333%}.am-avg-sm-3>li:nth-of-type(n){clear:none}.am-avg-sm-3>li:nth-of-type(3n+1){clear:both}.am-avg-sm-4>li{width:25%}.am-avg-sm-4>li:nth-of-type(n){clear:none}.am-avg-sm-4>li:nth-of-type(4n+1){clear:both}.am-avg-sm-5>li{width:20%}.am-avg-sm-5>li:nth-of-type(n){clear:none}.am-avg-sm-5>li:nth-of-type(5n+1){clear:both}.am-avg-sm-6>li{width:16.66666667%}.am-avg-sm-6>li:nth-of-type(n){clear:none}.am-avg-sm-6>li:nth-of-type(6n+1){clear:both}.am-avg-sm-7>li{width:14.28571429%}.am-avg-sm-7>li:nth-of-type(n){clear:none}.am-avg-sm-7>li:nth-of-type(7n+1){clear:both}.am-avg-sm-8>li{width:12.5%}.am-avg-sm-8>li:nth-of-type(n){clear:none}.am-avg-sm-8>li:nth-of-type(8n+1){clear:both}.am-avg-sm-9>li{width:11.11111111%}.am-avg-sm-9>li:nth-of-type(n){clear:none}.am-avg-sm-9>li:nth-of-type(9n+1){clear:both}.am-avg-sm-10>li{width:10%}.am-avg-sm-10>li:nth-of-type(n){clear:none}.am-avg-sm-10>li:nth-of-type(10n+1){clear:both}.am-avg-sm-11>li{width:9.09090909%}.am-avg-sm-11>li:nth-of-type(n){clear:none}.am-avg-sm-11>li:nth-of-type(11n+1){clear:both}.am-avg-sm-12>li{width:8.33333333%}.am-avg-sm-12>li:nth-of-type(n){clear:none}.am-avg-sm-12>li:nth-of-type(12n+1){clear:both}}@media only screen and (min-width:641px){.am-avg-md-1>li{width:100%}.am-avg-md-1>li:nth-of-type(n){clear:none}.am-avg-md-1>li:nth-of-type(1n+1){clear:both}.am-avg-md-2>li{width:50%}.am-avg-md-2>li:nth-of-type(n){clear:none}.am-avg-md-2>li:nth-of-type(2n+1){clear:both}.am-avg-md-3>li{width:33.33333333%}.am-avg-md-3>li:nth-of-type(n){clear:none}.am-avg-md-3>li:nth-of-type(3n+1){clear:both}.am-avg-md-4>li{width:25%}.am-avg-md-4>li:nth-of-type(n){clear:none}.am-avg-md-4>li:nth-of-type(4n+1){clear:both}.am-avg-md-5>li{width:20%}.am-avg-md-5>li:nth-of-type(n){clear:none}.am-avg-md-5>li:nth-of-type(5n+1){clear:both}.am-avg-md-6>li{width:16.66666667%}.am-avg-md-6>li:nth-of-type(n){clear:none}.am-avg-md-6>li:nth-of-type(6n+1){clear:both}.am-avg-md-7>li{width:14.28571429%}.am-avg-md-7>li:nth-of-type(n){clear:none}.am-avg-md-7>li:nth-of-type(7n+1){clear:both}.am-avg-md-8>li{width:12.5%}.am-avg-md-8>li:nth-of-type(n){clear:none}.am-avg-md-8>li:nth-of-type(8n+1){clear:both}.am-avg-md-9>li{width:11.11111111%}.am-avg-md-9>li:nth-of-type(n){clear:none}.am-avg-md-9>li:nth-of-type(9n+1){clear:both}.am-avg-md-10>li{width:10%}.am-avg-md-10>li:nth-of-type(n){clear:none}.am-avg-md-10>li:nth-of-type(10n+1){clear:both}.am-avg-md-11>li{width:9.09090909%}.am-avg-md-11>li:nth-of-type(n){clear:none}.am-avg-md-11>li:nth-of-type(11n+1){clear:both}.am-avg-md-12>li{width:8.33333333%}.am-avg-md-12>li:nth-of-type(n){clear:none}.am-avg-md-12>li:nth-of-type(12n+1){clear:both}}@media only screen and (min-width:1025px){.am-avg-lg-1>li{width:100%}.am-avg-lg-1>li:nth-of-type(n){clear:none}.am-avg-lg-1>li:nth-of-type(1n+1){clear:both}.am-avg-lg-2>li{width:50%}.am-avg-lg-2>li:nth-of-type(n){clear:none}.am-avg-lg-2>li:nth-of-type(2n+1){clear:both}.am-avg-lg-3>li{width:33.33333333%}.am-avg-lg-3>li:nth-of-type(n){clear:none}.am-avg-lg-3>li:nth-of-type(3n+1){clear:both}.am-avg-lg-4>li{width:25%}.am-avg-lg-4>li:nth-of-type(n){clear:none}.am-avg-lg-4>li:nth-of-type(4n+1){clear:both}.am-avg-lg-5>li{width:20%}.am-avg-lg-5>li:nth-of-type(n){clear:none}.am-avg-lg-5>li:nth-of-type(5n+1){clear:both}.am-avg-lg-6>li{width:16.66666667%}.am-avg-lg-6>li:nth-of-type(n){clear:none}.am-avg-lg-6>li:nth-of-type(6n+1){clear:both}.am-avg-lg-7>li{width:14.28571429%}.am-avg-lg-7>li:nth-of-type(n){clear:none}.am-avg-lg-7>li:nth-of-type(7n+1){clear:both}.am-avg-lg-8>li{width:12.5%}.am-avg-lg-8>li:nth-of-type(n){clear:none}.am-avg-lg-8>li:nth-of-type(8n+1){clear:both}.am-avg-lg-9>li{width:11.11111111%}.am-avg-lg-9>li:nth-of-type(n){clear:none}.am-avg-lg-9>li:nth-of-type(9n+1){clear:both}.am-avg-lg-10>li{width:10%}.am-avg-lg-10>li:nth-of-type(n){clear:none}.am-avg-lg-10>li:nth-of-type(10n+1){clear:both}.am-avg-lg-11>li{width:9.09090909%}.am-avg-lg-11>li:nth-of-type(n){clear:none}.am-avg-lg-11>li:nth-of-type(11n+1){clear:both}.am-avg-lg-12>li{width:8.33333333%}.am-avg-lg-12>li:nth-of-type(n){clear:none}.am-avg-lg-12>li:nth-of-type(12n+1){clear:both}}code,kbd,pre,samp{font-family:Monaco,Menlo,Consolas,"Courier New",FontAwesome,monospace}code{padding:2px 4px;font-size:1.3rem;color:#c7254e;background-color:#f8f8f8;white-space:nowrap;border-radius:0}pre{display:block;padding:1rem;margin:1rem 0;font-size:1.3rem;line-height:1.6;word-break:break-all;word-wrap:break-word;color:#555;background-color:#f8f8f8;border:1px solid #dedede;border-radius:0}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.am-pre-scrollable{max-height:24rem;overflow-y:scroll}.am-btn{display:inline-block;margin-bottom:0;padding:.5em 1em;vertical-align:middle;font-size:1.6rem;font-weight:400;line-height:1.2;text-align:center;white-space:nowrap;background-image:none;border:1px solid transparent;border-radius:0;cursor:pointer;outline:0;-webkit-appearance:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:background-color .3s ease-out,border-color .3s ease-out;transition:background-color .3s ease-out,border-color .3s ease-out}.am-btn:active:focus,.am-btn:focus{outline:thin dotted;outline:1px auto -webkit-focus-ring-color;outline-offset:-2px}.am-btn:focus,.am-btn:hover{color:#444;text-decoration:none}.am-btn.am-active,.am-btn:active{background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.15);box-shadow:inset 0 3px 5px rgba(0,0,0,.15)}.am-btn.am-disabled,.am-btn[disabled],fieldset[disabled] .am-btn{pointer-events:none;border-color:transparent;cursor:not-allowed;opacity:.45;-webkit-box-shadow:none;box-shadow:none}.am-btn.am-round{border-radius:1000px}.am-btn.am-radius{border-radius:2px}.am-btn-default{color:#444;background-color:#e6e6e6;border-color:#e6e6e6}a.am-btn-default:visited{color:#444}.am-btn-default.am-active,.am-btn-default:active,.am-btn-default:focus,.am-btn-default:hover,.am-dropdown.am-active .am-btn-default.am-dropdown-toggle{color:#444;border-color:#c7c7c7}.am-btn-default:focus,.am-btn-default:hover{background-color:#d4d4d4}.am-btn-default.am-active,.am-btn-default:active,.am-dropdown.am-active .am-btn-default.am-dropdown-toggle{background-image:none;background-color:#c2c2c2}.am-btn-default.am-disabled,.am-btn-default.am-disabled.am-active,.am-btn-default.am-disabled:active,.am-btn-default.am-disabled:focus,.am-btn-default.am-disabled:hover,.am-btn-default[disabled],.am-btn-default[disabled].am-active,.am-btn-default[disabled]:active,.am-btn-default[disabled]:focus,.am-btn-default[disabled]:hover,fieldset[disabled] .am-btn-default,fieldset[disabled] .am-btn-default.am-active,fieldset[disabled] .am-btn-default:active,fieldset[disabled] .am-btn-default:focus,fieldset[disabled] .am-btn-default:hover{background-color:#e6e6e6;border-color:#e6e6e6}.am-btn-group .am-btn-default,.am-btn-group-stacked .am-btn-default{border-color:#d9d9d9}.am-btn-primary{color:#fff;background-color:#0e90d2;border-color:#0e90d2}a.am-btn-primary:visited{color:#fff}.am-btn-primary.am-active,.am-btn-primary:active,.am-btn-primary:focus,.am-btn-primary:hover,.am-dropdown.am-active .am-btn-primary.am-dropdown-toggle{color:#fff;border-color:#0a6999}.am-btn-primary:focus,.am-btn-primary:hover{background-color:#0c79b1}.am-btn-primary.am-active,.am-btn-primary:active,.am-dropdown.am-active .am-btn-primary.am-dropdown-toggle{background-image:none;background-color:#0a628f}.am-btn-primary.am-disabled,.am-btn-primary.am-disabled.am-active,.am-btn-primary.am-disabled:active,.am-btn-primary.am-disabled:focus,.am-btn-primary.am-disabled:hover,.am-btn-primary[disabled],.am-btn-primary[disabled].am-active,.am-btn-primary[disabled]:active,.am-btn-primary[disabled]:focus,.am-btn-primary[disabled]:hover,fieldset[disabled] .am-btn-primary,fieldset[disabled] .am-btn-primary.am-active,fieldset[disabled] .am-btn-primary:active,fieldset[disabled] .am-btn-primary:focus,fieldset[disabled] .am-btn-primary:hover{background-color:#0e90d2;border-color:#0e90d2}.am-btn-group .am-btn-primary,.am-btn-group-stacked .am-btn-primary{border-color:#0c80ba}.am-btn-secondary{color:#fff;background-color:#3bb4f2;border-color:#3bb4f2}a.am-btn-secondary:visited{color:#fff}.am-btn-secondary.am-active,.am-btn-secondary:active,.am-btn-secondary:focus,.am-btn-secondary:hover,.am-dropdown.am-active .am-btn-secondary.am-dropdown-toggle{color:#fff;border-color:#0f9ae0}.am-btn-secondary:focus,.am-btn-secondary:hover{background-color:#19a7f0}.am-btn-secondary.am-active,.am-btn-secondary:active,.am-dropdown.am-active .am-btn-secondary.am-dropdown-toggle{background-image:none;background-color:#0e93d7}.am-btn-secondary.am-disabled,.am-btn-secondary.am-disabled.am-active,.am-btn-secondary.am-disabled:active,.am-btn-secondary.am-disabled:focus,.am-btn-secondary.am-disabled:hover,.am-btn-secondary[disabled],.am-btn-secondary[disabled].am-active,.am-btn-secondary[disabled]:active,.am-btn-secondary[disabled]:focus,.am-btn-secondary[disabled]:hover,fieldset[disabled] .am-btn-secondary,fieldset[disabled] .am-btn-secondary.am-active,fieldset[disabled] .am-btn-secondary:active,fieldset[disabled] .am-btn-secondary:focus,fieldset[disabled] .am-btn-secondary:hover{background-color:#3bb4f2;border-color:#3bb4f2}.am-btn-group .am-btn-secondary,.am-btn-group-stacked .am-btn-secondary{border-color:#23abf0}.am-btn-warning{color:#fff;background-color:#F37B1D;border-color:#F37B1D}a.am-btn-warning:visited{color:#fff}.am-btn-warning.am-active,.am-btn-warning:active,.am-btn-warning:focus,.am-btn-warning:hover,.am-dropdown.am-active .am-btn-warning.am-dropdown-toggle{color:#fff;border-color:#c85e0b}.am-btn-warning:focus,.am-btn-warning:hover{background-color:#e0690c}.am-btn-warning.am-active,.am-btn-warning:active,.am-dropdown.am-active .am-btn-warning.am-dropdown-toggle{background-image:none;background-color:#be590a}.am-btn-warning.am-disabled,.am-btn-warning.am-disabled.am-active,.am-btn-warning.am-disabled:active,.am-btn-warning.am-disabled:focus,.am-btn-warning.am-disabled:hover,.am-btn-warning[disabled],.am-btn-warning[disabled].am-active,.am-btn-warning[disabled]:active,.am-btn-warning[disabled]:focus,.am-btn-warning[disabled]:hover,fieldset[disabled] .am-btn-warning,fieldset[disabled] .am-btn-warning.am-active,fieldset[disabled] .am-btn-warning:active,fieldset[disabled] .am-btn-warning:focus,fieldset[disabled] .am-btn-warning:hover{background-color:#F37B1D;border-color:#F37B1D}.am-btn-group .am-btn-warning,.am-btn-group-stacked .am-btn-warning{border-color:#ea6e0c}.am-btn-danger{color:#fff;background-color:#dd514c;border-color:#dd514c}a.am-btn-danger:visited{color:#fff}.am-btn-danger.am-active,.am-btn-danger:active,.am-btn-danger:focus,.am-btn-danger:hover,.am-dropdown.am-active .am-btn-danger.am-dropdown-toggle{color:#fff;border-color:#c62b26}.am-btn-danger:focus,.am-btn-danger:hover{background-color:#d7342e}.am-btn-danger.am-active,.am-btn-danger:active,.am-dropdown.am-active .am-btn-danger.am-dropdown-toggle{background-image:none;background-color:#be2924}.am-btn-danger.am-disabled,.am-btn-danger.am-disabled.am-active,.am-btn-danger.am-disabled:active,.am-btn-danger.am-disabled:focus,.am-btn-danger.am-disabled:hover,.am-btn-danger[disabled],.am-btn-danger[disabled].am-active,.am-btn-danger[disabled]:active,.am-btn-danger[disabled]:focus,.am-btn-danger[disabled]:hover,fieldset[disabled] .am-btn-danger,fieldset[disabled] .am-btn-danger.am-active,fieldset[disabled] .am-btn-danger:active,fieldset[disabled] .am-btn-danger:focus,fieldset[disabled] .am-btn-danger:hover{background-color:#dd514c;border-color:#dd514c}.am-btn-group .am-btn-danger,.am-btn-group-stacked .am-btn-danger{border-color:#d93c37}.am-btn-success{color:#fff;background-color:#5eb95e;border-color:#5eb95e}a.am-btn-success:visited{color:#fff}.am-btn-success.am-active,.am-btn-success:active,.am-btn-success:focus,.am-btn-success:hover,.am-dropdown.am-active .am-btn-success.am-dropdown-toggle{color:#fff;border-color:#429842}.am-btn-success:focus,.am-btn-success:hover{background-color:#4aaa4a}.am-btn-success.am-active,.am-btn-success:active,.am-dropdown.am-active .am-btn-success.am-dropdown-toggle{background-image:none;background-color:#3f913f}.am-btn-success.am-disabled,.am-btn-success.am-disabled.am-active,.am-btn-success.am-disabled:active,.am-btn-success.am-disabled:focus,.am-btn-success.am-disabled:hover,.am-btn-success[disabled],.am-btn-success[disabled].am-active,.am-btn-success[disabled]:active,.am-btn-success[disabled]:focus,.am-btn-success[disabled]:hover,fieldset[disabled] .am-btn-success,fieldset[disabled] .am-btn-success.am-active,fieldset[disabled] .am-btn-success:active,fieldset[disabled] .am-btn-success:focus,fieldset[disabled] .am-btn-success:hover{background-color:#5eb95e;border-color:#5eb95e}.am-btn-group .am-btn-success,.am-btn-group-stacked .am-btn-success{border-color:#4db14d}.am-btn-link{color:#0e90d2;font-weight:400;cursor:pointer;border-radius:0}.am-btn-link,.am-btn-link:active,.am-btn-link[disabled],fieldset[disabled] .am-btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.am-btn-link,.am-btn-link:active,.am-btn-link:focus,.am-btn-link:hover{border-color:transparent}.am-btn-link:focus,.am-btn-link:hover{color:#095f8a;text-decoration:underline;background-color:transparent}.am-btn-link[disabled]:focus,.am-btn-link[disabled]:hover,fieldset[disabled] .am-btn-link:focus,fieldset[disabled] .am-btn-link:hover{color:#999;text-decoration:none}.am-btn-xs{font-size:1.2rem}.am-btn-sm{font-size:1.4rem}.am-btn-lg{font-size:1.8rem}.am-btn-xl{font-size:2rem}.am-btn-block{display:block;width:100%;padding-left:0;padding-right:0}.am-btn-block+.am-btn-block{margin-top:5px}input[type=button].am-btn-block,input[type=reset].am-btn-block,input[type=submit].am-btn-block{width:100%}.am-btn.am-btn-loading .am-icon-spin{margin-right:5px}table{max-width:100%;background-color:transparent;empty-cells:show}table code{white-space:normal}th{text-align:left}.am-table{width:100%;margin-bottom:1.6rem;border-spacing:0;border-collapse:separate}.am-table>tbody>tr>td,.am-table>tbody>tr>th,.am-table>tfoot>tr>td,.am-table>tfoot>tr>th,.am-table>thead>tr>td,.am-table>thead>tr>th{padding:.7rem;line-height:1.6;vertical-align:top;border-top:1px solid #ddd}.am-table>thead>tr>th{vertical-align:bottom;border-bottom:1px solid #ddd}.am-table>caption+thead>tr:first-child>td,.am-table>caption+thead>tr:first-child>th,.am-table>colgroup+thead>tr:first-child>td,.am-table>colgroup+thead>tr:first-child>th,.am-table>thead:first-child>tr:first-child>td,.am-table>thead:first-child>tr:first-child>th{border-top:0}.am-table>tbody+tbody tr:first-child td{border-top:2px solid #ddd}.am-table-bordered{border:1px solid #ddd;border-left:none}.am-table-bordered>tbody>tr>td,.am-table-bordered>tbody>tr>th,.am-table-bordered>tfoot>tr>td,.am-table-bordered>tfoot>tr>th,.am-table-bordered>thead>tr>td,.am-table-bordered>thead>tr>th{border-left:1px solid #ddd}.am-table-bordered>tbody>tr:first-child>td,.am-table-bordered>tbody>tr:first-child>th{border-top:none}.am-table-bordered>thead+tbody>tr:first-child>td,.am-table-bordered>thead+tbody>tr:first-child>th{border-top:1px solid #ddd}.am-table-radius{border:1px solid #ddd;border-radius:2px}.am-table-radius>thead>tr:first-child>td:first-child,.am-table-radius>thead>tr:first-child>th:first-child{border-top-left-radius:2px;border-left:none}.am-table-radius>thead>tr:first-child>td:last-child,.am-table-radius>thead>tr:first-child>th:last-child{border-top-right-radius:2px;border-right:none}.am-table-radius>tbody>tr>td:first-child,.am-table-radius>tbody>tr>th:first-child{border-left:none}.am-table-radius>tbody>tr>td:last-child,.am-table-radius>tbody>tr>th:last-child{border-right:none}.am-table-radius>tbody>tr:last-child>td,.am-table-radius>tbody>tr:last-child>th{border-bottom:none}.am-table-radius>tbody>tr:last-child>td:first-child,.am-table-radius>tbody>tr:last-child>th:first-child{border-bottom-left-radius:2px}.am-table-radius>tbody>tr:last-child>td:last-child,.am-table-radius>tbody>tr:last-child>th:last-child{border-bottom-right-radius:2px}.am-table-striped>tbody>tr:nth-child(odd)>td,.am-table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.am-table-hover>tbody>tr:hover>td,.am-table-hover>tbody>tr:hover>th{background-color:#e9e9e9}.am-table-compact>tbody>tr>td,.am-table-compact>tbody>tr>th,.am-table-compact>tfoot>tr>td,.am-table-compact>tfoot>tr>th,.am-table-compact>thead>tr>td,.am-table-compact>thead>tr>th{padding:.4rem}.am-table-centered>tbody>tr>td,.am-table-centered>tbody>tr>th,.am-table-centered>tfoot>tr>td,.am-table-centered>tfoot>tr>th,.am-table-centered>thead>tr>td,.am-table-centered>thead>tr>th{text-align:center}.am-table>tbody>tr.am-active>td,.am-table>tbody>tr.am-active>th,.am-table>tbody>tr>td.am-active,.am-table>tbody>tr>th.am-active,.am-table>tfoot>tr.am-active>td,.am-table>tfoot>tr.am-active>th,.am-table>tfoot>tr>td.am-active,.am-table>tfoot>tr>th.am-active,.am-table>thead>tr.am-active>td,.am-table>thead>tr.am-active>th,.am-table>thead>tr>td.am-active,.am-table>thead>tr>th.am-active{background-color:#ffd}.am-table>tbody>tr.am-disabled>td,.am-table>tbody>tr.am-disabled>th,.am-table>tbody>tr>td.am-disabled,.am-table>tbody>tr>th.am-disabled,.am-table>tfoot>tr.am-disabled>td,.am-table>tfoot>tr.am-disabled>th,.am-table>tfoot>tr>td.am-disabled,.am-table>tfoot>tr>th.am-disabled,.am-table>thead>tr.am-disabled>td,.am-table>thead>tr.am-disabled>th,.am-table>thead>tr>td.am-disabled,.am-table>thead>tr>th.am-disabled{color:#999}.am-table>tbody>tr.am-primary>td,.am-table>tbody>tr.am-primary>th,.am-table>tbody>tr>td.am-primary,.am-table>tbody>tr>th.am-primary,.am-table>tfoot>tr.am-primary>td,.am-table>tfoot>tr.am-primary>th,.am-table>tfoot>tr>td.am-primary,.am-table>tfoot>tr>th.am-primary,.am-table>thead>tr.am-primary>td,.am-table>thead>tr.am-primary>th,.am-table>thead>tr>td.am-primary,.am-table>thead>tr>th.am-primary{color:#0b76ac;background-color:rgba(14,144,210,.115)}.am-table>tbody>tr.am-success>td,.am-table>tbody>tr.am-success>th,.am-table>tbody>tr>td.am-success,.am-table>tbody>tr>th.am-success,.am-table>tfoot>tr.am-success>td,.am-table>tfoot>tr.am-success>th,.am-table>tfoot>tr>td.am-success,.am-table>tfoot>tr>th.am-success,.am-table>thead>tr.am-success>td,.am-table>thead>tr.am-success>th,.am-table>thead>tr>td.am-success,.am-table>thead>tr>th.am-success{color:#5eb95e;background-color:rgba(94,185,94,.115)}.am-table>tbody>tr.am-warning>td,.am-table>tbody>tr.am-warning>th,.am-table>tbody>tr>td.am-warning,.am-table>tbody>tr>th.am-warning,.am-table>tfoot>tr.am-warning>td,.am-table>tfoot>tr.am-warning>th,.am-table>tfoot>tr>td.am-warning,.am-table>tfoot>tr>th.am-warning,.am-table>thead>tr.am-warning>td,.am-table>thead>tr.am-warning>th,.am-table>thead>tr>td.am-warning,.am-table>thead>tr>th.am-warning{color:#F37B1D;background-color:rgba(243,123,29,.115)}.am-table>tbody>tr.am-danger>td,.am-table>tbody>tr.am-danger>th,.am-table>tbody>tr>td.am-danger,.am-table>tbody>tr>th.am-danger,.am-table>tfoot>tr.am-danger>td,.am-table>tfoot>tr.am-danger>th,.am-table>tfoot>tr>td.am-danger,.am-table>tfoot>tr>th.am-danger,.am-table>thead>tr.am-danger>td,.am-table>thead>tr.am-danger>th,.am-table>thead>tr>td.am-danger,.am-table>thead>tr>th.am-danger{color:#dd514c;background-color:rgba(221,81,76,.115)}fieldset{border:none}legend{display:block;width:100%;margin-bottom:2rem;font-size:2rem;line-height:inherit;color:#333;border-bottom:1px solid #e5e5e5;padding-bottom:.5rem}label{display:inline-block;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}select[multiple],select[size]{height:auto}select optgroup{font-size:inherit;font-style:inherit;font-family:inherit}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted;outline:1px auto -webkit-focus-ring-color;outline-offset:-2px}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}output{display:block;padding-top:1.6rem;font-size:1.6rem;line-height:1.6;color:#555;vertical-align:middle}.am-form input[type=number],.am-form input[type=search],.am-form input[type=text],.am-form input[type=password],.am-form input[type=datetime],.am-form input[type=datetime-local],.am-form input[type=date],.am-form input[type=month],.am-form input[type=time],.am-form input[type=week],.am-form input[type=email],.am-form input[type=url],.am-form input[type=tel],.am-form input[type=color],.am-form select,.am-form textarea,.am-form-field{display:block;width:100%;padding:.5em;font-size:1.6rem;line-height:1.2;color:#555;vertical-align:middle;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:0;-webkit-appearance:none;-webkit-transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out}.am-form input[type=number]:focus,.am-form input[type=search]:focus,.am-form input[type=text]:focus,.am-form input[type=password]:focus,.am-form input[type=datetime]:focus,.am-form input[type=datetime-local]:focus,.am-form input[type=date]:focus,.am-form input[type=month]:focus,.am-form input[type=time]:focus,.am-form input[type=week]:focus,.am-form input[type=email]:focus,.am-form input[type=url]:focus,.am-form input[type=tel]:focus,.am-form input[type=color]:focus,.am-form select:focus,.am-form textarea:focus,.am-form-field:focus{outline:0}.am-form input[type=number]:focus,.am-form input[type=search]:focus,.am-form input[type=text]:focus,.am-form input[type=password]:focus,.am-form input[type=datetime]:focus,.am-form input[type=datetime-local]:focus,.am-form input[type=date]:focus,.am-form input[type=month]:focus,.am-form input[type=time]:focus,.am-form input[type=week]:focus,.am-form input[type=email]:focus,.am-form input[type=url]:focus,.am-form input[type=tel]:focus,.am-form input[type=color]:focus,.am-form select:focus,.am-form textarea:focus,.am-form-field:focus{background-color:#fefffe;border-color:#3bb4f2;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 5px rgba(59,180,242,.3);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 5px rgba(59,180,242,.3)}.am-form input[type=number]::-webkit-input-placeholder,.am-form input[type=search]::-webkit-input-placeholder,.am-form input[type=text]::-webkit-input-placeholder,.am-form input[type=password]::-webkit-input-placeholder,.am-form input[type=datetime]::-webkit-input-placeholder,.am-form input[type=datetime-local]::-webkit-input-placeholder,.am-form input[type=date]::-webkit-input-placeholder,.am-form input[type=month]::-webkit-input-placeholder,.am-form input[type=time]::-webkit-input-placeholder,.am-form input[type=week]::-webkit-input-placeholder,.am-form input[type=email]::-webkit-input-placeholder,.am-form input[type=url]::-webkit-input-placeholder,.am-form input[type=tel]::-webkit-input-placeholder,.am-form input[type=color]::-webkit-input-placeholder,.am-form select::-webkit-input-placeholder,.am-form textarea::-webkit-input-placeholder,.am-form-field::-webkit-input-placeholder{color:#999}.am-form input[type=number]::-moz-placeholder,.am-form input[type=search]::-moz-placeholder,.am-form input[type=text]::-moz-placeholder,.am-form input[type=password]::-moz-placeholder,.am-form input[type=datetime]::-moz-placeholder,.am-form input[type=datetime-local]::-moz-placeholder,.am-form input[type=date]::-moz-placeholder,.am-form input[type=month]::-moz-placeholder,.am-form input[type=time]::-moz-placeholder,.am-form input[type=week]::-moz-placeholder,.am-form input[type=email]::-moz-placeholder,.am-form input[type=url]::-moz-placeholder,.am-form input[type=tel]::-moz-placeholder,.am-form input[type=color]::-moz-placeholder,.am-form select::-moz-placeholder,.am-form textarea::-moz-placeholder,.am-form-field::-moz-placeholder{color:#999}.am-form input[type=number]:-ms-input-placeholder,.am-form input[type=search]:-ms-input-placeholder,.am-form input[type=text]:-ms-input-placeholder,.am-form input[type=password]:-ms-input-placeholder,.am-form input[type=datetime]:-ms-input-placeholder,.am-form input[type=datetime-local]:-ms-input-placeholder,.am-form input[type=date]:-ms-input-placeholder,.am-form input[type=month]:-ms-input-placeholder,.am-form input[type=time]:-ms-input-placeholder,.am-form input[type=week]:-ms-input-placeholder,.am-form input[type=email]:-ms-input-placeholder,.am-form input[type=url]:-ms-input-placeholder,.am-form input[type=tel]:-ms-input-placeholder,.am-form input[type=color]:-ms-input-placeholder,.am-form select:-ms-input-placeholder,.am-form textarea:-ms-input-placeholder,.am-form-field:-ms-input-placeholder{color:#999}.am-form input[type=number]::placeholder,.am-form input[type=search]::placeholder,.am-form input[type=text]::placeholder,.am-form input[type=password]::placeholder,.am-form input[type=datetime]::placeholder,.am-form input[type=datetime-local]::placeholder,.am-form input[type=date]::placeholder,.am-form input[type=month]::placeholder,.am-form input[type=time]::placeholder,.am-form input[type=week]::placeholder,.am-form input[type=email]::placeholder,.am-form input[type=url]::placeholder,.am-form input[type=tel]::placeholder,.am-form input[type=color]::placeholder,.am-form select::placeholder,.am-form textarea::placeholder,.am-form-field::placeholder{color:#999}.am-form input[type=number]::-moz-placeholder,.am-form input[type=search]::-moz-placeholder,.am-form input[type=text]::-moz-placeholder,.am-form input[type=password]::-moz-placeholder,.am-form input[type=datetime]::-moz-placeholder,.am-form input[type=datetime-local]::-moz-placeholder,.am-form input[type=date]::-moz-placeholder,.am-form input[type=month]::-moz-placeholder,.am-form input[type=time]::-moz-placeholder,.am-form input[type=week]::-moz-placeholder,.am-form input[type=email]::-moz-placeholder,.am-form input[type=url]::-moz-placeholder,.am-form input[type=tel]::-moz-placeholder,.am-form input[type=color]::-moz-placeholder,.am-form select::-moz-placeholder,.am-form textarea::-moz-placeholder,.am-form-field::-moz-placeholder{opacity:1}.am-form input[type=number][disabled],.am-form input[type=number][readonly],.am-form input[type=search][disabled],.am-form input[type=search][readonly],.am-form input[type=text][disabled],.am-form input[type=text][readonly],.am-form input[type=password][disabled],.am-form input[type=password][readonly],.am-form input[type=datetime][disabled],.am-form input[type=datetime][readonly],.am-form input[type=datetime-local][disabled],.am-form input[type=datetime-local][readonly],.am-form input[type=date][disabled],.am-form input[type=date][readonly],.am-form input[type=month][disabled],.am-form input[type=month][readonly],.am-form input[type=time][disabled],.am-form input[type=time][readonly],.am-form input[type=week][disabled],.am-form input[type=week][readonly],.am-form input[type=email][disabled],.am-form input[type=email][readonly],.am-form input[type=url][disabled],.am-form input[type=url][readonly],.am-form input[type=tel][disabled],.am-form input[type=tel][readonly],.am-form input[type=color][disabled],.am-form input[type=color][readonly],.am-form select[disabled],.am-form select[readonly],.am-form textarea[disabled],.am-form textarea[readonly],.am-form-field[disabled],.am-form-field[readonly],fieldset[disabled] .am-form input[type=number],fieldset[disabled] .am-form input[type=search],fieldset[disabled] .am-form input[type=text],fieldset[disabled] .am-form input[type=password],fieldset[disabled] .am-form input[type=datetime],fieldset[disabled] .am-form input[type=datetime-local],fieldset[disabled] .am-form input[type=date],fieldset[disabled] .am-form input[type=month],fieldset[disabled] .am-form input[type=time],fieldset[disabled] .am-form input[type=week],fieldset[disabled] .am-form input[type=email],fieldset[disabled] .am-form input[type=url],fieldset[disabled] .am-form input[type=tel],fieldset[disabled] .am-form input[type=color],fieldset[disabled] .am-form select,fieldset[disabled] .am-form textarea,fieldset[disabled] .am-form-field{cursor:not-allowed;background-color:#eee}.am-form input[type=number].am-radius,.am-form input[type=search].am-radius,.am-form input[type=text].am-radius,.am-form input[type=password].am-radius,.am-form input[type=datetime].am-radius,.am-form input[type=datetime-local].am-radius,.am-form input[type=date].am-radius,.am-form input[type=month].am-radius,.am-form input[type=time].am-radius,.am-form input[type=week].am-radius,.am-form input[type=email].am-radius,.am-form input[type=url].am-radius,.am-form input[type=tel].am-radius,.am-form input[type=color].am-radius,.am-form select.am-radius,.am-form textarea.am-radius,.am-form-field.am-radius{border-radius:2px}.am-form input[type=number].am-round,.am-form input[type=search].am-round,.am-form input[type=text].am-round,.am-form input[type=password].am-round,.am-form input[type=datetime].am-round,.am-form input[type=datetime-local].am-round,.am-form input[type=date].am-round,.am-form input[type=month].am-round,.am-form input[type=time].am-round,.am-form input[type=week].am-round,.am-form input[type=email].am-round,.am-form input[type=url].am-round,.am-form input[type=tel].am-round,.am-form input[type=color].am-round,.am-form select.am-round,.am-form textarea.am-round,.am-form-field.am-round{border-radius:1000px}.am-form select[multiple],.am-form select[size],.am-form textarea{height:auto}.am-form select{-webkit-appearance:none!important;-moz-appearance:none!important;-webkit-border-radius:0;background:#fff url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMTJweCIgeT0iMHB4IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzcHgiIHZpZXdCb3g9IjAgMCA2IDMiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYgMyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBvbHlnb24gcG9pbnRzPSI1Ljk5MiwwIDIuOTkyLDMgLTAuMDA4LDAgIi8+PC9zdmc+) no-repeat 100% center}.am-form select[multiple=multiple]{background-image:none}.am-form input[type=datetime-local],.am-form input[type=date],input[type=datetime-local].am-form-field,input[type=date].am-form-field{height:37px}.am-form input[type=datetime-local].am-input-sm,.am-form input[type=date].am-input-sm,input[type=datetime-local].am-form-field.am-input-sm,input[type=date].am-form-field.am-input-sm{height:32px}.am-form input[type=datetime-local] .am-input-lg,.am-form input[type=date] .am-input-lg,input[type=datetime-local].am-form-field .am-input-lg,input[type=date].am-form-field .am-input-lg{height:41px}.am-form-help{display:block;margin-top:5px;margin-bottom:10px;color:#999;font-size:1.3rem}.am-form-group{margin-bottom:1.5rem}.am-form-file{position:relative;overflow:hidden}.am-form-file input[type=file]{position:absolute;left:0;top:0;z-index:1;width:100%;opacity:0;cursor:pointer;font-size:50rem}.am-checkbox,.am-radio{display:block;min-height:1.92rem;margin-top:10px;margin-bottom:10px;padding-left:20px;vertical-align:middle}.am-checkbox label,.am-radio label{display:inline;margin-bottom:0;font-weight:400;cursor:pointer}.am-checkbox input[type=checkbox],.am-checkbox-inline input[type=checkbox],.am-radio input[type=radio],.am-radio-inline input[type=radio]{float:left;margin-left:-20px;outline:0}.am-checkbox+.am-checkbox,.am-radio+.am-radio{margin-top:-5px}.am-checkbox-inline,.am-radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.am-checkbox-inline+.am-checkbox-inline,.am-radio-inline+.am-radio-inline{margin-top:0;margin-left:10px}.am-checkbox-inline[disabled],.am-checkbox[disabled],.am-radio-inline[disabled],.am-radio[disabled],fieldset[disabled] .am-checkbox,fieldset[disabled] .am-checkbox-inline,fieldset[disabled] .am-radio,fieldset[disabled] .am-radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.am-form-warning .am-checkbox,.am-form-warning .am-checkbox-inline,.am-form-warning .am-form-help,.am-form-warning .am-form-label,.am-form-warning .am-radio,.am-form-warning .am-radio-inline,.am-form-warning label{color:#F37B1D}.am-form-warning [class*=icon-]{color:#F37B1D}.am-form-warning .am-form-field{border-color:#F37B1D!important;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.am-form-warning .am-form-field:focus{background-color:#fefffe;border-color:#d2620b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 5px #f8b47e!important;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 5px #f8b47e!important}.am-form-error .am-checkbox,.am-form-error .am-checkbox-inline,.am-form-error .am-form-help,.am-form-error .am-form-label,.am-form-error .am-radio,.am-form-error .am-radio-inline,.am-form-error label{color:#dd514c}.am-form-error [class*=icon-]{color:#dd514c}.am-field-error,.am-form-error .am-form-field{border-color:#dd514c!important;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.am-field-error:focus,.am-form-error .am-form-field:focus{background-color:#fefffe;border-color:#cf2d27;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 5px #eda4a2!important;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 5px #eda4a2!important}.am-form-success .am-checkbox,.am-form-success .am-checkbox-inline,.am-form-success .am-form-help,.am-form-success .am-form-label,.am-form-success .am-radio,.am-form-success .am-radio-inline,.am-form-success label{color:#5eb95e}.am-form-success [class*=icon-]{color:#5eb95e}.am-field-valid,.am-form-success .am-form-field{border-color:#5eb95e!important;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.am-field-valid:focus,.am-form-success .am-form-field:focus{background-color:#fefffe;border-color:#459f45;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 5px #a5d8a5!important;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 5px #a5d8a5!important}.am-form-horizontal .am-checkbox,.am-form-horizontal .am-checkbox-inline,.am-form-horizontal .am-form-label,.am-form-horizontal .am-radio,.am-form-horizontal .am-radio-inline{margin-top:0;margin-bottom:0;padding-top:.6em}.am-form-horizontal .am-form-group:after,.am-form-horizontal .am-form-group:before{content:" ";display:table}.am-form-horizontal .am-form-group:after{clear:both}@media only screen and (min-width:641px){.am-form-horizontal .am-form-label{text-align:right}}@media only screen and (min-width:641px){.am-form-inline .am-form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.am-form-inline .am-form-field{display:inline-block;width:auto;vertical-align:middle}.am-form-inline .am-input-group{display:inline-table;vertical-align:middle}.am-form-inline .am-input-group .am-form-label,.am-form-inline .am-input-group .am-input-group-btn,.am-form-inline .am-input-group .am-input-group-label{width:auto}.am-form-inline .am-input-group>.am-form-field{width:100%}.am-form-inline .am-form-label{margin-bottom:0;vertical-align:middle}.am-form-inline .am-checkbox,.am-form-inline .am-radio{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.am-form-inline .am-checkbox input[type=checkbox],.am-form-inline .am-radio input[type=radio]{float:none;margin-left:0}}.am-input-sm{font-size:1.4rem!important}.am-input-lg{font-size:1.8rem!important}.am-form-group-sm .am-checkbox,.am-form-group-sm .am-form-field,.am-form-group-sm .am-form-label,.am-form-group-sm .am-radio{font-size:1.4rem!important}.am-form-group-lg .am-checkbox,.am-form-group-lg .am-form-field,.am-form-group-lg .am-form-label,.am-form-group-lg .am-radio{font-size:1.8rem!important}.am-form-group-lg input[type=checkbox],.am-form-group-lg input[type=radio]{margin-top:7px}.am-form-icon{position:relative}.am-form-icon .am-form-field{padding-left:1.75em!important}.am-form-icon [class*=am-icon-]{position:absolute;left:.5em;top:50%;display:block;margin-top:-.5em;line-height:1;z-index:2}.am-form-icon label~[class*=am-icon-]{top:70%}.am-form-feedback{position:relative}.am-form-feedback .am-form-field{padding-left:.5em!important;padding-right:1.75em!important}.am-form-feedback [class*=am-icon-]{right:.5em;left:auto}.am-form-horizontal .am-form-feedback [class*=am-icon-]{right:1.6em}.am-form-set{margin-bottom:1.5rem;padding:0}.am-form-set>input{position:relative;top:-1px;border-radius:0!important}.am-form-set>input:focus{z-index:2}.am-form-set>input:first-child{top:1px;border-top-right-radius:0!important;border-top-left-radius:0!important}.am-form-set>input:last-child{top:-2px;border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.am-img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:2px;line-height:1.6;background-color:#fff;border:1px solid #ddd;border-radius:0;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.am-img-thumbnail.am-radius{border-radius:2px}.am-img-responsive{display:block;max-width:100%;height:auto}.am-nav{margin-bottom:0;padding:0;list-style:none}.am-nav:after,.am-nav:before{content:" ";display:table}.am-nav:after{clear:both}.am-nav>li{position:relative;display:block}.am-nav>li+li{margin-top:5px}.am-nav>li+.am-nav-header{margin-top:1em}.am-nav>li>a{position:relative;display:block;padding:.4em 1em;border-radius:0}.am-nav>li>a:focus,.am-nav>li>a:hover{text-decoration:none;background-color:#eee}.am-nav>li.am-active>a,.am-nav>li.am-active>a:focus,.am-nav>li.am-active>a:hover{color:#fff;background-color:#0e90d2;cursor:default}.am-nav>li.am-disabled>a{color:#999}.am-nav>li.am-disabled>a:focus,.am-nav>li.am-disabled>a:hover{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.am-nav-header{padding:.4em 1em;text-transform:uppercase;font-weight:700;font-size:100%;color:#555}.am-nav-divider{margin:15px 1em!important;border-top:1px solid #ddd;-webkit-box-shadow:0 1px 0 #fff;box-shadow:0 1px 0 #fff}.am-nav-pills>li{float:left}.am-nav-pills>li+li{margin-left:5px;margin-top:0}.am-nav-tabs{border-bottom:1px solid #ddd}.am-nav-tabs>li{float:left;margin-bottom:-1px}.am-nav-tabs>li+li{margin-top:0}.am-nav-tabs>li>a{margin-right:5px;line-height:1.6;border:1px solid transparent;border-radius:0}.am-nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.am-nav-tabs>li.am-active>a,.am-nav-tabs>li.am-active>a:focus,.am-nav-tabs>li.am-active>a:hover{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.am-nav-tabs.am-nav-justify{border-bottom:0}.am-nav-tabs.am-nav-justify>li>a{margin-right:0;border-bottom:1px solid #ddd;border-radius:0}.am-nav-tabs.am-nav-justify>.am-active>a,.am-nav-tabs.am-nav-justify>.am-active>a:focus,.am-nav-tabs.am-nav-justify>.am-active>a:hover{border-bottom-color:#fff}.am-nav-justify{width:100%}.am-nav-justify>li{float:none;display:table-cell;width:1%}.am-nav-justify>li>a{text-align:center;margin-bottom:0}.lte9 .am-nav-justify>li{display:table-cell;width:1%}.am-topbar{position:relative;min-height:50px;margin-bottom:1.6rem;background:#f8f8f8;border-width:0 0 1px;border-style:solid;border-color:#ddd;color:#666}.am-topbar:after,.am-topbar:before{content:" ";display:table}.am-topbar:after{clear:both}.am-topbar a{color:#666}.am-topbar-brand{margin:0}@media only screen and (min-width:641px){.am-topbar-brand{float:left}}.am-topbar-brand a:hover{color:#4d4d4d}.am-topbar-collapse{width:100%;overflow-x:visible;padding:10px;clear:both;-webkit-overflow-scrolling:touch}.am-topbar-collapse:after,.am-topbar-collapse:before{content:" ";display:table}.am-topbar-collapse:after{clear:both}.am-topbar-collapse.am-in{overflow-y:auto}@media only screen and (min-width:641px){.am-topbar-collapse{margin-top:0;padding:0;width:auto;clear:none}.am-topbar-collapse.am-collapse{display:block!important;height:auto!important;padding:0;overflow:visible!important}.am-topbar-collapse.am-in{overflow-y:visible}}.am-topbar-brand{padding:0 10px;float:left;font-size:1.8rem;height:50px;line-height:50px}.am-topbar-toggle{position:relative;float:right;margin-right:10px}@media only screen and (min-width:641px){.am-topbar-toggle{display:none}}@media only screen and (max-width:640px){.am-topbar-nav{margin-bottom:8px}.am-topbar-nav>li{float:none}}@media only screen and (max-width:640px){.am-topbar-nav>li+li{margin-left:0;margin-top:5px}}@media only screen and (min-width:641px){.am-topbar-nav{float:left}.am-topbar-nav>li>a{position:relative;line-height:50px;padding:0 10px}.am-topbar-nav>li>a:after{position:absolute;left:50%;margin-left:-7px;bottom:-1px;content:"";display:inline-block;width:0;height:0;vertical-align:middle;border-bottom:7px solid #f8f8f8;border-right:7px solid transparent;border-left:7px solid transparent;border-top:0 dotted;-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);opacity:0;-webkit-transition:opacity .1s;transition:opacity .1s}.am-topbar-nav>li>a:hover:after{opacity:1;border-bottom-color:#666}.am-topbar-nav>li.am-dropdown>a:after{display:none}.am-topbar-nav>li.am-active>a,.am-topbar-nav>li.am-active>a:focus,.am-topbar-nav>li.am-active>a:hover{border-radius:0;color:#0e90d2;background:0 0}.am-topbar-nav>li.am-active>a:after{opacity:1;border-bottom-color:#0e90d2}}@media only screen and (max-width:640px){.am-topbar-collapse .am-dropdown.am-active .am-dropdown-content{float:none;position:relative;width:100%}}@media only screen and (min-width:641px){.am-topbar-left{float:left}.am-topbar-right{float:right;margin-right:10px}}@media only screen and (max-width:640px){.am-topbar-form .am-form-group{margin-bottom:5px}}@media only screen and (min-width:641px){.am-topbar-form{padding:0 10px;margin-top:8px}.am-topbar-form .am-form-group+.am-btn{margin-left:5px}}.am-topbar-btn{margin-top:8px}@media only screen and (max-width:640px){.am-topbar-collapse .am-btn,.am-topbar-collapse .am-topbar-btn{display:block;width:100%}}.am-topbar-inverse{background-color:#0e90d2;border-color:#0b6fa2;color:#eee}.am-topbar-inverse a{color:#eee}.am-topbar-inverse .am-topbar-brand a{color:#fff}.am-topbar-inverse .am-topbar-brand a:focus,.am-topbar-inverse .am-topbar-brand a:hover{color:#fff;background-color:transparent}.am-topbar-inverse .am-topbar-nav>li>a{color:#eee}.am-topbar-inverse .am-topbar-nav>li>a:focus,.am-topbar-inverse .am-topbar-nav>li>a:hover{color:#fff;background-color:rgba(0,0,0,.05)}.am-topbar-inverse .am-topbar-nav>li>a:focus:after,.am-topbar-inverse .am-topbar-nav>li>a:hover:after{border-bottom-color:#0b6fa2}.am-topbar-inverse .am-topbar-nav>li>a:after{border-bottom-color:#0e90d2}.am-topbar-inverse .am-topbar-nav>li.am-active>a,.am-topbar-inverse .am-topbar-nav>li.am-active>a:focus,.am-topbar-inverse .am-topbar-nav>li.am-active>a:hover{color:#fff;background-color:rgba(0,0,0,.1)}.am-topbar-inverse .am-topbar-nav>li.am-active>a:after,.am-topbar-inverse .am-topbar-nav>li.am-active>a:focus:after,.am-topbar-inverse .am-topbar-nav>li.am-active>a:hover:after{border-bottom-color:#fff}.am-topbar-inverse .am-topbar-nav>li .disabled>a,.am-topbar-inverse .am-topbar-nav>li .disabled>a:focus,.am-topbar-inverse .am-topbar-nav>li .disabled>a:hover{color:#444;background-color:transparent}.am-topbar-fixed-bottom,.am-topbar-fixed-top{position:fixed;right:0;left:0;z-index:1000;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.am-topbar-fixed-top{top:0}.am-topbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.am-with-topbar-fixed-top{padding-top:51px}.am-with-topbar-fixed-bottom{padding-bottom:51px}@media only screen and (max-width:640px){.am-topbar-fixed-bottom .am-topbar-collapse{position:absolute;bottom:100%;margin-bottom:1px;background-color:#f8f8f8}.am-topbar-fixed-bottom .am-topbar-collapse .am-dropdown-content:after,.am-topbar-fixed-bottom .am-topbar-collapse .am-dropdown-content:before{display:none}.am-topbar-fixed-bottom.am-topbar-inverse .am-topbar-collapse{background-color:#0e90d2}}.am-breadcrumb{padding:.7em .5em;margin-bottom:2rem;list-style:none;background-color:transparent;border-radius:0;font-size:85%}.am-breadcrumb>li{display:inline-block}.am-breadcrumb>li [class*=am-icon-]:before{color:#999;margin-right:5px}.am-breadcrumb>li+li:before{content:"\00bb\00a0";padding:0 8px;color:#ccc}.am-breadcrumb>.am-active{color:#999}.am-breadcrumb-slash>li+li:before{content:"/\00a0"}.am-pagination{padding-left:0;margin:1.5rem 0;list-style:none;color:#999;text-align:left}.am-pagination:after,.am-pagination:before{content:" ";display:table}.am-pagination:after{clear:both}.am-pagination>li{display:inline-block}.am-pagination>li>a,.am-pagination>li>span{position:relative;display:block;padding:.5em 1em;text-decoration:none;line-height:1.2;background-color:#fff;border:1px solid #ddd;border-radius:0;margin-bottom:5px;margin-right:5px}.am-pagination>li:last-child>a,.am-pagination>li:last-child>span{margin-right:0}.am-pagination>li>a:focus,.am-pagination>li>a:hover,.am-pagination>li>span:focus,.am-pagination>li>span:hover{background-color:#eee}.am-pagination>.am-active>a,.am-pagination>.am-active>a:focus,.am-pagination>.am-active>a:hover,.am-pagination>.am-active>span,.am-pagination>.am-active>span:focus,.am-pagination>.am-active>span:hover{z-index:2;color:#fff;background-color:#0e90d2;border-color:#0e90d2;cursor:default}.am-pagination>.am-disabled>a,.am-pagination>.am-disabled>a:focus,.am-pagination>.am-disabled>a:hover,.am-pagination>.am-disabled>span,.am-pagination>.am-disabled>span:focus,.am-pagination>.am-disabled>span:hover{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed;pointer-events:none}.am-pagination .am-pagination-prev{float:left}.am-pagination .am-pagination-prev a{border-radius:0}.am-pagination .am-pagination-next{float:right}.am-pagination .am-pagination-next a{border-radius:0}.am-pagination-centered{text-align:center}.am-pagination-right{text-align:right}[class*=am-animation-]{-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}@media screen{.cssanimations [data-am-scrollspy*=animation]{opacity:0}}.am-animation-fade{-webkit-animation-name:am-fade;animation-name:am-fade;-webkit-animation-duration:.8s;animation-duration:.8s;-webkit-animation-timing-function:linear;animation-timing-function:linear}.am-animation-scale-up{-webkit-animation-name:am-scale-up;animation-name:am-scale-up}.am-animation-scale-down{-webkit-animation-name:am-scale-down;animation-name:am-scale-down}.am-animation-slide-top{-webkit-animation-name:am-slide-top;animation-name:am-slide-top}.am-animation-slide-bottom{-webkit-animation-name:am-slide-bottom;animation-name:am-slide-bottom}.am-animation-slide-left{-webkit-animation-name:am-slide-left;animation-name:am-slide-left}.am-animation-slide-right{-webkit-animation-name:am-slide-right;animation-name:am-slide-right}.am-animation-slide-top-fixed{-webkit-animation-name:am-slide-top-fixed;animation-name:am-slide-top-fixed}.am-animation-shake{-webkit-animation-name:am-shake;animation-name:am-shake}.am-animation-spin{-webkit-animation:am-spin 2s infinite linear;animation:am-spin 2s infinite linear}.am-animation-left-spring{-webkit-animation:am-left-spring .3s ease-in-out;animation:am-left-spring .3s ease-in-out}.am-animation-right-spring{-webkit-animation:am-right-spring .3s ease-in-out;animation:am-right-spring .3s ease-in-out}.am-animation-reverse{-webkit-animation-direction:reverse;animation-direction:reverse}.am-animation-paused{-webkit-animation-play-state:paused!important;animation-play-state:paused!important}.am-animation-delay-1{-webkit-animation-delay:1s;animation-delay:1s}.am-animation-delay-2{-webkit-animation-delay:2s;animation-delay:2s}.am-animation-delay-3{-webkit-animation-delay:3s;animation-delay:3s}.am-animation-delay-4{-webkit-animation-delay:4s;animation-delay:4s}.am-animation-delay-5{-webkit-animation-delay:5s;animation-delay:5s}.am-animation-delay-6{-webkit-animation-delay:6s;animation-delay:6s}@-webkit-keyframes am-fade{0%{opacity:0}100%{opacity:1}}@keyframes am-fade{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes am-scale-up{0%{opacity:0;-webkit-transform:scale(.2);transform:scale(.2)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes am-scale-up{0%{opacity:0;-webkit-transform:scale(.2);transform:scale(.2)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes am-scale-down{0%{opacity:0;-webkit-transform:scale(1.8);transform:scale(1.8)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes am-scale-down{0%{opacity:0;-webkit-transform:scale(1.8);transform:scale(1.8)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes am-slide-top{0%{opacity:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes am-slide-top{0%{opacity:0;-webkit-transform:translateY(-100%);transform:translateY(-100%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes am-slide-bottom{0%{opacity:0;-webkit-transform:translateY(100%);transform:translateY(100%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes am-slide-bottom{0%{opacity:0;-webkit-transform:translateY(100%);transform:translateY(100%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes am-slide-left{0%{opacity:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}100%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes am-slide-left{0%{opacity:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}100%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes am-slide-right{0%{opacity:0;-webkit-transform:translateX(100%);transform:translateX(100%)}100%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes am-slide-right{0%{opacity:0;-webkit-transform:translateX(100%);transform:translateX(100%)}100%{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes am-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%{-webkit-transform:translateX(-9px);transform:translateX(-9px)}20%{-webkit-transform:translateX(8px);transform:translateX(8px)}30%{-webkit-transform:translateX(-7px);transform:translateX(-7px)}40%{-webkit-transform:translateX(6px);transform:translateX(6px)}50%{-webkit-transform:translateX(-5px);transform:translateX(-5px)}60%{-webkit-transform:translateX(4px);transform:translateX(4px)}70%{-webkit-transform:translateX(-3px);transform:translateX(-3px)}80%{-webkit-transform:translateX(2px);transform:translateX(2px)}90%{-webkit-transform:translateX(-1px);transform:translateX(-1px)}}@keyframes am-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%{-webkit-transform:translateX(-9px);transform:translateX(-9px)}20%{-webkit-transform:translateX(8px);transform:translateX(8px)}30%{-webkit-transform:translateX(-7px);transform:translateX(-7px)}40%{-webkit-transform:translateX(6px);transform:translateX(6px)}50%{-webkit-transform:translateX(-5px);transform:translateX(-5px)}60%{-webkit-transform:translateX(4px);transform:translateX(4px)}70%{-webkit-transform:translateX(-3px);transform:translateX(-3px)}80%{-webkit-transform:translateX(2px);transform:translateX(2px)}90%{-webkit-transform:translateX(-1px);transform:translateX(-1px)}}@-webkit-keyframes am-slide-top-fixed{0%{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes am-slide-top-fixed{0%{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes am-slide-bottom-fixed{0%{opacity:0;-webkit-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes am-slide-bottom-fixed{0%{opacity:0;-webkit-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes am-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes am-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes am-right-spring{0%{-webkit-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(-20%);transform:translateX(-20%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes am-right-spring{0%{-webkit-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(-20%);transform:translateX(-20%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes am-left-spring{0%{-webkit-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(20%);transform:translateX(20%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes am-left-spring{0%{-webkit-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(20%);transform:translateX(20%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}.am-article:after,.am-article:before{content:" ";display:table}.am-article:after{clear:both}.am-article>:last-child{margin-bottom:0}.am-article+.am-article{margin-top:2.4rem}.am-article-title{font-size:2.8rem;line-height:1.15;font-weight:400}.am-article-title a{color:inherit;text-decoration:none}.am-article-meta{font-size:1.2rem;line-height:1.5;color:#999}.am-article-lead{color:#666;font-size:1.4rem;line-height:1.5;border:1px solid #dedede;border-radius:2px;background:#f9f9f9;padding:10px}.am-article-divider{margin-bottom:2.4rem;border-color:#eee}*+.am-article-divider{margin-top:2.4rem}.am-article-bd blockquote{font-family:Georgia,"Times New Roman",Times,Kai,"Kaiti SC",KaiTi,BiauKai,FontAwesome,serif}.am-article-bd img{display:block;max-width:100%}.am-badge{display:inline-block;min-width:10px;padding:.25em .625em;font-size:1.2rem;font-weight:700;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:0}.am-badge:empty{display:none}.am-badge.am-square{border-radius:0}.am-badge.am-radius{border-radius:2px}.am-badge.am-round{border-radius:1000px}a.am-badge:focus,a.am-badge:hover{color:#fff;text-decoration:none;cursor:pointer}.am-badge-primary{background-color:#0e90d2}.am-badge-secondary{background-color:#3bb4f2}.am-badge-success{background-color:#5eb95e}.am-badge-warning{background-color:#F37B1D}.am-badge-danger{background-color:#dd514c}.am-comment:after,.am-comment:before{content:" ";display:table}.am-comment:after{clear:both}.am-comment-avatar{float:left;width:32px;height:32px;border-radius:50%;border:1px solid transparent}@media only screen and (min-width:641px){.am-comment-avatar{width:48px;height:48px}}.am-comment-main{position:relative;margin-left:42px;border:1px solid #dedede;border-radius:0}.am-comment-main:after,.am-comment-main:before{position:absolute;top:10px;left:-8px;right:100%;width:0;height:0;display:block;content:" ";border-color:transparent;border-style:solid solid outset;border-width:8px 8px 8px 0;pointer-events:none}.am-comment-main:before{border-right-color:#dedede;z-index:1}.am-comment-main:after{border-right-color:#f8f8f8;margin-left:1px;z-index:2}@media only screen and (min-width:641px){.am-comment-main{margin-left:63px}}.am-comment-hd{background:#f8f8f8;border-bottom:1px solid #eee;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.am-comment-title{margin:0 0 8px 0;font-size:1.6rem;line-height:1.2}.am-comment-meta{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;padding:10px 15px;font-size:13px;color:#999;line-height:1.2;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.am-comment-meta a{color:#999}.am-comment-author{font-weight:700;color:#999}.am-comment-bd{padding:15px;overflow:hidden}.am-comment-bd>:last-child{margin-bottom:0}.am-comment-footer{padding:0 15px 5px}.am-comment-footer .am-comment-actions a+a{margin-left:5px}.am-comment-actions{font-size:13px;color:#999}.am-comment-actions a{display:inline-block;padding:10px 5px;line-height:1;color:#999;opacity:.7}.am-comment-actions a:hover{color:#0e90d2;opacity:1}.am-comment-hd .am-comment-actions{padding-right:.5rem}.am-comment-flip .am-comment-avatar{float:right}.am-comment-flip .am-comment-main{margin-left:auto;margin-right:42px}@media only screen and (min-width:641px){.am-comment-flip .am-comment-main{margin-right:63px}}.am-comment-flip .am-comment-main:after,.am-comment-flip .am-comment-main:before{left:auto;right:-8px;border-width:8px 0 8px 8px}.am-comment-flip .am-comment-main:before{border-left-color:#dedede}.am-comment-flip .am-comment-main:after{border-left-color:#f8f8f8;margin-right:1px;margin-left:auto}.am-comment-primary .am-comment-avatar{border-color:#0e90d2}.am-comment-primary .am-comment-main{border-color:#0e90d2}.am-comment-primary .am-comment-main:before{border-right-color:#0e90d2}.am-comment-primary.am-comment-flip .am-comment-main:before{border-left-color:#0e90d2;border-right-color:transparent}.am-comment-primary.am-comment-flip .am-comment-main:after{border-left-color:#f8f8f8}.am-comment-highlight .am-comment-avatar,.am-comment-secondary .am-comment-avatar{border-color:#3bb4f2}.am-comment-highlight .am-comment-main,.am-comment-secondary .am-comment-main{border-color:#3bb4f2}.am-comment-highlight .am-comment-main:before,.am-comment-secondary .am-comment-main:before{border-right-color:#3bb4f2}.am-comment-highlight.am-comment-flip .am-comment-main:before,.am-comment-secondary.am-comment-flip .am-comment-main:before{border-left-color:#3bb4f2;border-right-color:transparent}.am-comment-highlight.am-comment-flip .am-comment-main:after,.am-comment-secondary.am-comment-flip .am-comment-main:after{border-left-color:#f8f8f8}.am-comment-success .am-comment-avatar{border-color:#5eb95e}.am-comment-success .am-comment-main{border-color:#5eb95e}.am-comment-success .am-comment-main:before{border-right-color:#5eb95e}.am-comment-success.am-comment-flip .am-comment-main:before{border-left-color:#5eb95e;border-right-color:transparent}.am-comment-success.am-comment-flip .am-comment-main:after{border-left-color:#f8f8f8}.am-comment-warning .am-comment-avatar{border-color:#F37B1D}.am-comment-warning .am-comment-main{border-color:#F37B1D}.am-comment-warning .am-comment-main:before{border-right-color:#F37B1D}.am-comment-warning.am-comment-flip .am-comment-main:before{border-left-color:#F37B1D;border-right-color:transparent}.am-comment-warning.am-comment-flip .am-comment-main:after{border-left-color:#f8f8f8}.am-comment-danger .am-comment-avatar{border-color:#dd514c}.am-comment-danger .am-comment-main{border-color:#dd514c}.am-comment-danger .am-comment-main:before{border-right-color:#dd514c}.am-comment-danger.am-comment-flip .am-comment-main:before{border-left-color:#dd514c;border-right-color:transparent}.am-comment-danger.am-comment-flip .am-comment-main:after{border-left-color:#f8f8f8}.am-comments-list{padding:0;list-style:none}.am-comments-list .am-comment{margin:1.6rem 0 0 0;list-style:none}@media only screen and (min-width:641px){.am-comments-list-flip .am-comment-main{margin-right:64px}.am-comments-list-flip .am-comment-flip .am-comment-main{margin-left:64px}}.am-btn-group,.am-btn-group-stacked{position:relative;display:inline-block;vertical-align:middle}.am-btn-group-stacked>.am-btn,.am-btn-group>.am-btn{position:relative;float:left}.am-btn-group-stacked>.am-btn.active,.am-btn-group-stacked>.am-btn:active,.am-btn-group-stacked>.am-btn:focus,.am-btn-group-stacked>.am-btn:hover,.am-btn-group>.am-btn.active,.am-btn-group>.am-btn:active,.am-btn-group>.am-btn:focus,.am-btn-group>.am-btn:hover{z-index:2}.am-btn-group-stacked>.am-btn:focus,.am-btn-group>.am-btn:focus{outline:0}.am-btn-group .am-btn+.am-btn,.am-btn-group .am-btn+.am-btn-group,.am-btn-group .am-btn-group+.am-btn,.am-btn-group .am-btn-group+.am-btn-group{margin-left:-1px}.am-btn-toolbar{margin-left:-5px}.am-btn-toolbar:after,.am-btn-toolbar:before{content:" ";display:table}.am-btn-toolbar:after{clear:both}.am-btn-toolbar .am-btn-group,.am-btn-toolbar .am-input-group{float:left}.am-btn-toolbar>.am-btn,.am-btn-toolbar>.am-btn-group,.am-btn-toolbar>.am-input-group{margin-left:5px}.am-btn-group>.am-btn:not(:first-child):not(:last-child):not(.am-dropdown-toggle){border-radius:0}.am-btn-group>.am-btn:first-child{margin-left:0}.am-btn-group>.am-btn:first-child:not(:last-child):not(.am-dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.am-btn-group>.am-btn:last-child:not(:first-child),.am-btn-group>.am-dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.am-btn-group>.am-btn-group{float:left}.am-btn-group>.am-btn-group:not(:first-child):not(:last-child)>.am-btn{border-radius:0}.am-btn-group>.am-btn-group:first-child>.am-btn:last-child,.am-btn-group>.am-btn-group:first-child>.am-dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.am-btn-group>.am-btn-group:last-child>.am-btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.am-btn-group-xs>.am-btn{font-size:1.2rem}.am-btn-group-sm>.am-btn{font-size:1.4rem}.am-btn-group-lg>.am-btn{font-size:1.8rem}.am-btn-group-stacked>.am-btn,.am-btn-group-stacked>.am-btn-group,.am-btn-group-stacked>.am-btn-group>.am-btn{display:block;float:none;width:100%;max-width:100%}.am-btn-group-stacked>.am-btn-group:after,.am-btn-group-stacked>.am-btn-group:before{content:" ";display:table}.am-btn-group-stacked>.am-btn-group:after{clear:both}.am-btn-group-stacked>.am-btn-group>.am-btn{float:none}.am-btn-group-stacked>.am-btn+.am-btn,.am-btn-group-stacked>.am-btn+.am-btn-group,.am-btn-group-stacked>.am-btn-group+.am-btn,.am-btn-group-stacked>.am-btn-group+.am-btn-group{margin-top:-1px;margin-left:0}.am-btn-group-stacked>.am-btn:not(:first-child):not(:last-child){border-radius:0}.am-btn-group-stacked>.am-btn:first-child:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.am-btn-group-stacked>.am-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:0;border-top-left-radius:0}.am-btn-group-stacked>.am-btn-group:not(:first-child):not(:last-child)>.am-btn{border-radius:0}.am-btn-group-stacked>.am-btn-group:first-child:not(:last-child)>.am-btn:last-child,.am-btn-group-stacked>.am-btn-group:first-child:not(:last-child)>.am-dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.am-btn-group-stacked>.am-btn-group:last-child:not(:first-child)>.am-btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.am-btn-group-justify{display:table;table-layout:fixed;border-collapse:separate;width:100%}.am-btn-group-justify>.am-btn,.am-btn-group-justify>.am-btn-group{float:none;display:table-cell;width:1%}.am-btn-group-justify>.am-btn-group .am-btn{width:100%}.lte9 .am-btn-group-justify{display:table;table-layout:fixed;border-collapse:separate}.lte9 .am-btn-group-justify>.am-btn,.lte9 .am-btn-group-justify>.am-btn-group{float:none;display:table-cell;width:1%}.am-btn-group .am-dropdown{float:left;margin-left:-1px}.am-btn-group .am-dropdown>.am-btn{border-bottom-left-radius:0;border-top-left-radius:0}.am-btn-group .am-active .am-dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.am-btn-group .am-active .am-dropdown-toggle.am-btn-link{-webkit-box-shadow:none;box-shadow:none}.am-btn-group .am-active .am-dropdown-toggle,.am-btn-group .am-dropdown-toggle:active{outline:0}.am-btn-group-check>.am-btn>input[type=checkbox],.am-btn-group-check>.am-btn>input[type=radio],[data-am-button]>.am-btn>input[type=checkbox],[data-am-button]>.am-btn>input[type=radio]{position:absolute;z-index:-1;opacity:0}.am-close{display:inline-block;text-align:center;width:24px;font-size:20px;font-weight:700;line-height:24px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;-webkit-transition:all .3s;transition:all .3s}.am-close:focus,.am-close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5;outline:0}.am-close[class*=am-icon-]{font-size:16px}button.am-close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}a.am-close:hover{color:inherit;text-decoration:none;cursor:pointer}.am-close-alt{border-radius:50%;background:#eee;opacity:.7;-webkit-box-shadow:0 0 0 1px rgba(0,0,0,.25);box-shadow:0 0 0 1px rgba(0,0,0,.25)}.am-close-alt:focus,.am-close-alt:hover{opacity:1}.am-close-spin:hover{-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}@font-face{font-family:FontAwesome;src:url(../fonts/fontawesome-webfont.eot?v=4.6.3);src:url(../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3) format('embedded-opentype'),url(../fonts/fontawesome-webfont.woff2?v=4.6.3) format('woff2'),url(../fonts/fontawesome-webfont.woff?v=4.6.3) format('woff'),url(../fonts/fontawesome-webfont.ttf?v=4.6.3) format('truetype');font-weight:400;font-style:normal}[class*=am-icon-]{display:inline-block;font-style:normal}[class*=am-icon-]:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.am-icon-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}[class*=am-icon-].am-fl{margin-right:.3em}[class*=am-icon-].am-fr{margin-left:.3em}.am-icon-sm:before{font-size:150%;vertical-align:-10%}.am-icon-md:before{font-size:200%;vertical-align:-16%}.am-icon-lg:before{font-size:250%;vertical-align:-22%}.am-icon-btn{-webkit-box-sizing:border-box;box-sizing:border-box;display:inline-block;width:48px;height:48px;font-size:24px;line-height:48px;border-radius:50%;background-color:#eee;color:#555;text-align:center}.am-icon-btn:focus,.am-icon-btn:hover{background-color:#f5f5f5;color:#333;text-decoration:none;outline:0}.am-icon-btn:active{background-color:#ddd;color:#333}.am-icon-btn.am-danger,.am-icon-btn.am-primary,.am-icon-btn.am-secondary,.am-icon-btn.am-success,.am-icon-btn.am-warning{color:#fff}.am-icon-btn.am-primary{background-color:#0e90d2}.am-icon-btn.am-secondary{background-color:#3bb4f2}.am-icon-btn.am-success{background-color:#5eb95e}.am-icon-btn.am-warning{background-color:#F37B1D}.am-icon-btn.am-danger{background-color:#dd514c}.am-icon-btn-sm{width:32px;height:32px;font-size:16px;line-height:32px}.am-icon-btn-lg{width:64px;height:64px;font-size:28px;line-height:64px}.am-icon-fw{width:1.25em;text-align:center}.am-icon-glass:before{content:"\f000"}.am-icon-music:before{content:"\f001"}.am-icon-search:before{content:"\f002"}.am-icon-envelope-o:before{content:"\f003"}.am-icon-heart:before{content:"\f004"}.am-icon-star:before{content:"\f005"}.am-icon-star-o:before{content:"\f006"}.am-icon-user:before{content:"\f007"}.am-icon-film:before{content:"\f008"}.am-icon-th-large:before{content:"\f009"}.am-icon-th:before{content:"\f00a"}.am-icon-th-list:before{content:"\f00b"}.am-icon-check:before{content:"\f00c"}.am-icon-close:before,.am-icon-remove:before,.am-icon-times:before{content:"\f00d"}.am-icon-search-plus:before{content:"\f00e"}.am-icon-search-minus:before{content:"\f010"}.am-icon-power-off:before{content:"\f011"}.am-icon-signal:before{content:"\f012"}.am-icon-cog:before,.am-icon-gear:before{content:"\f013"}.am-icon-trash-o:before{content:"\f014"}.am-icon-home:before{content:"\f015"}.am-icon-file-o:before{content:"\f016"}.am-icon-clock-o:before{content:"\f017"}.am-icon-road:before{content:"\f018"}.am-icon-download:before{content:"\f019"}.am-icon-arrow-circle-o-down:before{content:"\f01a"}.am-icon-arrow-circle-o-up:before{content:"\f01b"}.am-icon-inbox:before{content:"\f01c"}.am-icon-play-circle-o:before{content:"\f01d"}.am-icon-repeat:before,.am-icon-rotate-right:before{content:"\f01e"}.am-icon-refresh:before{content:"\f021"}.am-icon-list-alt:before{content:"\f022"}.am-icon-lock:before{content:"\f023"}.am-icon-flag:before{content:"\f024"}.am-icon-headphones:before{content:"\f025"}.am-icon-volume-off:before{content:"\f026"}.am-icon-volume-down:before{content:"\f027"}.am-icon-volume-up:before{content:"\f028"}.am-icon-qrcode:before{content:"\f029"}.am-icon-barcode:before{content:"\f02a"}.am-icon-tag:before{content:"\f02b"}.am-icon-tags:before{content:"\f02c"}.am-icon-book:before{content:"\f02d"}.am-icon-bookmark:before{content:"\f02e"}.am-icon-print:before{content:"\f02f"}.am-icon-camera:before{content:"\f030"}.am-icon-font:before{content:"\f031"}.am-icon-bold:before{content:"\f032"}.am-icon-italic:before{content:"\f033"}.am-icon-text-height:before{content:"\f034"}.am-icon-text-width:before{content:"\f035"}.am-icon-align-left:before{content:"\f036"}.am-icon-align-center:before{content:"\f037"}.am-icon-align-right:before{content:"\f038"}.am-icon-align-justify:before{content:"\f039"}.am-icon-list:before{content:"\f03a"}.am-icon-dedent:before,.am-icon-outdent:before{content:"\f03b"}.am-icon-indent:before{content:"\f03c"}.am-icon-video-camera:before{content:"\f03d"}.am-icon-image:before,.am-icon-photo:before,.am-icon-picture-o:before{content:"\f03e"}.am-icon-pencil:before{content:"\f040"}.am-icon-map-marker:before{content:"\f041"}.am-icon-adjust:before{content:"\f042"}.am-icon-tint:before{content:"\f043"}.am-icon-edit:before,.am-icon-pencil-square-o:before{content:"\f044"}.am-icon-share-square-o:before{content:"\f045"}.am-icon-check-square-o:before{content:"\f046"}.am-icon-arrows:before{content:"\f047"}.am-icon-step-backward:before{content:"\f048"}.am-icon-fast-backward:before{content:"\f049"}.am-icon-backward:before{content:"\f04a"}.am-icon-play:before{content:"\f04b"}.am-icon-pause:before{content:"\f04c"}.am-icon-stop:before{content:"\f04d"}.am-icon-forward:before{content:"\f04e"}.am-icon-fast-forward:before{content:"\f050"}.am-icon-step-forward:before{content:"\f051"}.am-icon-eject:before{content:"\f052"}.am-icon-chevron-left:before{content:"\f053"}.am-icon-chevron-right:before{content:"\f054"}.am-icon-plus-circle:before{content:"\f055"}.am-icon-minus-circle:before{content:"\f056"}.am-icon-times-circle:before{content:"\f057"}.am-icon-check-circle:before{content:"\f058"}.am-icon-question-circle:before{content:"\f059"}.am-icon-info-circle:before{content:"\f05a"}.am-icon-crosshairs:before{content:"\f05b"}.am-icon-times-circle-o:before{content:"\f05c"}.am-icon-check-circle-o:before{content:"\f05d"}.am-icon-ban:before{content:"\f05e"}.am-icon-arrow-left:before{content:"\f060"}.am-icon-arrow-right:before{content:"\f061"}.am-icon-arrow-up:before{content:"\f062"}.am-icon-arrow-down:before{content:"\f063"}.am-icon-mail-forward:before,.am-icon-share:before{content:"\f064"}.am-icon-expand:before{content:"\f065"}.am-icon-compress:before{content:"\f066"}.am-icon-plus:before{content:"\f067"}.am-icon-minus:before{content:"\f068"}.am-icon-asterisk:before{content:"\f069"}.am-icon-exclamation-circle:before{content:"\f06a"}.am-icon-gift:before{content:"\f06b"}.am-icon-leaf:before{content:"\f06c"}.am-icon-fire:before{content:"\f06d"}.am-icon-eye:before{content:"\f06e"}.am-icon-eye-slash:before{content:"\f070"}.am-icon-exclamation-triangle:before,.am-icon-warning:before{content:"\f071"}.am-icon-plane:before{content:"\f072"}.am-icon-calendar:before{content:"\f073"}.am-icon-random:before{content:"\f074"}.am-icon-comment:before{content:"\f075"}.am-icon-magnet:before{content:"\f076"}.am-icon-chevron-up:before{content:"\f077"}.am-icon-chevron-down:before{content:"\f078"}.am-icon-retweet:before{content:"\f079"}.am-icon-shopping-cart:before{content:"\f07a"}.am-icon-folder:before{content:"\f07b"}.am-icon-folder-open:before{content:"\f07c"}.am-icon-arrows-v:before{content:"\f07d"}.am-icon-arrows-h:before{content:"\f07e"}.am-icon-bar-chart-o:before,.am-icon-bar-chart:before{content:"\f080"}.am-icon-twitter-square:before{content:"\f081"}.am-icon-facebook-square:before{content:"\f082"}.am-icon-camera-retro:before{content:"\f083"}.am-icon-key:before{content:"\f084"}.am-icon-cogs:before,.am-icon-gears:before{content:"\f085"}.am-icon-comments:before{content:"\f086"}.am-icon-thumbs-o-up:before{content:"\f087"}.am-icon-thumbs-o-down:before{content:"\f088"}.am-icon-star-half:before{content:"\f089"}.am-icon-heart-o:before{content:"\f08a"}.am-icon-sign-out:before{content:"\f08b"}.am-icon-linkedin-square:before{content:"\f08c"}.am-icon-thumb-tack:before{content:"\f08d"}.am-icon-external-link:before{content:"\f08e"}.am-icon-sign-in:before{content:"\f090"}.am-icon-trophy:before{content:"\f091"}.am-icon-github-square:before{content:"\f092"}.am-icon-upload:before{content:"\f093"}.am-icon-lemon-o:before{content:"\f094"}.am-icon-phone:before{content:"\f095"}.am-icon-square-o:before{content:"\f096"}.am-icon-bookmark-o:before{content:"\f097"}.am-icon-phone-square:before{content:"\f098"}.am-icon-twitter:before{content:"\f099"}.am-icon-facebook-f:before,.am-icon-facebook:before{content:"\f09a"}.am-icon-github:before{content:"\f09b"}.am-icon-unlock:before{content:"\f09c"}.am-icon-credit-card:before{content:"\f09d"}.am-icon-feed:before,.am-icon-rss:before{content:"\f09e"}.am-icon-hdd-o:before{content:"\f0a0"}.am-icon-bullhorn:before{content:"\f0a1"}.am-icon-bell:before{content:"\f0f3"}.am-icon-certificate:before{content:"\f0a3"}.am-icon-hand-o-right:before{content:"\f0a4"}.am-icon-hand-o-left:before{content:"\f0a5"}.am-icon-hand-o-up:before{content:"\f0a6"}.am-icon-hand-o-down:before{content:"\f0a7"}.am-icon-arrow-circle-left:before{content:"\f0a8"}.am-icon-arrow-circle-right:before{content:"\f0a9"}.am-icon-arrow-circle-up:before{content:"\f0aa"}.am-icon-arrow-circle-down:before{content:"\f0ab"}.am-icon-globe:before{content:"\f0ac"}.am-icon-wrench:before{content:"\f0ad"}.am-icon-tasks:before{content:"\f0ae"}.am-icon-filter:before{content:"\f0b0"}.am-icon-briefcase:before{content:"\f0b1"}.am-icon-arrows-alt:before{content:"\f0b2"}.am-icon-group:before,.am-icon-users:before{content:"\f0c0"}.am-icon-chain:before,.am-icon-link:before{content:"\f0c1"}.am-icon-cloud:before{content:"\f0c2"}.am-icon-flask:before{content:"\f0c3"}.am-icon-cut:before,.am-icon-scissors:before{content:"\f0c4"}.am-icon-copy:before,.am-icon-files-o:before{content:"\f0c5"}.am-icon-paperclip:before{content:"\f0c6"}.am-icon-floppy-o:before,.am-icon-save:before{content:"\f0c7"}.am-icon-square:before{content:"\f0c8"}.am-icon-bars:before,.am-icon-navicon:before,.am-icon-reorder:before{content:"\f0c9"}.am-icon-list-ul:before{content:"\f0ca"}.am-icon-list-ol:before{content:"\f0cb"}.am-icon-strikethrough:before{content:"\f0cc"}.am-icon-underline:before{content:"\f0cd"}.am-icon-table:before{content:"\f0ce"}.am-icon-magic:before{content:"\f0d0"}.am-icon-truck:before{content:"\f0d1"}.am-icon-pinterest:before{content:"\f0d2"}.am-icon-pinterest-square:before{content:"\f0d3"}.am-icon-google-plus-square:before{content:"\f0d4"}.am-icon-google-plus:before{content:"\f0d5"}.am-icon-money:before{content:"\f0d6"}.am-icon-caret-down:before{content:"\f0d7"}.am-icon-caret-up:before{content:"\f0d8"}.am-icon-caret-left:before{content:"\f0d9"}.am-icon-caret-right:before{content:"\f0da"}.am-icon-columns:before{content:"\f0db"}.am-icon-sort:before,.am-icon-unsorted:before{content:"\f0dc"}.am-icon-sort-desc:before,.am-icon-sort-down:before{content:"\f0dd"}.am-icon-sort-asc:before,.am-icon-sort-up:before{content:"\f0de"}.am-icon-envelope:before{content:"\f0e0"}.am-icon-linkedin:before{content:"\f0e1"}.am-icon-rotate-left:before,.am-icon-undo:before{content:"\f0e2"}.am-icon-gavel:before,.am-icon-legal:before{content:"\f0e3"}.am-icon-dashboard:before,.am-icon-tachometer:before{content:"\f0e4"}.am-icon-comment-o:before{content:"\f0e5"}.am-icon-comments-o:before{content:"\f0e6"}.am-icon-bolt:before,.am-icon-flash:before{content:"\f0e7"}.am-icon-sitemap:before{content:"\f0e8"}.am-icon-umbrella:before{content:"\f0e9"}.am-icon-clipboard:before,.am-icon-paste:before{content:"\f0ea"}.am-icon-lightbulb-o:before{content:"\f0eb"}.am-icon-exchange:before{content:"\f0ec"}.am-icon-cloud-download:before{content:"\f0ed"}.am-icon-cloud-upload:before{content:"\f0ee"}.am-icon-user-md:before{content:"\f0f0"}.am-icon-stethoscope:before{content:"\f0f1"}.am-icon-suitcase:before{content:"\f0f2"}.am-icon-bell-o:before{content:"\f0a2"}.am-icon-coffee:before{content:"\f0f4"}.am-icon-cutlery:before{content:"\f0f5"}.am-icon-file-text-o:before{content:"\f0f6"}.am-icon-building-o:before{content:"\f0f7"}.am-icon-hospital-o:before{content:"\f0f8"}.am-icon-ambulance:before{content:"\f0f9"}.am-icon-medkit:before{content:"\f0fa"}.am-icon-fighter-jet:before{content:"\f0fb"}.am-icon-beer:before{content:"\f0fc"}.am-icon-h-square:before{content:"\f0fd"}.am-icon-plus-square:before{content:"\f0fe"}.am-icon-angle-double-left:before{content:"\f100"}.am-icon-angle-double-right:before{content:"\f101"}.am-icon-angle-double-up:before{content:"\f102"}.am-icon-angle-double-down:before{content:"\f103"}.am-icon-angle-left:before{content:"\f104"}.am-icon-angle-right:before{content:"\f105"}.am-icon-angle-up:before{content:"\f106"}.am-icon-angle-down:before{content:"\f107"}.am-icon-desktop:before{content:"\f108"}.am-icon-laptop:before{content:"\f109"}.am-icon-tablet:before{content:"\f10a"}.am-icon-mobile-phone:before,.am-icon-mobile:before{content:"\f10b"}.am-icon-circle-o:before{content:"\f10c"}.am-icon-quote-left:before{content:"\f10d"}.am-icon-quote-right:before{content:"\f10e"}.am-icon-spinner:before{content:"\f110"}.am-icon-circle:before{content:"\f111"}.am-icon-mail-reply:before,.am-icon-reply:before{content:"\f112"}.am-icon-github-alt:before{content:"\f113"}.am-icon-folder-o:before{content:"\f114"}.am-icon-folder-open-o:before{content:"\f115"}.am-icon-smile-o:before{content:"\f118"}.am-icon-frown-o:before{content:"\f119"}.am-icon-meh-o:before{content:"\f11a"}.am-icon-gamepad:before{content:"\f11b"}.am-icon-keyboard-o:before{content:"\f11c"}.am-icon-flag-o:before{content:"\f11d"}.am-icon-flag-checkered:before{content:"\f11e"}.am-icon-terminal:before{content:"\f120"}.am-icon-code:before{content:"\f121"}.am-icon-mail-reply-all:before,.am-icon-reply-all:before{content:"\f122"}.am-icon-star-half-empty:before,.am-icon-star-half-full:before,.am-icon-star-half-o:before{content:"\f123"}.am-icon-location-arrow:before{content:"\f124"}.am-icon-crop:before{content:"\f125"}.am-icon-code-fork:before{content:"\f126"}.am-icon-chain-broken:before,.am-icon-unlink:before{content:"\f127"}.am-icon-question:before{content:"\f128"}.am-icon-info:before{content:"\f129"}.am-icon-exclamation:before{content:"\f12a"}.am-icon-superscript:before{content:"\f12b"}.am-icon-subscript:before{content:"\f12c"}.am-icon-eraser:before{content:"\f12d"}.am-icon-puzzle-piece:before{content:"\f12e"}.am-icon-microphone:before{content:"\f130"}.am-icon-microphone-slash:before{content:"\f131"}.am-icon-shield:before{content:"\f132"}.am-icon-calendar-o:before{content:"\f133"}.am-icon-fire-extinguisher:before{content:"\f134"}.am-icon-rocket:before{content:"\f135"}.am-icon-maxcdn:before{content:"\f136"}.am-icon-chevron-circle-left:before{content:"\f137"}.am-icon-chevron-circle-right:before{content:"\f138"}.am-icon-chevron-circle-up:before{content:"\f139"}.am-icon-chevron-circle-down:before{content:"\f13a"}.am-icon-html5:before{content:"\f13b"}.am-icon-css3:before{content:"\f13c"}.am-icon-anchor:before{content:"\f13d"}.am-icon-unlock-alt:before{content:"\f13e"}.am-icon-bullseye:before{content:"\f140"}.am-icon-ellipsis-h:before{content:"\f141"}.am-icon-ellipsis-v:before{content:"\f142"}.am-icon-rss-square:before{content:"\f143"}.am-icon-play-circle:before{content:"\f144"}.am-icon-ticket:before{content:"\f145"}.am-icon-minus-square:before{content:"\f146"}.am-icon-minus-square-o:before{content:"\f147"}.am-icon-level-up:before{content:"\f148"}.am-icon-level-down:before{content:"\f149"}.am-icon-check-square:before{content:"\f14a"}.am-icon-pencil-square:before{content:"\f14b"}.am-icon-external-link-square:before{content:"\f14c"}.am-icon-share-square:before{content:"\f14d"}.am-icon-compass:before{content:"\f14e"}.am-icon-caret-square-o-down:before,.am-icon-toggle-down:before{content:"\f150"}.am-icon-caret-square-o-up:before,.am-icon-toggle-up:before{content:"\f151"}.am-icon-caret-square-o-right:before,.am-icon-toggle-right:before{content:"\f152"}.am-icon-eur:before,.am-icon-euro:before{content:"\f153"}.am-icon-gbp:before{content:"\f154"}.am-icon-dollar:before,.am-icon-usd:before{content:"\f155"}.am-icon-inr:before,.am-icon-rupee:before{content:"\f156"}.am-icon-cny:before,.am-icon-jpy:before,.am-icon-rmb:before,.am-icon-yen:before{content:"\f157"}.am-icon-rouble:before,.am-icon-rub:before,.am-icon-ruble:before{content:"\f158"}.am-icon-krw:before,.am-icon-won:before{content:"\f159"}.am-icon-bitcoin:before,.am-icon-btc:before{content:"\f15a"}.am-icon-file:before{content:"\f15b"}.am-icon-file-text:before{content:"\f15c"}.am-icon-sort-alpha-asc:before{content:"\f15d"}.am-icon-sort-alpha-desc:before{content:"\f15e"}.am-icon-sort-amount-asc:before{content:"\f160"}.am-icon-sort-amount-desc:before{content:"\f161"}.am-icon-sort-numeric-asc:before{content:"\f162"}.am-icon-sort-numeric-desc:before{content:"\f163"}.am-icon-thumbs-up:before{content:"\f164"}.am-icon-thumbs-down:before{content:"\f165"}.am-icon-youtube-square:before{content:"\f166"}.am-icon-youtube:before{content:"\f167"}.am-icon-xing:before{content:"\f168"}.am-icon-xing-square:before{content:"\f169"}.am-icon-youtube-play:before{content:"\f16a"}.am-icon-dropbox:before{content:"\f16b"}.am-icon-stack-overflow:before{content:"\f16c"}.am-icon-instagram:before{content:"\f16d"}.am-icon-flickr:before{content:"\f16e"}.am-icon-adn:before{content:"\f170"}.am-icon-bitbucket:before{content:"\f171"}.am-icon-bitbucket-square:before{content:"\f172"}.am-icon-tumblr:before{content:"\f173"}.am-icon-tumblr-square:before{content:"\f174"}.am-icon-long-arrow-down:before{content:"\f175"}.am-icon-long-arrow-up:before{content:"\f176"}.am-icon-long-arrow-left:before{content:"\f177"}.am-icon-long-arrow-right:before{content:"\f178"}.am-icon-apple:before{content:"\f179"}.am-icon-windows:before{content:"\f17a"}.am-icon-android:before{content:"\f17b"}.am-icon-linux:before{content:"\f17c"}.am-icon-dribbble:before{content:"\f17d"}.am-icon-skype:before{content:"\f17e"}.am-icon-foursquare:before{content:"\f180"}.am-icon-trello:before{content:"\f181"}.am-icon-female:before{content:"\f182"}.am-icon-male:before{content:"\f183"}.am-icon-gittip:before,.am-icon-gratipay:before{content:"\f184"}.am-icon-sun-o:before{content:"\f185"}.am-icon-moon-o:before{content:"\f186"}.am-icon-archive:before{content:"\f187"}.am-icon-bug:before{content:"\f188"}.am-icon-vk:before{content:"\f189"}.am-icon-weibo:before{content:"\f18a"}.am-icon-renren:before{content:"\f18b"}.am-icon-pagelines:before{content:"\f18c"}.am-icon-stack-exchange:before{content:"\f18d"}.am-icon-arrow-circle-o-right:before{content:"\f18e"}.am-icon-arrow-circle-o-left:before{content:"\f190"}.am-icon-caret-square-o-left:before,.am-icon-toggle-left:before{content:"\f191"}.am-icon-dot-circle-o:before{content:"\f192"}.am-icon-wheelchair:before{content:"\f193"}.am-icon-vimeo-square:before{content:"\f194"}.am-icon-try:before,.am-icon-turkish-lira:before{content:"\f195"}.am-icon-plus-square-o:before{content:"\f196"}.am-icon-space-shuttle:before{content:"\f197"}.am-icon-slack:before{content:"\f198"}.am-icon-envelope-square:before{content:"\f199"}.am-icon-wordpress:before{content:"\f19a"}.am-icon-openid:before{content:"\f19b"}.am-icon-bank:before,.am-icon-institution:before,.am-icon-university:before{content:"\f19c"}.am-icon-graduation-cap:before,.am-icon-mortar-board:before{content:"\f19d"}.am-icon-yahoo:before{content:"\f19e"}.am-icon-google:before{content:"\f1a0"}.am-icon-reddit:before{content:"\f1a1"}.am-icon-reddit-square:before{content:"\f1a2"}.am-icon-stumbleupon-circle:before{content:"\f1a3"}.am-icon-stumbleupon:before{content:"\f1a4"}.am-icon-delicious:before{content:"\f1a5"}.am-icon-digg:before{content:"\f1a6"}.am-icon-pied-piper-pp:before{content:"\f1a7"}.am-icon-pied-piper-alt:before{content:"\f1a8"}.am-icon-drupal:before{content:"\f1a9"}.am-icon-joomla:before{content:"\f1aa"}.am-icon-language:before{content:"\f1ab"}.am-icon-fax:before{content:"\f1ac"}.am-icon-building:before{content:"\f1ad"}.am-icon-child:before{content:"\f1ae"}.am-icon-paw:before{content:"\f1b0"}.am-icon-spoon:before{content:"\f1b1"}.am-icon-cube:before{content:"\f1b2"}.am-icon-cubes:before{content:"\f1b3"}.am-icon-behance:before{content:"\f1b4"}.am-icon-behance-square:before{content:"\f1b5"}.am-icon-steam:before{content:"\f1b6"}.am-icon-steam-square:before{content:"\f1b7"}.am-icon-recycle:before{content:"\f1b8"}.am-icon-automobile:before,.am-icon-car:before{content:"\f1b9"}.am-icon-cab:before,.am-icon-taxi:before{content:"\f1ba"}.am-icon-tree:before{content:"\f1bb"}.am-icon-spotify:before{content:"\f1bc"}.am-icon-deviantart:before{content:"\f1bd"}.am-icon-soundcloud:before{content:"\f1be"}.am-icon-database:before{content:"\f1c0"}.am-icon-file-pdf-o:before{content:"\f1c1"}.am-icon-file-word-o:before{content:"\f1c2"}.am-icon-file-excel-o:before{content:"\f1c3"}.am-icon-file-powerpoint-o:before{content:"\f1c4"}.am-icon-file-image-o:before,.am-icon-file-photo-o:before,.am-icon-file-picture-o:before{content:"\f1c5"}.am-icon-file-archive-o:before,.am-icon-file-zip-o:before{content:"\f1c6"}.am-icon-file-audio-o:before,.am-icon-file-sound-o:before{content:"\f1c7"}.am-icon-file-movie-o:before,.am-icon-file-video-o:before{content:"\f1c8"}.am-icon-file-code-o:before{content:"\f1c9"}.am-icon-vine:before{content:"\f1ca"}.am-icon-codepen:before{content:"\f1cb"}.am-icon-jsfiddle:before{content:"\f1cc"}.am-icon-life-bouy:before,.am-icon-life-buoy:before,.am-icon-life-ring:before,.am-icon-life-saver:before,.am-icon-support:before{content:"\f1cd"}.am-icon-circle-o-notch:before{content:"\f1ce"}.am-icon-ra:before,.am-icon-rebel:before,.am-icon-resistance:before{content:"\f1d0"}.am-icon-empire:before,.am-icon-ge:before{content:"\f1d1"}.am-icon-git-square:before{content:"\f1d2"}.am-icon-git:before{content:"\f1d3"}.am-icon-hacker-news:before,.am-icon-y-combinator-square:before,.am-icon-yc-square:before{content:"\f1d4"}.am-icon-tencent-weibo:before{content:"\f1d5"}.am-icon-qq:before{content:"\f1d6"}.am-icon-wechat:before,.am-icon-weixin:before{content:"\f1d7"}.am-icon-paper-plane:before,.am-icon-send:before{content:"\f1d8"}.am-icon-paper-plane-o:before,.am-icon-send-o:before{content:"\f1d9"}.am-icon-history:before{content:"\f1da"}.am-icon-circle-thin:before{content:"\f1db"}.am-icon-header:before{content:"\f1dc"}.am-icon-paragraph:before{content:"\f1dd"}.am-icon-sliders:before{content:"\f1de"}.am-icon-share-alt:before{content:"\f1e0"}.am-icon-share-alt-square:before{content:"\f1e1"}.am-icon-bomb:before{content:"\f1e2"}.am-icon-futbol-o:before,.am-icon-soccer-ball-o:before{content:"\f1e3"}.am-icon-tty:before{content:"\f1e4"}.am-icon-binoculars:before{content:"\f1e5"}.am-icon-plug:before{content:"\f1e6"}.am-icon-slideshare:before{content:"\f1e7"}.am-icon-twitch:before{content:"\f1e8"}.am-icon-yelp:before{content:"\f1e9"}.am-icon-newspaper-o:before{content:"\f1ea"}.am-icon-wifi:before{content:"\f1eb"}.am-icon-calculator:before{content:"\f1ec"}.am-icon-paypal:before{content:"\f1ed"}.am-icon-google-wallet:before{content:"\f1ee"}.am-icon-cc-visa:before{content:"\f1f0"}.am-icon-cc-mastercard:before{content:"\f1f1"}.am-icon-cc-discover:before{content:"\f1f2"}.am-icon-cc-amex:before{content:"\f1f3"}.am-icon-cc-paypal:before{content:"\f1f4"}.am-icon-cc-stripe:before{content:"\f1f5"}.am-icon-bell-slash:before{content:"\f1f6"}.am-icon-bell-slash-o:before{content:"\f1f7"}.am-icon-trash:before{content:"\f1f8"}.am-icon-copyright:before{content:"\f1f9"}.am-icon-at:before{content:"\f1fa"}.am-icon-eyedropper:before{content:"\f1fb"}.am-icon-paint-brush:before{content:"\f1fc"}.am-icon-birthday-cake:before{content:"\f1fd"}.am-icon-area-chart:before{content:"\f1fe"}.am-icon-pie-chart:before{content:"\f200"}.am-icon-line-chart:before{content:"\f201"}.am-icon-lastfm:before{content:"\f202"}.am-icon-lastfm-square:before{content:"\f203"}.am-icon-toggle-off:before{content:"\f204"}.am-icon-toggle-on:before{content:"\f205"}.am-icon-bicycle:before{content:"\f206"}.am-icon-bus:before{content:"\f207"}.am-icon-ioxhost:before{content:"\f208"}.am-icon-angellist:before{content:"\f209"}.am-icon-cc:before{content:"\f20a"}.am-icon-ils:before,.am-icon-shekel:before,.am-icon-sheqel:before{content:"\f20b"}.am-icon-meanpath:before{content:"\f20c"}.am-icon-buysellads:before{content:"\f20d"}.am-icon-connectdevelop:before{content:"\f20e"}.am-icon-dashcube:before{content:"\f210"}.am-icon-forumbee:before{content:"\f211"}.am-icon-leanpub:before{content:"\f212"}.am-icon-sellsy:before{content:"\f213"}.am-icon-shirtsinbulk:before{content:"\f214"}.am-icon-simplybuilt:before{content:"\f215"}.am-icon-skyatlas:before{content:"\f216"}.am-icon-cart-plus:before{content:"\f217"}.am-icon-cart-arrow-down:before{content:"\f218"}.am-icon-diamond:before{content:"\f219"}.am-icon-ship:before{content:"\f21a"}.am-icon-user-secret:before{content:"\f21b"}.am-icon-motorcycle:before{content:"\f21c"}.am-icon-street-view:before{content:"\f21d"}.am-icon-heartbeat:before{content:"\f21e"}.am-icon-venus:before{content:"\f221"}.am-icon-mars:before{content:"\f222"}.am-icon-mercury:before{content:"\f223"}.am-icon-intersex:before,.am-icon-transgender:before{content:"\f224"}.am-icon-transgender-alt:before{content:"\f225"}.am-icon-venus-double:before{content:"\f226"}.am-icon-mars-double:before{content:"\f227"}.am-icon-venus-mars:before{content:"\f228"}.am-icon-mars-stroke:before{content:"\f229"}.am-icon-mars-stroke-v:before{content:"\f22a"}.am-icon-mars-stroke-h:before{content:"\f22b"}.am-icon-neuter:before{content:"\f22c"}.am-icon-genderless:before{content:"\f22d"}.am-icon-facebook-official:before{content:"\f230"}.am-icon-pinterest-p:before{content:"\f231"}.am-icon-whatsapp:before{content:"\f232"}.am-icon-server:before{content:"\f233"}.am-icon-user-plus:before{content:"\f234"}.am-icon-user-times:before{content:"\f235"}.am-icon-bed:before,.am-icon-hotel:before{content:"\f236"}.am-icon-viacoin:before{content:"\f237"}.am-icon-train:before{content:"\f238"}.am-icon-subway:before{content:"\f239"}.am-icon-medium:before{content:"\f23a"}.am-icon-y-combinator:before,.am-icon-yc:before{content:"\f23b"}.am-icon-optin-monster:before{content:"\f23c"}.am-icon-opencart:before{content:"\f23d"}.am-icon-expeditedssl:before{content:"\f23e"}.am-icon-battery-4:before,.am-icon-battery-full:before{content:"\f240"}.am-icon-battery-3:before,.am-icon-battery-three-quarters:before{content:"\f241"}.am-icon-battery-2:before,.am-icon-battery-half:before{content:"\f242"}.am-icon-battery-1:before,.am-icon-battery-quarter:before{content:"\f243"}.am-icon-battery-0:before,.am-icon-battery-empty:before{content:"\f244"}.am-icon-mouse-pointer:before{content:"\f245"}.am-icon-i-cursor:before{content:"\f246"}.am-icon-object-group:before{content:"\f247"}.am-icon-object-ungroup:before{content:"\f248"}.am-icon-sticky-note:before{content:"\f249"}.am-icon-sticky-note-o:before{content:"\f24a"}.am-icon-cc-jcb:before{content:"\f24b"}.am-icon-cc-diners-club:before{content:"\f24c"}.am-icon-clone:before{content:"\f24d"}.am-icon-balance-scale:before{content:"\f24e"}.am-icon-hourglass-o:before{content:"\f250"}.am-icon-hourglass-1:before,.am-icon-hourglass-start:before{content:"\f251"}.am-icon-hourglass-2:before,.am-icon-hourglass-half:before{content:"\f252"}.am-icon-hourglass-3:before,.am-icon-hourglass-end:before{content:"\f253"}.am-icon-hourglass:before{content:"\f254"}.am-icon-hand-grab-o:before,.am-icon-hand-rock-o:before{content:"\f255"}.am-icon-hand-paper-o:before,.am-icon-hand-stop-o:before{content:"\f256"}.am-icon-hand-scissors-o:before{content:"\f257"}.am-icon-hand-lizard-o:before{content:"\f258"}.am-icon-hand-spock-o:before{content:"\f259"}.am-icon-hand-pointer-o:before{content:"\f25a"}.am-icon-hand-peace-o:before{content:"\f25b"}.am-icon-trademark:before{content:"\f25c"}.am-icon-registered:before{content:"\f25d"}.am-icon-creative-commons:before{content:"\f25e"}.am-icon-gg:before{content:"\f260"}.am-icon-gg-circle:before{content:"\f261"}.am-icon-tripadvisor:before{content:"\f262"}.am-icon-odnoklassniki:before{content:"\f263"}.am-icon-odnoklassniki-square:before{content:"\f264"}.am-icon-get-pocket:before{content:"\f265"}.am-icon-wikipedia-w:before{content:"\f266"}.am-icon-safari:before{content:"\f267"}.am-icon-chrome:before{content:"\f268"}.am-icon-firefox:before{content:"\f269"}.am-icon-opera:before{content:"\f26a"}.am-icon-internet-explorer:before{content:"\f26b"}.am-icon-television:before,.am-icon-tv:before{content:"\f26c"}.am-icon-contao:before{content:"\f26d"}.am-icon-500px:before{content:"\f26e"}.am-icon-amazon:before{content:"\f270"}.am-icon-calendar-plus-o:before{content:"\f271"}.am-icon-calendar-minus-o:before{content:"\f272"}.am-icon-calendar-times-o:before{content:"\f273"}.am-icon-calendar-check-o:before{content:"\f274"}.am-icon-industry:before{content:"\f275"}.am-icon-map-pin:before{content:"\f276"}.am-icon-map-signs:before{content:"\f277"}.am-icon-map-o:before{content:"\f278"}.am-icon-map:before{content:"\f279"}.am-icon-commenting:before{content:"\f27a"}.am-icon-commenting-o:before{content:"\f27b"}.am-icon-houzz:before{content:"\f27c"}.am-icon-vimeo:before{content:"\f27d"}.am-icon-black-tie:before{content:"\f27e"}.am-icon-fonticons:before{content:"\f280"}.am-icon-reddit-alien:before{content:"\f281"}.am-icon-edge:before{content:"\f282"}.am-icon-credit-card-alt:before{content:"\f283"}.am-icon-codiepie:before{content:"\f284"}.am-icon-modx:before{content:"\f285"}.am-icon-fort-awesome:before{content:"\f286"}.am-icon-usb:before{content:"\f287"}.am-icon-product-hunt:before{content:"\f288"}.am-icon-mixcloud:before{content:"\f289"}.am-icon-scribd:before{content:"\f28a"}.am-icon-pause-circle:before{content:"\f28b"}.am-icon-pause-circle-o:before{content:"\f28c"}.am-icon-stop-circle:before{content:"\f28d"}.am-icon-stop-circle-o:before{content:"\f28e"}.am-icon-shopping-bag:before{content:"\f290"}.am-icon-shopping-basket:before{content:"\f291"}.am-icon-hashtag:before{content:"\f292"}.am-icon-bluetooth:before{content:"\f293"}.am-icon-bluetooth-b:before{content:"\f294"}.am-icon-percent:before{content:"\f295"}.am-icon-gitlab:before{content:"\f296"}.am-icon-wpbeginner:before{content:"\f297"}.am-icon-wpforms:before{content:"\f298"}.am-icon-envira:before{content:"\f299"}.am-icon-universal-access:before{content:"\f29a"}.am-icon-wheelchair-alt:before{content:"\f29b"}.am-icon-question-circle-o:before{content:"\f29c"}.am-icon-blind:before{content:"\f29d"}.am-icon-audio-description:before{content:"\f29e"}.am-icon-volume-control-phone:before{content:"\f2a0"}.am-icon-braille:before{content:"\f2a1"}.am-icon-assistive-listening-systems:before{content:"\f2a2"}.am-icon-american-sign-language-interpreting:before,.am-icon-asl-interpreting:before{content:"\f2a3"}.am-icon-deaf:before,.am-icon-deafness:before,.am-icon-hard-of-hearing:before{content:"\f2a4"}.am-icon-glide:before{content:"\f2a5"}.am-icon-glide-g:before{content:"\f2a6"}.am-icon-sign-language:before,.am-icon-signing:before{content:"\f2a7"}.am-icon-low-vision:before{content:"\f2a8"}.am-icon-viadeo:before{content:"\f2a9"}.am-icon-viadeo-square:before{content:"\f2aa"}.am-icon-snapchat:before{content:"\f2ab"}.am-icon-snapchat-ghost:before{content:"\f2ac"}.am-icon-snapchat-square:before{content:"\f2ad"}.am-icon-pied-piper:before{content:"\f2ae"}.am-icon-first-order:before{content:"\f2b0"}.am-icon-yoast:before{content:"\f2b1"}.am-icon-themeisle:before{content:"\f2b2"}.am-icon-google-plus-circle:before,.am-icon-google-plus-official:before{content:"\f2b3"}.am-icon-fa:before,.am-icon-font-awesome:before{content:"\f2b4"}@-webkit-keyframes icon-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes icon-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.am-icon-spin{-webkit-animation:icon-spin 2s infinite linear;animation:icon-spin 2s infinite linear}.am-icon-pulse{-webkit-animation:icon-spin 1s infinite steps(8);animation:icon-spin 1s infinite steps(8)}.am-icon-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.am-icon-ul>li{position:relative}.am-icon-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.am-input-group{position:relative;display:table;border-collapse:separate}.am-input-group .am-form-field{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.am-input-group .am-form-field,.am-input-group-btn,.am-input-group-label{display:table-cell}.am-input-group .am-form-field:not(:first-child):not(:last-child),.am-input-group-btn:not(:first-child):not(:last-child),.am-input-group-label:not(:first-child):not(:last-child){border-radius:0}.am-input-group-btn,.am-input-group-label{width:1%;white-space:nowrap;vertical-align:middle}.am-input-group-label{height:38px;padding:0 1em;font-size:1.6rem;font-weight:400;line-height:36px;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:0}.am-input-group-label input[type=checkbox],.am-input-group-label input[type=radio]{margin-top:0}.am-input-group .am-form-field:first-child,.am-input-group-btn:first-child>.am-btn,.am-input-group-btn:first-child>.am-btn-group>.am-btn,.am-input-group-btn:first-child>.am-dropdown-toggle,.am-input-group-btn:last-child>.am-btn-group:not(:last-child)>.am-btn,.am-input-group-btn:last-child>.am-btn:not(:last-child):not(.dropdown-toggle),.am-input-group-label:first-child{border-bottom-right-radius:0;border-top-right-radius:0}.am-input-group-label:first-child{border-right:0}.am-input-group .am-form-field:last-child,.am-input-group-btn:first-child>.am-btn-group:not(:first-child)>.am-btn,.am-input-group-btn:first-child>.am-btn:not(:first-child),.am-input-group-btn:last-child>.am-btn,.am-input-group-btn:last-child>.am-btn-group>.am-btn,.am-input-group-btn:last-child>.am-dropdown-toggle,.am-input-group-label:last-child{border-bottom-left-radius:0;border-top-left-radius:0}.am-input-group-label:last-child{border-left:0}.am-input-group-btn{position:relative;font-size:0;white-space:nowrap}.am-input-group-btn>.am-btn{position:relative;border-color:#ccc}.am-input-group-btn>.am-btn+.am-btn{margin-left:-1px}.am-input-group-btn>.am-btn:active,.am-input-group-btn>.am-btn:focus,.am-input-group-btn>.am-btn:hover{z-index:2}.am-input-group-btn:first-child>.am-btn,.am-input-group-btn:first-child>.am-btn-group{margin-right:-2px}.am-input-group-btn:last-child>.am-btn,.am-input-group-btn:last-child>.am-btn-group{margin-left:-1px}.am-input-group .am-form-field,.am-input-group-btn>.am-btn{height:38px;padding-bottom:auto}.am-input-group-lg>.am-form-field,.am-input-group-lg>.am-input-group-btn>.am-btn,.am-input-group-lg>.am-input-group-label{height:42px;font-size:1.8rem!important}.am-input-group-lg>.am-input-group-label{line-height:40px}.am-input-group-sm>.am-form-field,.am-input-group-sm>.am-input-group-btn>.am-btn,.am-input-group-sm>.am-input-group-label{height:33px;font-size:1.4rem!important}.am-input-group-sm>.am-input-group-label{line-height:31px}.am-input-group-primary .am-input-group-label{background:#0e90d2;color:#fff}.am-input-group-primary .am-input-group-btn>.am-btn,.am-input-group-primary .am-input-group-label,.am-input-group-primary.am-input-group .am-form-field{border-color:#0e90d2}.am-input-group-secondary .am-input-group-label{background:#3bb4f2;color:#fff}.am-input-group-secondary .am-input-group-btn>.am-btn,.am-input-group-secondary .am-input-group-label,.am-input-group-secondary.am-input-group .am-form-field{border-color:#3bb4f2}.am-input-group-success .am-input-group-label{background:#5eb95e;color:#fff}.am-input-group-success .am-input-group-btn>.am-btn,.am-input-group-success .am-input-group-label,.am-input-group-success.am-input-group .am-form-field{border-color:#5eb95e}.am-input-group-warning .am-input-group-label{background:#F37B1D;color:#fff}.am-input-group-warning .am-input-group-btn>.am-btn,.am-input-group-warning .am-input-group-label,.am-input-group-warning.am-input-group .am-form-field{border-color:#F37B1D}.am-input-group-danger .am-input-group-label{background:#dd514c;color:#fff}.am-input-group-danger .am-input-group-btn>.am-btn,.am-input-group-danger .am-input-group-label,.am-input-group-danger.am-input-group .am-form-field{border-color:#dd514c}.am-list{margin-bottom:1.6rem;padding-left:0}.am-list>li{position:relative;display:block;margin-bottom:-1px;background-color:#fff;border:1px solid #dedede;border-width:1px 0}.am-list>li>a{display:block;padding:1rem 0}.am-list>li>a.am-active,.am-list>li>a.am-active:focus,.am-list>li>a.am-active:hover{z-index:2;color:#fff;background-color:#0e90d2;border-color:#0e90d2}.am-list>li>a.am-active .am-list-item-heading,.am-list>li>a.am-active:focus .am-list-item-heading,.am-list>li>a.am-active:hover .am-list-item-heading{color:inherit}.am-list>li>a.am-active .am-list-item-text,.am-list>li>a.am-active:focus .am-list-item-text,.am-list>li>a.am-active:hover .am-list-item-text{color:#b2e2fa}.am-list>li>.am-badge{float:right}.am-list>li>.am-badge+.am-badge{margin-right:5px}.am-list-static>li{padding:.8rem .2rem}.am-list-static.am-list-border>li{padding:1rem}.am-list-border>li,.am-list-bordered>li{border-width:1px}.am-list-border>li:first-child,.am-list-border>li:first-child>a,.am-list-bordered>li:first-child,.am-list-bordered>li:first-child>a{border-top-right-radius:0;border-top-left-radius:0}.am-list-border>li:last-child,.am-list-border>li:last-child>a,.am-list-bordered>li:last-child,.am-list-bordered>li:last-child>a{margin-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.am-list-border>li>a,.am-list-bordered>li>a{padding:1rem}.am-list-border>li>a:focus,.am-list-border>li>a:hover,.am-list-bordered>li>a:focus,.am-list-bordered>li>a:hover{background-color:#f5f5f5}.am-list-striped>li:nth-of-type(even){background:#f5f5f5}.am-list-item-hd{margin-top:0}.am-list-item-text{line-height:1.4;font-size:1.3rem;color:#999;margin:0}.am-panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.am-panel-hd{padding:.6rem 1.25rem;border-bottom:1px solid transparent;border-top-right-radius:0;border-top-left-radius:0}.am-panel-bd{padding:1.25rem}.am-panel-title{margin:0;font-size:100%;color:inherit}.am-panel-title>a{color:inherit}.am-panel-footer{padding:.6rem 1.25rem;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:0;border-bottom-left-radius:0}.am-panel-default{border-color:#ddd}.am-panel-default>.am-panel-hd{color:#444;background-color:#f5f5f5;border-color:#ddd}.am-panel-default>.am-panel-hd+.am-panel-collapse>.am-panel-bd{border-top-color:#ddd}.am-panel-default>.am-panel-footer+.am-panel-collapse>.am-panel-bd{border-bottom-color:#ddd}.am-panel-primary{border-color:#10a0ea}.am-panel-primary>.am-panel-hd{color:#fff;background-color:#0e90d2;border-color:#10a0ea}.am-panel-primary>.am-panel-hd+.am-panel-collapse>.am-panel-bd{border-top-color:#10a0ea}.am-panel-primary>.am-panel-footer+.am-panel-collapse>.am-panel-bd{border-bottom-color:#10a0ea}.am-panel-secondary{border-color:#caebfb}.am-panel-secondary>.am-panel-hd{color:#14a6ef;background-color:rgba(59,180,242,.15);border-color:#caebfb}.am-panel-secondary>.am-panel-hd+.am-panel-collapse>.am-panel-bd{border-top-color:#caebfb}.am-panel-secondary>.am-panel-footer+.am-panel-collapse>.am-panel-bd{border-bottom-color:#caebfb}.am-panel-success{border-color:#c9e7c9}.am-panel-success>.am-panel-hd{color:#5eb95e;background-color:rgba(94,185,94,.15);border-color:#c9e7c9}.am-panel-success>.am-panel-hd+.am-panel-collapse>.am-panel-bd{border-top-color:#c9e7c9}.am-panel-success>.am-panel-footer+.am-panel-collapse>.am-panel-bd{border-bottom-color:#c9e7c9}.am-panel-warning{border-color:#fbd0ae}.am-panel-warning>.am-panel-hd{color:#F37B1D;background-color:rgba(243,123,29,.15);border-color:#fbd0ae}.am-panel-warning>.am-panel-hd+.am-panel-collapse>.am-panel-bd{border-top-color:#fbd0ae}.am-panel-warning>.am-panel-footer+.am-panel-collapse>.am-panel-bd{border-bottom-color:#fbd0ae}.am-panel-danger{border-color:#f5cecd}.am-panel-danger>.am-panel-hd{color:#dd514c;background-color:rgba(221,81,76,.15);border-color:#f5cecd}.am-panel-danger>.am-panel-hd+.am-panel-collapse>.am-panel-bd{border-top-color:#f5cecd}.am-panel-danger>.am-panel-footer+.am-panel-collapse>.am-panel-bd{border-bottom-color:#f5cecd}.am-panel>.am-table{margin-bottom:0}.am-panel>.am-table:first-child{border-top-right-radius:0;border-top-left-radius:0}.am-panel>.am-table:first-child>tbody:first-child>tr:first-child td:first-child,.am-panel>.am-table:first-child>tbody:first-child>tr:first-child th:first-child,.am-panel>.am-table:first-child>thead:first-child>tr:first-child td:first-child,.am-panel>.am-table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:0}.am-panel>.am-table:first-child>tbody:first-child>tr:first-child td:last-child,.am-panel>.am-table:first-child>tbody:first-child>tr:first-child th:last-child,.am-panel>.am-table:first-child>thead:first-child>tr:first-child td:last-child,.am-panel>.am-table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:0}.am-panel>.am-table:last-child{border-bottom-right-radius:0;border-bottom-left-radius:0}.am-panel>.am-table:last-child>tbody:last-child>tr:last-child td:first-child,.am-panel>.am-table:last-child>tbody:last-child>tr:last-child th:first-child,.am-panel>.am-table:last-child>tfoot:last-child>tr:last-child td:first-child,.am-panel>.am-table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:0}.am-panel>.am-table:last-child>tbody:last-child>tr:last-child td:last-child,.am-panel>.am-table:last-child>tbody:last-child>tr:last-child th:last-child,.am-panel>.am-table:last-child>tfoot:last-child>tr:last-child td:last-child,.am-panel>.am-table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:0}.am-panel>.am-panel-bd+.am-table{border-top:1px solid #ddd}.am-panel>.am-table>tbody:first-child>tr:first-child td,.am-panel>.am-table>tbody:first-child>tr:first-child th{border-top:0}.am-panel>.am-table-bd{border:0}.am-panel>.am-table-bd>tbody>tr>td:first-child,.am-panel>.am-table-bd>tbody>tr>th:first-child,.am-panel>.am-table-bd>tfoot>tr>td:first-child,.am-panel>.am-table-bd>tfoot>tr>th:first-child,.am-panel>.am-table-bd>thead>tr>td:first-child,.am-panel>.am-table-bd>thead>tr>th:first-child{border-left:0}.am-panel>.am-table-bd>tbody>tr>td:last-child,.am-panel>.am-table-bd>tbody>tr>th:last-child,.am-panel>.am-table-bd>tfoot>tr>td:last-child,.am-panel>.am-table-bd>tfoot>tr>th:last-child,.am-panel>.am-table-bd>thead>tr>td:last-child,.am-panel>.am-table-bd>thead>tr>th:last-child{border-right:0}.am-panel>.am-table-bd>tbody>tr:first-child>td,.am-panel>.am-table-bd>tbody>tr:first-child>th,.am-panel>.am-table-bd>thead>tr:first-child>td,.am-panel>.am-table-bd>thead>tr:first-child>th{border-bottom:0}.am-panel>.am-table-bd>tbody>tr:last-child>td,.am-panel>.am-table-bd>tbody>tr:last-child>th,.am-panel>.am-table-bd>tfoot>tr:last-child>td,.am-panel>.am-table-bd>tfoot>tr:last-child>th{border-bottom:0}.am-panel>.am-list{margin:0}.am-panel>.am-list>li>a{padding-left:1rem;padding-right:1rem}.am-panel>.am-list-static li{padding-left:1rem;padding-right:1rem}.am-panel-group{margin-bottom:2rem}.am-panel-group .am-panel{margin-bottom:0;border-radius:0}.am-panel-group .am-panel+.am-panel{margin-top:6px}.am-panel-group .am-panel-hd{border-bottom:0}.am-panel-group .am-panel-hd+.am-panel-collapse .am-panel-bd{border-top:1px solid #ddd}.am-panel-group .am-panel-footer{border-top:0}.am-panel-group .am-panel-footer+.am-panel-collapse .am-panel-bd{border-bottom:1px solid #ddd}@-webkit-keyframes progress-bar-stripes{from{background-position:36px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:36px 0}to{background-position:0 0}}.am-progress{overflow:hidden;height:2rem;margin-bottom:2rem;background-color:#f5f5f5;border-radius:0;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.am-progress-bar{float:left;width:0;height:100%;font-size:1.2rem;line-height:2rem;color:#fff;text-align:center;background-color:#0e90d2;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.am-progress-striped .am-progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(255,255,255,.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(255,255,255,.15)),color-stop(.75,rgba(255,255,255,.15)),color-stop(.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:36px 36px;background-size:36px 36px}.am-progress.am-active .am-progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.am-progress-bar[aria-valuenow="1"],.am-progress-bar[aria-valuenow="2"]{min-width:30px}.am-progress-bar[aria-valuenow="0"]{color:#999;min-width:30px;background:0 0;-webkit-box-shadow:none;box-shadow:none}.am-progress-bar-secondary{background-color:#3bb4f2}.am-progress-striped .am-progress-bar-secondary{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(255,255,255,.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(255,255,255,.15)),color-stop(.75,rgba(255,255,255,.15)),color-stop(.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.am-progress-bar-success{background-color:#5eb95e}.am-progress-striped .am-progress-bar-success{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(255,255,255,.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(255,255,255,.15)),color-stop(.75,rgba(255,255,255,.15)),color-stop(.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.am-progress-bar-warning{background-color:#F37B1D}.am-progress-striped .am-progress-bar-warning{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(255,255,255,.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(255,255,255,.15)),color-stop(.75,rgba(255,255,255,.15)),color-stop(.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.am-progress-bar-danger{background-color:#dd514c}.am-progress-striped .am-progress-bar-danger{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(255,255,255,.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(255,255,255,.15)),color-stop(.75,rgba(255,255,255,.15)),color-stop(.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.am-progress-xs{height:.6rem}.am-progress-sm{height:1.2rem}.am-thumbnail{display:block;padding:2px;margin-bottom:2rem;background-color:#fff;border:1px solid #ddd;border-radius:0;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.am-thumbnail a>img,.am-thumbnail>img{margin-left:auto;margin-right:auto;display:block}.am-thumbnail a.am-thumbnail.active,.am-thumbnail a.am-thumbnail:focus,.am-thumbnail a.am-thumbnail:hover{border-color:#0e90d2;background-color:#fff}.am-thumbnail a>img,.am-thumbnail>img,img.am-thumbnail{max-width:100%;height:auto}.am-thumbnail-caption{margin:0;padding:.8rem;color:#333;font-weight:400}.am-thumbnail-caption :last-child{margin-bottom:0}.am-thumbnails{margin-left:-.5rem;margin-right:-.5rem}.am-thumbnails>li{padding:0 .5rem 1rem .5rem}.am-scrollable-horizontal{width:100%;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.am-scrollable-vertical{height:240px;overflow-y:scroll;-webkit-overflow-scrolling:touch;resize:vertical}.am-square{border-radius:0}.am-radius{border-radius:2px}.am-round{border-radius:1000px}.am-circle{border-radius:50%}.am-cf:after,.am-cf:before{content:" ";display:table}.am-cf:after{clear:both}.am-fl{float:left}.am-fr{float:right}.am-nbfc{overflow:hidden}.am-center{display:block;margin-left:auto;margin-right:auto}.am-block{display:block!important}.am-inline{display:inline!important}.am-inline-block{display:inline-block!important}.am-hide{display:none!important;visibility:hidden!important}.am-vertical-align{font-size:0}.am-vertical-align:before{content:'';display:inline-block;height:100%;vertical-align:middle}.am-vertical-align-bottom,.am-vertical-align-middle{display:inline-block;font-size:1.6rem;max-width:100%}.am-vertical-align-middle{vertical-align:middle}.am-vertical-align-bottom{vertical-align:bottom}.am-responsive-width{-webkit-box-sizing:border-box;box-sizing:border-box;max-width:100%;height:auto}.am-margin{margin:1.6rem}.am-margin-0{margin:0!important}.am-margin-xs{margin:.5rem}.am-margin-sm{margin:1rem}.am-margin-lg{margin:2.4rem}.am-margin-xl{margin:3.2rem}.am-margin-horizontal{margin-left:1.6rem;margin-right:1.6rem}.am-margin-horizontal-0{margin-left:0!important;margin-right:0!important}.am-margin-horizontal-xs{margin-left:.5rem;margin-right:.5rem}.am-margin-horizontal-sm{margin-left:1rem;margin-right:1rem}.am-margin-horizontal-lg{margin-left:2.4rem;margin-right:2.4rem}.am-margin-horizontal-xl{margin-left:3.2rem;margin-right:3.2rem}.am-margin-vertical{margin-top:1.6rem;margin-bottom:1.6rem}.am-margin-vertical-0{margin-top:0!important;margin-bottom:0!important}.am-margin-vertical-xs{margin-top:.5rem;margin-bottom:.5rem}.am-margin-vertical-sm{margin-top:1rem;margin-bottom:1rem}.am-margin-vertical-lg{margin-top:2.4rem;margin-bottom:2.4rem}.am-margin-vertical-xl{margin-top:3.2rem;margin-bottom:3.2rem}.am-margin-top{margin-top:1.6rem}.am-margin-top-0{margin-top:0!important}.am-margin-top-xs{margin-top:.5rem}.am-margin-top-sm{margin-top:1rem}.am-margin-top-lg{margin-top:2.4rem}.am-margin-top-xl{margin-top:3.2rem}.am-margin-bottom{margin-bottom:1.6rem}.am-margin-bottom-0{margin-bottom:0!important}.am-margin-bottom-xs{margin-bottom:.5rem}.am-margin-bottom-sm{margin-bottom:1rem}.am-margin-bottom-lg{margin-bottom:2.4rem}.am-margin-bottom-xl{margin-bottom:3.2rem}.am-margin-left{margin-left:1.6rem}.am-margin-left-0{margin-left:0!important}.am-margin-left-xs{margin-left:.5rem}.am-margin-left-sm{margin-left:1rem}.am-margin-left-lg{margin-left:2.4rem}.am-margin-left-xl{margin-left:3.2rem}.am-margin-right{margin-right:1.6rem}.am-margin-right-0{margin-right:0!important}.am-margin-right-xs{margin-right:.5rem}.am-margin-right-sm{margin-right:1rem}.am-margin-right-lg{margin-right:2.4rem}.am-margin-right-xl{margin-right:3.2rem}.am-padding{padding:1.6rem}.am-padding-0{padding:0!important}.am-padding-xs{padding:.5rem}.am-padding-sm{padding:1rem}.am-padding-lg{padding:2.4rem}.am-padding-xl{padding:3.2rem}.am-padding-horizontal{padding-left:1.6rem;padding-right:1.6rem}.am-padding-horizontal-0{padding-left:0!important;padding-right:0!important}.am-padding-horizontal-xs{padding-left:.5rem;padding-right:.5rem}.am-padding-horizontal-sm{padding-left:1rem;padding-right:1rem}.am-padding-horizontal-lg{padding-left:2.4rem;padding-right:2.4rem}.am-padding-horizontal-xl{padding-left:3.2rem;padding-right:3.2rem}.am-padding-vertical{padding-top:1.6rem;padding-bottom:1.6rem}.am-padding-vertical-0{padding-top:0!important;padding-bottom:0!important}.am-padding-vertical-xs{padding-top:.5rem;padding-bottom:.5rem}.am-padding-vertical-sm{padding-top:1rem;padding-bottom:1rem}.am-padding-vertical-lg{padding-top:2.4rem;padding-bottom:2.4rem}.am-padding-vertical-xl{padding-top:3.2rem;padding-bottom:3.2rem}.am-padding-top{padding-top:1.6rem}.am-padding-top-0{padding-top:0!important}.am-padding-top-xs{padding-top:.5rem}.am-padding-top-sm{padding-top:1rem}.am-padding-top-lg{padding-top:2.4rem}.am-padding-top-xl{padding-top:3.2rem}.am-padding-bottom{padding-bottom:1.6rem}.am-padding-bottom-0{padding-bottom:0!important}.am-padding-bottom-xs{padding-bottom:.5rem}.am-padding-bottom-sm{padding-bottom:1rem}.am-padding-bottom-lg{padding-bottom:2.4rem}.am-padding-bottom-xl{padding-bottom:3.2rem}.am-padding-left{padding-left:1.6rem}.am-padding-left-0{padding-left:0!important}.am-padding-left-xs{padding-left:.5rem}.am-padding-left-sm{padding-left:1rem}.am-padding-left-lg{padding-left:2.4rem}.am-padding-left-xl{padding-left:3.2rem}.am-padding-right{padding-right:1.6rem}.am-padding-right-0{padding-right:0!important}.am-padding-right-xs{padding-right:.5rem}.am-padding-right-sm{padding-right:1rem}.am-padding-right-lg{padding-right:2.4rem}.am-padding-right-xl{padding-right:3.2rem}@media only screen{.am-hide-lg,.am-hide-lg-only,.am-hide-lg-up,.am-hide-md,.am-hide-md-only,.am-hide-md-up,.am-show-lg-down,.am-show-md-down,.am-show-sm,.am-show-sm-down,.am-show-sm-only,.am-show-sm-up{display:inherit!important}.am-hide-lg-down,.am-hide-md-down,.am-hide-sm,.am-hide-sm-down,.am-hide-sm-only,.am-hide-sm-up,.am-show-lg,.am-show-lg-only,.am-show-lg-up,.am-show-md,.am-show-md-only,.am-show-md-up{display:none!important}table.am-hide-lg,table.am-hide-lg-only,table.am-hide-lg-up,table.am-hide-md,table.am-hide-md-only,table.am-hide-md-up,table.am-show-lg-down,table.am-show-md-down,table.am-show-sm,table.am-show-sm-down,table.am-show-sm-only,table.am-show-sm-up{display:table!important}thead.am-hide-lg,thead.am-hide-lg-only,thead.am-hide-lg-up,thead.am-hide-md,thead.am-hide-md-only,thead.am-hide-md-up,thead.am-show-lg-down,thead.am-show-md-down,thead.am-show-sm,thead.am-show-sm-down,thead.am-show-sm-only,thead.am-show-sm-up{display:table-header-group!important}tbody.am-hide-lg,tbody.am-hide-lg-only,tbody.am-hide-lg-up,tbody.am-hide-md,tbody.am-hide-md-only,tbody.am-hide-md-up,tbody.am-show-lg-down,tbody.am-show-md-down,tbody.am-show-sm,tbody.am-show-sm-down,tbody.am-show-sm-only,tbody.am-show-sm-up{display:table-row-group!important}tr.am-hide-lg,tr.am-hide-lg-only,tr.am-hide-lg-up,tr.am-hide-md,tr.am-hide-md-only,tr.am-hide-md-up,tr.am-show-lg-down,tr.am-show-md-down,tr.am-show-sm,tr.am-show-sm-down,tr.am-show-sm-only,tr.am-show-sm-up{display:table-row!important}td.am-hide-lg,td.am-hide-lg-only,td.am-hide-lg-up,td.am-hide-md,td.am-hide-md-only,td.am-hide-md-up,td.am-show-lg-down,td.am-show-md-down,td.am-show-sm,td.am-show-sm-down,td.am-show-sm-only,td.am-show-sm-up,th.am-hide-lg,th.am-hide-lg-only,th.am-hide-lg-up,th.am-hide-md,th.am-hide-md-only,th.am-hide-md-up,th.am-show-lg-down,th.am-show-md-down,th.am-show-sm,th.am-show-sm-down,th.am-show-sm-only,th.am-show-sm-up{display:table-cell!important}}@media only screen and (min-width:641px){.am-hide-lg,.am-hide-lg-only,.am-hide-lg-up,.am-hide-sm,.am-hide-sm-down,.am-hide-sm-only,.am-show-lg-down,.am-show-md,.am-show-md-down,.am-show-md-only,.am-show-md-up,.am-show-sm-up{display:inherit!important}.am-hide-lg-down,.am-hide-md,.am-hide-md-down,.am-hide-md-only,.am-hide-md-up,.am-hide-sm-up,.am-show-lg,.am-show-lg-only,.am-show-lg-up,.am-show-sm,.am-show-sm-down,.am-show-sm-only{display:none!important}table.am-hide-lg,table.am-hide-lg-only,table.am-hide-lg-up,table.am-hide-sm,table.am-hide-sm-down,table.am-hide-sm-only,table.am-show-lg-down,table.am-show-md,table.am-show-md-down,table.am-show-md-only,table.am-show-md-up,table.am-show-sm-up{display:table!important}thead.am-hide-lg,thead.am-hide-lg-only,thead.am-hide-lg-up,thead.am-hide-sm,thead.am-hide-sm-down,thead.am-hide-sm-only,thead.am-show-lg-down,thead.am-show-md,thead.am-show-md-down,thead.am-show-md-only,thead.am-show-md-up,thead.am-show-sm-up{display:table-header-group!important}tbody.am-hide-lg,tbody.am-hide-lg-only,tbody.am-hide-lg-up,tbody.am-hide-sm,tbody.am-hide-sm-down,tbody.am-hide-sm-only,tbody.am-show-lg-down,tbody.am-show-md,tbody.am-show-md-down,tbody.am-show-md-only,tbody.am-show-md-up,tbody.am-show-sm-up{display:table-row-group!important}tr.am-hide-lg,tr.am-hide-lg-only,tr.am-hide-lg-up,tr.am-hide-sm,tr.am-hide-sm-down,tr.am-hide-sm-only,tr.am-show-lg-down,tr.am-show-md,tr.am-show-md-down,tr.am-show-md-only,tr.am-show-md-up,tr.am-show-sm-up{display:table-row!important}td.am-hide-lg,td.am-hide-lg-only,td.am-hide-lg-up,td.am-hide-sm,td.am-hide-sm-down,td.am-hide-sm-only,td.am-show-lg-down,td.am-show-md,td.am-show-md-down,td.am-show-md-only,td.am-show-md-up,td.am-show-sm-up,th.am-hide-lg,th.am-hide-lg-only,th.am-hide-lg-up,th.am-hide-sm,th.am-hide-sm-down,th.am-hide-sm-only,th.am-show-lg-down,th.am-show-md,th.am-show-md-down,th.am-show-md-only,th.am-show-md-up,th.am-show-sm-up{display:table-cell!important}}@media only screen and (min-width:1025px){.am-hide-md,.am-hide-md-down,.am-hide-md-only,.am-hide-sm,.am-hide-sm-down,.am-hide-sm-only,.am-show-lg,.am-show-lg-down,.am-show-lg-only,.am-show-lg-up,.am-show-md-up,.am-show-sm-up{display:inherit!important}.am-hide-lg,.am-hide-lg-down,.am-hide-lg-only,.am-hide-lg-up,.am-hide-md-up,.am-hide-sm-up,.am-show-md,.am-show-md-down,.am-show-md-only,.am-show-sm,.am-show-sm-down,.am-show-sm-only{display:none!important}table.am-hide-md,table.am-hide-md-down,table.am-hide-md-only,table.am-hide-sm,table.am-hide-sm-down,table.am-hide-sm-only,table.am-show-lg,table.am-show-lg-down,table.am-show-lg-only,table.am-show-lg-up,table.am-show-md-up,table.am-show-sm-up{display:table!important}thead.am-hide-md,thead.am-hide-md-down,thead.am-hide-md-only,thead.am-hide-sm,thead.am-hide-sm-down,thead.am-hide-sm-only,thead.am-show-lg,thead.am-show-lg-down,thead.am-show-lg-only,thead.am-show-lg-up,thead.am-show-md-up,thead.am-show-sm-up{display:table-header-group!important}tbody.am-hide-md,tbody.am-hide-md-down,tbody.am-hide-md-only,tbody.am-hide-sm,tbody.am-hide-sm-down,tbody.am-hide-sm-only,tbody.am-show-lg,tbody.am-show-lg-down,tbody.am-show-lg-only,tbody.am-show-lg-up,tbody.am-show-md-up,tbody.am-show-sm-up{display:table-row-group!important}tr.am-hide-md,tr.am-hide-md-down,tr.am-hide-md-only,tr.am-hide-sm,tr.am-hide-sm-down,tr.am-hide-sm-only,tr.am-show-lg,tr.am-show-lg-down,tr.am-show-lg-only,tr.am-show-lg-up,tr.am-show-md-up,tr.am-show-sm-up{display:table-row!important}td.am-hide-md,td.am-hide-md-down,td.am-hide-md-only,td.am-hide-sm,td.am-hide-sm-down,td.am-hide-sm-only,td.am-show-lg,td.am-show-lg-down,td.am-show-lg-only,td.am-show-lg-up,td.am-show-md-up,td.am-show-sm-up,th.am-hide-md,th.am-hide-md-down,th.am-hide-md-only,th.am-hide-sm,th.am-hide-sm-down,th.am-hide-sm-only,th.am-show-lg,th.am-show-lg-down,th.am-show-lg-only,th.am-show-lg-up,th.am-show-md-up,th.am-show-sm-up{display:table-cell!important}}@media only screen and (orientation:landscape){.am-hide-portrait,.am-show-landscape{display:inherit!important}.am-hide-landscape,.am-show-portrait{display:none!important}}@media only screen and (orientation:portrait){.am-hide-landscape,.am-show-portrait{display:inherit!important}.am-hide-portrait,.am-show-landscape{display:none!important}}.am-sans-serif{font-family:"Segoe UI","Lucida Grande",Helvetica,Arial,"Microsoft YaHei",FreeSans,Arimo,"Droid Sans","wenquanyi micro hei","Hiragino Sans GB","Hiragino Sans GB W3",FontAwesome,sans-serif}.am-serif{font-family:Georgia,"Times New Roman",Times,SimSun,FontAwesome,serif}.am-kai{font-family:Georgia,"Times New Roman",Times,Kai,"Kaiti SC",KaiTi,BiauKai,FontAwesome,serif}.am-monospace{font-family:Monaco,Menlo,Consolas,"Courier New",FontAwesome,monospace}.am-text-primary{color:#0e90d2}.am-text-secondary{color:#3bb4f2}.am-text-success{color:#5eb95e}.am-text-warning{color:#F37B1D}.am-text-danger{color:#dd514c}.am-link-muted{color:#666}.am-link-muted a{color:#666}.am-link-muted a:hover,.am-link-muted:hover{color:#555}.am-text-default{font-size:1.6rem}.am-text-xs{font-size:1.2rem}.am-text-sm{font-size:1.4rem}.am-text-lg{font-size:1.8rem}.am-text-xl{font-size:2.4rem}.am-text-xxl{font-size:3.2rem}.am-text-xxxl{font-size:4.2rem}.am-ellipsis,.am-text-truncate{word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-text-break{word-wrap:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;-moz-hyphens:auto;hyphens:auto}.am-text-nowrap{white-space:nowrap}[class*=am-align-]{margin-bottom:1rem}.am-align-left{margin-right:1rem;float:left}.am-align-right{margin-left:1rem;float:right}.am-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}.am-text-ir{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}@media only screen{.am-text-left{text-align:left!important}.am-text-right{text-align:right!important}.am-text-center{text-align:center!important}.am-text-justify{text-align:justify!important}}@media only screen and (max-width:640px){.am-sm-only-text-left{text-align:left!important}.am-sm-only-text-right{text-align:right!important}.am-sm-only-text-center{text-align:center!important}.am-sm-only-text-justify{text-align:justify!important}}@media only screen and (min-width:641px) and (max-width:1024px){.am-md-only-text-left{text-align:left!important}.am-md-only-text-right{text-align:right!important}.am-md-only-text-center{text-align:center!important}.am-md-only-text-justify{text-align:justify!important}}@media only screen and (min-width:641px){.am-md-text-left{text-align:left!important}.am-md-text-right{text-align:right!important}.am-md-text-center{text-align:center!important}.am-md-text-justify{text-align:justify!important}}@media only screen and (min-width:1025px){.am-lg-text-left{text-align:left!important}.am-lg-text-right{text-align:right!important}.am-lg-text-center{text-align:center!important}.am-lg-text-justify{text-align:justify!important}}.am-text-top{vertical-align:top!important}.am-text-middle{vertical-align:middle!important}.am-text-bottom{vertical-align:bottom!important}.am-angle{position:absolute}.am-angle:after,.am-angle:before{position:absolute;display:block;content:"";width:0;height:0;border:8px dashed transparent;z-index:1}.am-angle-up{top:0}.am-angle-up:after,.am-angle-up:before{border-bottom-style:solid;border-width:0 8px 8px}.am-angle-up:before{border-bottom-color:#ddd;bottom:0}.am-angle-up:after{border-bottom-color:#fff;bottom:-1px}.am-angle-down{bottom:-9px}.am-angle-down:after,.am-angle-down:before{border-top-style:solid;border-width:8px 8px 0}.am-angle-down:before{border-top-color:#ddd;bottom:0}.am-angle-down:after{border-top-color:#fff;bottom:1px}.am-angle-left{left:-9px}.am-angle-left:after,.am-angle-left:before{border-right-style:solid;border-width:8px 8px 8px 0}.am-angle-left:before{border-right-color:#ddd;left:0}.am-angle-left:after{border-right-color:#fff;left:1px}.am-angle-right{right:0}.am-angle-right:after,.am-angle-right:before{border-left-style:solid;border-width:8px 0 8px 8px}.am-angle-right:before{border-left-color:#ddd;left:0}.am-angle-right:after{border-left-color:#fff;left:-1px}.am-alert{margin-bottom:1em;padding:.625em;background:#0e90d2;color:#fff;border:1px solid #0c7cb5;border-radius:0}.am-alert a{color:#fff}.am-alert h1,.am-alert h2,.am-alert h3,.am-alert h4,.am-alert h5,.am-alert h6{color:inherit}.am-alert .am-close{opacity:.4}.am-alert .am-close:hover{opacity:.6}*+.am-alert{margin-top:1em}.am-alert>:last-child{margin-bottom:0}.am-form-group .am-alert{margin:5px 0 0;padding:.25em .625em;font-size:1.3rem}.am-alert>.am-close:first-child{float:right;height:auto;margin:-3px -5px auto auto}.am-alert>.am-close:first-child+*{margin-top:0}.am-alert-secondary{background-color:#eee;border-color:#dfdfdf;color:#555}.am-alert-success{background-color:#5eb95e;border-color:#4bad4b;color:#fff}.am-alert-warning{background-color:#F37B1D;border-color:#e56c0c;color:#fff}.am-alert-danger{background-color:#dd514c;border-color:#d83832;color:#fff}.am-dropdown{position:relative;display:inline-block}.am-dropdown-toggle:focus{outline:0}.am-dropdown-content{position:absolute;top:100%;left:0;z-index:1020;display:none;float:left;min-width:160px;padding:15px;margin:9px 0 0;text-align:left;line-height:1.6;background-color:#fff;border:1px solid #ddd;border-radius:0;-webkit-background-clip:padding-box;background-clip:padding-box;-webkit-animation-duration:.15s;animation-duration:.15s}.am-dropdown-content:after,.am-dropdown-content:before{position:absolute;display:block;content:"";width:0;height:0;border:8px dashed transparent;z-index:1}.am-dropdown-content:after,.am-dropdown-content:before{border-bottom-style:solid;border-width:0 8px 8px}.am-dropdown-content:before{border-bottom-color:#ddd;bottom:0}.am-dropdown-content:after{border-bottom-color:#fff;bottom:-1px}.am-dropdown-content:after,.am-dropdown-content:before{left:10px;top:-8px;pointer-events:none}.am-dropdown-content:after{top:-7px}.am-active>.am-dropdown-content{display:block}.am-dropdown-content :first-child{margin-top:0}.am-dropdown-up .am-dropdown-content{top:auto;bottom:100%;margin:0 0 9px}.am-dropdown-up .am-dropdown-content:after,.am-dropdown-up .am-dropdown-content:before{border-bottom:none;border-top:8px solid #ddd;top:auto;bottom:-8px}.am-dropdown-up .am-dropdown-content:after{bottom:-7px;border-top-color:#fff}.am-dropdown-flip .am-dropdown-content{left:auto;right:0}.am-dropdown-flip .am-dropdown-content:after,.am-dropdown-flip .am-dropdown-content:before{left:auto;right:10px}ul.am-dropdown-content{list-style:none;padding:5px 0}ul.am-dropdown-content.am-fr{right:0;left:auto}ul.am-dropdown-content .am-divider{height:1px;margin:0rem 0;overflow:hidden;background-color:#e5e5e5}ul.am-dropdown-content>li>a{display:block;padding:6px 20px;clear:both;font-weight:400;color:#333;white-space:nowrap}ul.am-dropdown-content>li>a:focus,ul.am-dropdown-content>li>a:hover{text-decoration:none;color:#262626;background-color:#f5f5f5}ul.am-dropdown-content>.am-active>a,ul.am-dropdown-content>.am-active>a:focus,ul.am-dropdown-content>.am-active>a:hover{color:#fff;text-decoration:none;outline:0;background-color:#0e90d2}ul.am-dropdown-content>.am-disabled>a,ul.am-dropdown-content>.am-disabled>a:focus,ul.am-dropdown-content>.am-disabled>a:hover{color:#999}ul.am-dropdown-content>.am-disabled>a:focus,ul.am-dropdown-content>.am-disabled>a:hover{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.am-dropdown-header{display:block;padding:6px 20px;font-size:1.2rem;color:#999}.am-fr>.am-dropdown-content{right:0;left:auto}.am-fr>.am-dropdown-content:before{right:10px;left:auto}.am-dropdown-animation{-webkit-animation:am-dropdown-animation .15s ease-out;animation:am-dropdown-animation .15s ease-out}@-webkit-keyframes am-dropdown-animation{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}}@keyframes am-dropdown-animation{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-10px);transform:translateY(-10px)}}.am-slider a:focus,.am-slider a:hover{outline:0}.am-control-nav,.am-direction-nav,.am-slides{margin:0;padding:0;list-style:none}.am-slider{margin:0;padding:0}.am-slider .am-slides:after,.am-slider .am-slides:before{content:" ";display:table}.am-slider .am-slides:after{clear:both}.am-slider .am-slides>li{display:none;-webkit-backface-visibility:hidden;position:relative}.no-js .am-slider .am-slides>li:first-child{display:block}.am-slider .am-slides img{width:100%;display:block}.am-pauseplay span{text-transform:capitalize}.am-slider{position:relative}.am-viewport{-webkit-transition:all 1s ease;transition:all 1s ease}.am-slider-carousel li{margin-right:5px}.am-control-nav{position:absolute}.am-control-nav li{display:inline-block}.am-control-thumbs{position:static;overflow:hidden}.am-control-thumbs img{-webkit-transition:all 1s ease;transition:all 1s ease}.am-slider-slide .am-slides>li{display:none;position:relative}@media all and (transform-3d),(-webkit-transform-3d){.am-slider-slide .am-slides>li{-webkit-transition:-webkit-transform .6s ease-in-out;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.am-slider-slide .am-slides>li.active.right,.am-slider-slide .am-slides>li.next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0}.am-slider-slide .am-slides>li.active.left,.am-slider-slide .am-slides>li.prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0}.am-slider-slide .am-slides>li.active,.am-slider-slide .am-slides>li.next.left,.am-slider-slide .am-slides>li.prev.right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);left:0}}.am-slider-slide .am-slides>.active,.am-slider-slide .am-slides>.next,.am-slider-slide .am-slides>.prev{display:block}.am-slider-slide .am-slides>.active{left:0}.am-slider-slide .am-slides>.next,.am-slider-slide .am-slides>.prev{position:absolute;top:0;width:100%}.am-slider-slide .am-slides>.next{left:100%}.am-slider-slide .am-slides>.prev{left:-100%}.am-slider-slide .am-slides>.next.left,.am-slider-slide .am-slides>.prev.right{left:0}.am-slider-slide .am-slides>.active.left{left:-100%}.am-slider-slide .am-slides>.active.right{left:100%}.am-slider-default{margin:0 0 20px;background-color:#fff;border-radius:2px;-webkit-box-shadow:0 0 2px rgba(0,0,0,.15);box-shadow:0 0 2px rgba(0,0,0,.15)}.am-slider-default .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-default .am-viewport{max-height:300px}.am-slider-default .carousel li{margin-right:5px}.am-slider-default .am-direction-nav a{position:absolute;top:50%;z-index:10;display:block;width:36px;height:36px;margin:-18px 0 0;overflow:hidden;opacity:.45;cursor:pointer;color:rgba(0,0,0,.65);-webkit-transition:all .3s ease;transition:all .3s ease}.am-slider-default .am-direction-nav a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);width:100%;color:#333;content:"\f137";font-size:24px!important;text-align:center;line-height:36px!important;height:36px}.am-slider-default .am-direction-nav a.am-next:before{content:"\f138"}.am-slider-default .am-direction-nav .am-prev{left:10px}.am-slider-default .am-direction-nav .am-next{right:10px;text-align:right}.am-slider-default .am-direction-nav .am-disabled{opacity:0!important;cursor:default}.am-slider-default:hover .am-prev{opacity:.7;left:10px}.am-slider-default:hover .am-prev:hover{opacity:1}.am-slider-default:hover .am-next{opacity:.7;right:10px}.am-slider-default:hover .am-next:hover{opacity:1}.am-slider-default .am-pauseplay a{display:block;width:20px;height:20px;position:absolute;bottom:5px;left:10px;opacity:.8;z-index:10;overflow:hidden;cursor:pointer;color:#000}.am-slider-default .am-pauseplay a::before{font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);font-size:20px;display:inline-block;content:"\f04c"}.am-slider-default .am-pauseplay a:hover{opacity:1}.am-slider-default .am-pauseplay a.am-play::before{content:"\f04b"}.am-slider-default .am-slider-desc{background-color:rgba(0,0,0,.7);position:absolute;bottom:0;padding:10px;width:100%;color:#fff}.am-slider-default .am-control-nav{width:100%;position:absolute;bottom:-15px;text-align:center}.am-slider-default .am-control-nav li{margin:0 6px;display:inline-block}.am-slider-default .am-control-nav li a{width:8px;height:8px;display:block;background-color:#666;background-color:rgba(0,0,0,.5);line-height:0;font-size:0;cursor:pointer;text-indent:-9999px;border-radius:20px;-webkit-box-shadow:inset 0 0 3px rgba(0,0,0,.3);box-shadow:inset 0 0 3px rgba(0,0,0,.3)}.am-slider-default .am-control-nav li a:hover{background-color:#333;background-color:rgba(0,0,0,.7)}.am-slider-default .am-control-nav li a.am-active{background-color:#000;background-color:#0e90d2;cursor:default}.am-slider-default .am-control-thumbs{margin:5px 0 0;position:static;overflow:hidden}.am-slider-default .am-control-thumbs li{width:25%;float:left;margin:0}.am-slider-default .am-control-thumbs img{width:100%;height:auto;display:block;opacity:.7;cursor:pointer}.am-slider-default .am-control-thumbs img:hover{opacity:1}.am-slider-default .am-control-thumbs .am-active{opacity:1;cursor:default}.am-slider-default .am-control-thumbs i{position:absolute}.am-modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1110;display:none;opacity:0;outline:0;text-align:center;-webkit-transform:scale(1.185);-ms-transform:scale(1.185);transform:scale(1.185);-webkit-transition-property:opacity,-webkit-transform;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.am-modal:focus{outline:0}.am-modal.am-modal-active{opacity:1;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1);overflow-x:hidden;overflow-y:auto}.am-modal.am-modal-out{opacity:0;z-index:1109;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:scale(.815);-ms-transform:scale(.815);transform:scale(.815)}.am-modal:before{content:"\200B";display:inline-block;height:100%;vertical-align:middle}.am-modal-dialog{position:relative;display:inline-block;vertical-align:middle;margin-left:auto;margin-right:auto;width:270px;max-width:100%;border-radius:0;background:#f8f8f8}@media only screen and (min-width:641px){.am-modal-dialog{width:540px}}.am-modal-hd{padding:15px 10px 5px 10px;font-size:1.8rem;font-weight:500}.am-modal-hd+.am-modal-bd{padding-top:0}.am-modal-hd .am-close{position:absolute;top:4px;right:4px}.am-modal-bd{padding:15px 10px;text-align:center;border-bottom:1px solid #dedede;border-radius:2px 2px 0 0}.am-modal-bd+.am-modal-bd{margin-top:5px}.am-modal-prompt-input{display:block;margin:5px auto 0 auto;border-radius:0;padding:5px;line-height:1.8rem;width:80%;border:1px solid #dedede;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none}.am-modal-prompt-input:focus{outline:0;border-color:#d6d6d6}.am-modal-footer{height:44px;overflow:hidden;display:table;width:100%;border-collapse:collapse}.am-modal-btn{display:table-cell!important;padding:0 5px;height:44px;-webkit-box-sizing:border-box!important;box-sizing:border-box!important;font-size:1.6rem;line-height:44px;text-align:center;color:#0e90d2;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;cursor:pointer;border-right:1px solid #dedede}.am-modal-btn:first-child{border-radius:0}.am-modal-btn:last-child{border-right:none;border-radius:0}.am-modal-btn:first-child:last-child{border-radius:0}.am-modal-btn.am-modal-btn-bold{font-weight:500}.am-modal-btn:active{background:#d4d4d4}.am-modal-btn+.am-modal-btn{border-left:1px solid #dedede}.am-modal-no-btn .am-modal-dialog{border-radius:0;border-bottom:none}.am-modal-no-btn .am-modal-bd{border-bottom:none}.am-modal-no-btn .am-modal-footer{display:none}.am-modal-loading .am-modal-bd{border-bottom:none}.am-modal-loading .am-icon-spin{display:inline-block;font-size:2.4rem}.am-modal-loading .am-modal-footer{display:none}.am-modal-actions{position:fixed;left:0;bottom:0;z-index:1110;width:100%;max-height:100%;overflow-x:hidden;overflow-y:auto;text-align:center;border-radius:0;-webkit-transform:translateY(100%);-ms-transform:translateY(100%);transform:translateY(100%);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s}.am-modal-actions.am-modal-active{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}.am-modal-actions.am-modal-out{z-index:1109;-webkit-transform:translateY(100%);-ms-transform:translateY(100%);transform:translateY(100%)}.am-modal-actions-group{margin:10px}.am-modal-actions-group .am-list{margin:0;border-radius:0}.am-modal-actions-group .am-list>li{margin-bottom:0;border-bottom:none;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;-webkit-box-shadow:inset 0 1px 0 rgba(0,0,0,.015);box-shadow:inset 0 1px 0 rgba(0,0,0,.015)}.am-modal-actions-group .am-list>li>a{padding:1rem;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-modal-actions-group .am-list>li:first-child{border-top:none;border-top-right-radius:0;border-top-left-radius:0}.am-modal-actions-group .am-list>li:last-child{border-bottom:none;border-bottom-right-radius:0;border-bottom-left-radius:0}.am-modal-actions-header{padding:1rem;color:#999;font-size:1.4rem}.am-modal-actions-danger{color:#dd514c}.am-modal-actions-danger a{color:inherit}.am-popup{position:fixed;left:0;top:0;width:100%;height:100%;z-index:1110;background:#fff;display:none;overflow:hidden;-webkit-transition-property:-webkit-transform;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;-webkit-transform:translateY(100%);-ms-transform:translateY(100%);transform:translateY(100%)}.am-popup.am-modal-active,.am-popup.am-modal-out{-webkit-transition-duration:.3s;transition-duration:.3s}.am-popup.am-modal-active{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}.am-popup.am-modal-out{-webkit-transform:translateY(100%);-ms-transform:translateY(100%);transform:translateY(100%)}@media all and (min-width:630px) and (min-height:630px){.am-popup{width:630px;height:630px;left:50%;top:50%;margin-left:-315px;margin-top:-315px;-webkit-transform:translateY(1024px);-ms-transform:translateY(1024px);transform:translateY(1024px)}.am-popup.am-modal-active{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}.am-popup.am-modal-out{-webkit-transform:translateY(1024px);-ms-transform:translateY(1024px);transform:translateY(1024px)}}.am-popup-inner{padding-top:44px;height:100%;overflow:auto;-webkit-overflow-scrolling:touch}.am-popup-hd{position:absolute;top:0;z-index:1000;width:100%;height:43px;border-bottom:1px solid #dedede;background-color:#fff}.am-popup-hd .am-popup-title{font-size:1.8rem;font-weight:700;line-height:43px;text-align:center;margin:0 30px;color:#333;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-popup-hd .am-close{position:absolute;right:10px;top:8px;cursor:pointer;-webkit-transition:all .3s;transition:all .3s;color:#999}.am-popup-hd .am-close:hover{-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);color:#555}.am-popup-bd{padding:15px;background:#f8f8f8;color:#555}.am-offcanvas{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1090;background:rgba(0,0,0,.15)}.am-offcanvas.am-active{display:block}.am-offcanvas-page{position:fixed;-webkit-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out}.am-offcanvas-bar{position:fixed;top:0;bottom:0;left:0;z-index:1091;width:270px;max-width:100%;background:#333;overflow-y:auto;-webkit-overflow-scrolling:touch;-webkit-transition:-webkit-transform .3s ease-in-out;transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;transition:transform .3s ease-in-out,-webkit-transform .3s ease-in-out;-webkit-transform:translateX(-100%);-ms-transform:translateX(-100%);transform:translateX(-100%)}.am-offcanvas-bar:after{content:"";display:block;position:absolute;top:0;bottom:0;right:0;width:1px;background:#262626}.am-offcanvas.am-active .am-offcanvas-bar.am-offcanvas-bar-active{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}.am-offcanvas-bar-flip{left:auto;right:0;-webkit-transform:translateX(100%);-ms-transform:translateX(100%);transform:translateX(100%)}.am-offcanvas-bar-flip:after{right:auto;left:0}.am-offcanvas-content{padding:15px;color:#999}.am-offcanvas-content a{color:#ccc}.am-popover{position:absolute;top:0;left:0;margin:0;border-radius:0;background:#333;color:#fff;border:1px solid #333;display:none;font-size:1.6rem;z-index:1150;opacity:0;-webkit-transition:opacity .3s;transition:opacity .3s}.am-popover.am-active{display:block!important;opacity:1}.am-popover-inner{position:relative;background:#333;padding:8px;z-index:110}.am-popover-caret{position:absolute;top:0;z-index:100;display:inline-block;width:0;height:0;vertical-align:middle;border-bottom:8px solid #333;border-right:8px solid transparent;border-left:8px solid transparent;border-top:0 dotted;-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);overflow:hidden}.am-popover-top .am-popover-caret{top:auto;bottom:-8px;-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.am-popover-bottom .am-popover-caret{top:-8px}.am-popover-bottom .am-popover-caret,.am-popover-top .am-popover-caret{left:50%;margin-left:-8px}.am-popover-left .am-popover-caret{top:auto;left:auto;right:-12px;-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.am-popover-right .am-popover-caret{right:auto;left:-12px;-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);transform:rotate(-90deg)}.am-popover-left .am-popover-caret,.am-popover-right .am-popover-caret{top:50%;margin-top:-4px}.am-popover-sm{font-size:1.4rem}.am-popover-sm .am-popover-inner{padding:5px}.am-popover-lg{font-size:1.8rem}.am-popover-primary{border-color:#0e90d2}.am-popover-primary .am-popover-inner{background:#0e90d2}.am-popover-primary .am-popover-caret{border-bottom-color:#0e90d2}.am-popover-secondary{border-color:#3bb4f2}.am-popover-secondary .am-popover-inner{background:#3bb4f2}.am-popover-secondary .am-popover-caret{border-bottom-color:#3bb4f2}.am-popover-success{border-color:#5eb95e}.am-popover-success .am-popover-inner{background:#5eb95e}.am-popover-success .am-popover-caret{border-bottom-color:#5eb95e}.am-popover-warning{border-color:#F37B1D}.am-popover-warning .am-popover-inner{background:#F37B1D}.am-popover-warning .am-popover-caret{border-bottom-color:#F37B1D}.am-popover-danger{border-color:#dd514c}.am-popover-danger .am-popover-inner{background:#dd514c}.am-popover-danger .am-popover-caret{border-bottom-color:#dd514c}#nprogress{pointer-events:none}#nprogress .nprogress-bar{position:fixed;top:0;left:0;z-index:2000;width:100%;height:2px;background:#5eb95e}#nprogress .nprogress-peg{display:block;position:absolute;right:0;width:100px;height:100%;-webkit-box-shadow:0 0 10px #5eb95e,0 0 5px #5eb95e;box-shadow:0 0 10px #5eb95e,0 0 5px #5eb95e;opacity:1;-webkit-transform:rotate(3deg) translate(0,-4px);-ms-transform:rotate(3deg) translate(0,-4px);transform:rotate(3deg) translate(0,-4px)}#nprogress .nprogress-spinner{position:fixed;top:15px;right:15px;z-index:2000;display:block}#nprogress .nprogress-spinner-icon{width:18px;height:18px;-webkit-box-sizing:border-box;box-sizing:border-box;border:solid 2px transparent;border-top-color:#5eb95e;border-left-color:#5eb95e;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.am-tabs-bd{position:relative;overflow:hidden;border:1px solid #ddd;border-top:none;z-index:100;-webkit-transition:height .3s;transition:height .3s}.am-tabs-bd:after,.am-tabs-bd:before{content:" ";display:table}.am-tabs-bd:after{clear:both}.am-tabs-bd .am-tab-panel{position:absolute;top:0;z-index:99;float:left;width:100%;padding:10px 10px 15px;visibility:hidden;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:translateX(-100%);-ms-transform:translateX(-100%);transform:translateX(-100%)}.am-tabs-bd .am-tab-panel *{-webkit-user-drag:none}.am-tabs-bd .am-tab-panel.am-active{position:relative;z-index:100;visibility:visible;-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}.am-tabs-bd .am-tab-panel.am-active~.am-tab-panel{-webkit-transform:translateX(100%);-ms-transform:translateX(100%);transform:translateX(100%)}.am-tabs-bd .am-tabs-bd{border:none}.am-tabs-bd-ofv{overflow:visible}.am-tabs-bd-ofv>.am-tab-panel{display:none}.am-tabs-bd-ofv>.am-tab-panel.am-active{display:block}.am-tabs-fade .am-tab-panel{opacity:0;-webkit-transition:opacity .25s linear;transition:opacity .25s linear}.am-tabs-fade .am-tab-panel.am-in{opacity:1}.am-share{font-size:14px}.am-share-title{padding:10px 0 0;margin:0 10px;font-weight:400;text-align:center;color:#555;background-color:#f8f8f8;border-bottom:1px solid #fff;border-top-right-radius:2px;border-top-left-radius:2px}.am-share-title:after{content:"";display:block;width:100%;height:0;margin-top:10px;border-bottom:1px solid #dfdfdf}.am-share-sns{margin:0 10px;padding-top:15px;background-color:#f8f8f8;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.am-share-sns li{margin-bottom:15px}.am-share-sns a{display:block;color:#555}.am-share-sns span{display:block}.am-share-sns [class*=am-icon]{background-color:#3bb4f2;border-radius:50%;width:36px;height:36px;line-height:36px;color:#fff;margin-bottom:5px;font-size:18px}.am-share-sns .am-icon-weibo{background-color:#ea1328}.am-share-sns .am-icon-qq{background-color:#009cda}.am-share-sns .am-icon-star{background-color:#ffc028}.am-share-sns .am-icon-tencent-weibo{background-color:#23ccfe}.am-share-sns .am-icon-wechat,.am-share-sns .am-icon-weixin{background-color:#44b549}.am-share-sns .am-icon-renren{background-color:#105ba3}.am-share-sns .am-icon-comment{background-color:#5eb95e}.am-share-footer{margin:10px}.am-share-footer .am-btn{color:#555}.am-share-wechat-qr{font-size:14px;color:#777}.am-share-wechat-qr .am-modal-dialog{background-color:#fff;border:1px solid #dedede}.am-share-wechat-qr .am-modal-hd{padding-top:10px;text-align:left;margin-bottom:10px}.am-share-wechat-qr .am-share-wx-qr{margin-bottom:10px}.am-share-wechat-qr .am-share-wechat-tip{text-align:left}.am-share-wechat-qr .am-share-wechat-tip em{color:#dd514c;font-weight:700;font-style:normal;margin-left:3px;margin-right:3px}.am-pureview{position:fixed;left:0;top:0;bottom:0;right:0;z-index:1120;width:100%;height:100%;background:rgba(0,0,0,.95);display:none;overflow:hidden;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:translate(0,100%);-ms-transform:translate(0,100%);transform:translate(0,100%)}.am-pureview.am-active{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.am-pureview ol,.am-pureview ul{list-style:none;padding:0;margin:0;width:100%}.am-pureview-slider{overflow:hidden;height:100%}.am-pureview-slider li{position:absolute;width:100%;height:100%;top:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;vertical-align:middle;-webkit-transition:all .3s linear;transition:all .3s linear;z-index:100;visibility:hidden}.am-pureview-slider li.am-pureview-slide-prev{-webkit-transform:translate(-100%,0);-ms-transform:translate(-100%,0);transform:translate(-100%,0);z-index:109}.am-pureview-slider li.am-pureview-slide-next{-webkit-transform:translate(100%,0);-ms-transform:translate(100%,0);transform:translate(100%,0);z-index:109}.am-pureview-slider li.am-active{position:relative;z-index:110;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);visibility:visible}.am-pureview-slider .pinch-zoom-container{width:100%;z-index:1121}.am-pureview-slider .am-pinch-zoom{position:relative;width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.am-pureview-slider .am-pinch-zoom:after{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f110";-webkit-animation:icon-spin 2s infinite linear;animation:icon-spin 2s infinite linear;font-size:24px;line-height:24px;color:#eee;position:absolute;top:50%;left:50%;margin-left:-12px;margin-top:-12px;z-index:1}.am-pureview-slider .am-pinch-zoom.am-pureview-loaded:after{display:none}.am-pureview-slider img{position:relative;display:block;max-width:100%;max-height:100%;opacity:0;z-index:200;-webkit-user-drag:none;-webkit-transition:opacity .2s ease-in;transition:opacity .2s ease-in}.am-pureview-slider img.am-img-loaded{opacity:1}.am-pureview-direction{position:absolute;top:50%;width:100%;margin-top:-18px!important;z-index:1122}.am-pureview-only .am-pureview-direction,.am-touch .am-pureview-direction{display:none}.am-pureview-direction li{position:absolute;width:36px;height:36px}.am-pureview-direction a{display:block;height:36px;border:none;color:#ccc;opacity:.5;cursor:pointer;text-align:center;z-index:1125}.am-pureview-direction a:before{content:"\f137";line-height:36px;font-size:24px}.am-pureview-direction a:hover{opacity:1}.am-pureview-direction .am-pureview-prev{left:15px}.am-pureview-direction .am-pureview-next{right:15px}.am-pureview-direction .am-pureview-next a:before{content:"\f138"}.am-pureview-bar{position:absolute;bottom:0;height:45px;width:100%;background-color:rgba(0,0,0,.35);color:#eee;line-height:45px;padding:0 10px;font-size:14px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.am-pureview-bar .am-pureview-title{display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;margin-left:6px;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.am-pureview-bar .am-pureview-total{font-size:10px;line-height:48px}.am-pureview-actions{position:absolute;z-index:1130;left:0;right:0;top:0;height:45px;background-color:rgba(0,0,0,.35)}.am-pureview-actions a{position:absolute;left:10px;color:#ccc;display:block;width:45px;line-height:45px;text-align:left;font-size:16px}.am-pureview-actions a:hover{color:#fff}.am-pureview-actions [data-am-toggle=share]{left:auto;right:10px}.am-pureview-actions,.am-pureview-bar{opacity:0;-webkit-transition:all .15s;transition:all .15s;z-index:1130}.am-pureview-bar-active .am-pureview-actions,.am-pureview-bar-active .am-pureview-bar{opacity:1}.am-pureview-nav{position:absolute;bottom:15px;left:0;right:0;text-align:center;z-index:1131}.am-pureview-bar-active .am-pureview-nav{display:none}.am-pureview-nav li{display:inline-block;background:#ccc;background:rgba(255,255,255,.5);width:8px;height:8px;margin:0 3px;border-radius:50%;text-indent:-9999px;overflow:hidden;cursor:pointer}.am-pureview-nav .am-active{background:#fff;background:rgba(255,255,255,.9)}[data-am-pureview] img{cursor:pointer}.am-pureview-active{overflow:hidden}.ath-viewport *{-webkit-box-sizing:border-box;box-sizing:border-box}.ath-viewport{position:relative;z-index:2147483641;pointer-events:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}.ath-modal{pointer-events:auto!important;background:rgba(0,0,0,.6)}.ath-mandatory{background:#000}.ath-container{pointer-events:auto!important;position:absolute;z-index:2147483641;padding:.7em .6em;width:18em;background:#eee;-webkit-background-size:100% auto;background-size:100% auto;-webkit-box-shadow:0 .2em 0 #d1d1d1;box-shadow:0 .2em 0 #d1d1d1;font-family:sans-serif;font-size:15px;line-height:1.5em;text-align:center}.ath-container small{font-size:.8em;line-height:1.3em;display:block;margin-top:.5em}.ath-ios.ath-phone{bottom:1.8em;left:50%;margin-left:-9em}.ath-ios6.ath-tablet{left:5em;top:1.8em}.ath-ios7.ath-tablet{left:.7em;top:1.8em}.ath-ios8.ath-tablet{right:.4em;top:1.8em}.ath-android{bottom:1.8em;left:50%;margin-left:-9em}.ath-container:before{content:'';position:relative;display:block;float:right;margin:-.7em -.6em 0 .5em;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAACECAMAAABmmnOVAAAAdVBMVEUAAAA5OTkzMzM7Ozs3NzdBQUFAQEA/Pz8+Pj5BQUFAQEA/Pz8+Pj5BQUFAQEA/Pz9BQUE+Pj4/Pz8/Pz8+Pj4/Pz8/Pz8/Pz8+Pj4/Pz8+Pj4/Pz8/Pz8/Pz8/Pz8/Pz8+Pj4/Pz8/Pz8/Pz8/Pz9AQEA/Pz+fdCaPAAAAJnRSTlMACQoNDjM4OTo7PEFCQ0RFS6ytsbS1tru8vcTFxu7x8vX19vf4+C5yomAAAAJESURBVHgBvdzLTsJAGEfxr4C2KBcVkQsIDsK8/yPaqIsPzVlyzrKrX/5p0kkXEz81L23otc9NpIbbWia2YVLqdnhlqFlhGWpSDHe1aopsSIpRb8gK0dC3G30b9rVmhWZIimTICsvQtx/FsuYOrWHoDjX3Gu31gzJxdki934WrAIOsAIOsAIOiAMPhPsJTgKGN0BVsYIVsYIVpYIVpYIVpYIVpYIVpYIVpYIVpYIVlAIVgEBRs8BRs8BRs8BRs8BRs8BRs8BRs8BRTNmgKNngKNngKNngKNngKNhiKGxgiOlZoBlaYBlaYBlaYBlaYBlaYBlaYBlaYBlZIBlBMfQMrVAMr2KAqBENSHFHhGEABhi5CV6gGUKgGUKgGUKgGUFwuqgEUvoEVsoEVpoEUpgEUggF+gKTKY+h1fxSlC7/Z+RrxOQ3fcEoAPPHZBlaYBlaYBlaYBlZYBlYIhvLBCstw7PgM7hkiWOEZWGEaWGEaWGEaIsakEAysmHkGVpxmvoEVqoEVpoEVpoEVpoEVpoEVpoEVkoEVgkFQsEFSsEFQsGEcoSvY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnY4CnmbNAUT2c2WAo2eAo2eAo2eAo2eAo2eArNEPFACjZ4CjZ4CjZ4CjaIird/rBvFH6llNCvewdli1URWCIakSIZesUaDoFg36dKFWk9zCZDei3TtwmCj7pC22AwikiIZPEU29IpFNliKxa/hC9DFITjQPYhcAAAAAElFTkSuQmCC);background-color:rgba(255,255,255,.8);-webkit-background-size:50% 50%;background-size:50%;background-repeat:no-repeat;background-position:50%;width:2.7em;height:2.7em;text-align:center;overflow:hidden;color:#a33;z-index:2147483642}.ath-container.ath-icon:before{position:absolute;top:0;right:0;margin:0;float:none}.ath-mandatory .ath-container:before{display:none}.ath-container.ath-android:before{float:left;margin:-.7em .5em 0 -.6em}.ath-container.ath-android.ath-icon:before{position:absolute;right:auto;left:0;margin:0;float:none}.ath-action-icon{display:inline-block;vertical-align:middle;background-position:50%;background-repeat:no-repeat;text-indent:-9999em;overflow:hidden}.ath-ios7 .ath-action-icon,.ath-ios8 .ath-action-icon{width:1.6em;height:1.6em;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAACtCAYAAAB7l7tOAAAF6UlEQVR4AezZWWxUZRiH8VcQEdxZEFFiUZBFUCIa1ABBDARDcCciYGKMqTEGww3SOcNSAwQTjOBiiIpEhRjAhRgXRC8MFxojEhAFZUGttVhaoSxlaW3n8W3yXZxm6vTrOMM5Q98n+V9MMu1pvl++uZhKuypghu49KaaTWGdZSYoVN6VD95nMpLNYZ9XNbdQR2od2k88O3Gm6Bh0t7H0p5Vwp2Ax3ajpu2tYbciFWwkTFO63DY6+JcI4USFaSyYpWp8N7SVZJKR3EinkBk9JxvZFXxhnZSjBaoWp1ZL0ES8WKYXMZp0AndORgy8WKFe5Yf1zvvSBWDEpys2LU6MjD5kmEWQlGKsJRHXlcqUSQVcItEnDEA6gAb7LhjvD9WO6yIEfICQI5A1nzGCYB1T4og5bBiFcyv2f6ujYhl4iVxwKG6qp8MK55HsqPwK0rMr9v/yEo3uCPrJstVh5KMER30Aeh31Ioq0FrHfjXw9CYghnrvYFTuqfEymFzGSwBlT4ARYr7u+K6GLmCVGvAGg2NMG0d/sgJnpScZLjXSkC5z8H3eQ72/k24Q8NfzvwFyK4qtuJSZKaubRPyE/K/Mtx+EvCHL+7uasId1t10w0scz/RzSzYzAfgKV30D3LPaG7lRkR8RK4tKKJKAMp+D7r0EfmmOe0x3m2itAc/ZxBjgAt1mXHWKPPkdb+QGSTJdrDaU5EoJ2OtzwD0WwY7KNNzbRfMFFg24WPdtGHnS221Cflgsj56hjwTs8TnY7oq7/QDhjutGicsb2AVcovsO18l6uPPNNiE/JFaGAq7Q7fY50G4LYVtz3FrdaNGyBXbIl+q24DqhyHes9EaulwR3SwtZs+ktAT/7HORliru1gnCndONFyx44Dfn7MPLYN7yR6yTJZAllJeguAT/4HOBFz8I3ZWm4E0TLFbBD7qn7EVdtHYx53R9ZN0ksrZRuErDN5+AuLIWvm+Oe1k0ULdfADrmX7idcR0/DyBXeyCdlLuMMOGCBz4F1ng+f7yFcve5e0fIFHELeiav6BAx70Rt5p0yhY3u/wR0kyarW/uX35b403PtFyzewQ75ctwtXzSkY8WqruHslSV8RscrL6TJ1bcvfWJ0/HzbtIdw/ugdFyzdwOOAq3T6fmzxwGQ3vbmO8iFioIWqYSsHMj9M/ljfuTsOdItoZBXYBfXX7cVXVwvXLm/8+fU3lcdCqdEMNGBbgUmRmfQISQKd5sGEn4VK6YtEiAXYBA3QVuA4q8hCHrDcafR1ul65jewfuovsCl7vJrNlOuEbdo6JFCuwCrtb9hqusBu56Cw4cI1y1briIWEBn3Ue0XKPuMdGiBg4H9NdV0HJ/6QZLOEPmPN0GmpfSPS5arIBdwHUtIFfoBsl/ZsgfhHCfFi2WwC5goO4AmvanbqBkzJA76tboZokWa2AXMEi3RTdAvDLkDqJFAhzB32xFD2wZsGXA0WfAlgFbBmwZsGXAlgFbBpzk04JaKb0iA9ZnF9x5SQAFtRKKIgPWZxfaeRmwAZ/BGbAB37eaG6MCbnq2Aed5czYyKirgpmcbsAHHZAZswN0Wwo7KeG1fFf2jAm56dtzOQ42yB+65mDhWFBUwUETMUiMDNmADbp/APRaTAh6I2bpGCNw1bufRZJQ1cPdF/NueHZsgDEBBGLbMGoIu4AZu5gLOZeEaYmEXeznF3jRPyEv4frgJvvJe3qTefY0AAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwb8rwADBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgz4/sz1Nia/9hizA7zgklwy3RYwYMBzBRjw4bPjxAbAAizAAtwgwAIswAIswAIMGDBgARZgARZgAS4FWIAFWIAFWIABAwYswAIswAIswIUAC7AAC7AACzBgwIAFWIAFWIAFuBBgARZgARZgAQYMGPApQ99ZCdgWtzqwATbABtgAG2DbnxNb7zbRimsMLMACrDf2wMWI/WasfQAAAABJRU5ErkJggg==);margin-top:-.3em;-webkit-background-size:auto 100%;background-size:auto 100%}.ath-ios6 .ath-action-icon{width:1.8em;height:1.8em;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAAB0CAQAAADAmnOnAAAAAnNCSVQICFXsRgQAAAAJcEhZcwAAWwEAAFsBAXkZiFwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAF4klEQVR4Ae3a/a+XdR3H8ec5HM45HDmKICoVohkZsxESRRCzcZM/2JKkdGR5MrSkleA0Pd00O4u5IVuNM2yYc6XSzCExU4oUNRPCJFdMUAhsYZpUGhscOHA4N8/WZzsL6HBxvofvdV3fa3yer//gsV3vH659KHzncBsJxUYhDzOEhCKQbORs+ip2wzgM+wvj+P9i35qAGLaHGcQSgKSTrxBLABJppZpYApCspoFYApBsZjSxBCD5OxOJJQBJG1cQSwCSLpqJJQCJ3MvgCGTinuSMCJS8LZwfgZL3FtMiUPIOcU0ESl4PLRHoRPsJtREoeRsYGYGS9yrvo6RmpbLaigWSfzOdErLs6+bLUMFA0sF1+QF1cz1UNlBYK9V5AHXyWSgEkKyiIWOgGh829Ki1lLcaxjCVK7mJRSxjBY+zgRf/u9pXcMB7jhEZAg32EUP3O6hMKOP5Iq2sZQeHMZXt5KKMgOpcY+iHVnFyjeQKlrCBdsxge5ieAVC9vzLUelI8H+A7bKIHM10H81IGGuKvDf1ggDxVTKOV1zG3/Yia1ICG+ltD32MgNTKfP2HuW0VDKkCNrjfUTOm9i6XswwrZJkaVHeh0f2fodkrtfO6jAytqrzG+rEDDfVG1x1sprZEs5RBW4PZxeT+Bbrf5hPu9arfzKaU6WjiAFbseWvoF1GW/6vYGSmkyW7Dit4xB5QHq9Br6Xx2t9GAhtp6zkoHsfNp1J9wX6H+jeR4LtJc4LxGopZZyNpN/YcG2mw9nBTSPLizgOmjKAujGgvJID3ekD7QYi7nGzkvmQtpA38Vi7iJf0TedlC7QTVjMfcY2QyvSBPpUMW/PIBfbo9pls1XpAX2EdizeznStob3OJpQO0DB2YfE21q2GtnghpAm0Gou3T9tm6BGHQppA12HRVt17eboNlydNoLHsx2JtmL801OYcQmkC/QKLtQt9ydBW3wNpA30ci7Ur3WdolUMhbaBqNhf/8qQJ9Hkszs5wjaH9XkUobaAqtmFRdoGbDb3sWMgG6DIs5852knO82RaXer+P+qyb3eWeo7ZNBrRZvm1otY2QFdBjeHIb6hTne49Put12+9ObMoDdYmfy5UkF6AK6cCCr9aM2u9IddptcOYCG+FNDB5xLKCugO7G01TndFp/xgAntdYvrfdwVLnORt3q9Vx25F27DUjbGPxr6qxMgW6Cd2N+d6wLXedA+6nKbK73Lr/pJxzusvE/wZrvX0FOOgGyBxmF/dprXutYOj6nNdS6xyYnWp/dGcaGdhr5vDWQN9E1MXrUzfcA2j2qPj/l1J1uT9iPOeh8w1O7nCGUN9HzyGZ7ndo9qp0ucanU2r1xH+wdDu5wIeQDVVx0+/kd1i697RNv8thdn+Qz4Uv9p6DeOhHyApmBfq3OBu+3Nfd7nVELZAX3Nw4ZarYG8gG7GY1dlk6/Zm3/2Rk8jlB1QvT82dNAmQjkBVf8Mj957fdrefM7ZVhPKEuidvmDob06CXIGGbsX/bZDf8KAhfdbJhLIGmuZuQ084HHIGatiLvRvrRkP6qldbBXkAzbfD0N0OhryBGqrEMOd50FC7d1hPKGugBh8ydMh5hPIGGouI1d5lj6F1vptQ9kDvcKOhN5wMlQH0QcRGnzC03yZCeQDN9G1D6xwBFQI07FI8x02GdjgB8gJqttPQcmuhYoAumzvG7YZWejrkA1TrPYYO+SVCFQO0aM4bqj0uJJQH0LluSP7PkyeQU9QOmyAvoBm+Zegpz4LKA/qYB/wE5AXUe3m81zqoRKAPOYWcuvP9dxvqcD6h7IAKkaNU3eUlHLcI9EzS5YlAi62h/zUy89QCqqKUmvgHywsJlEHnsQYxAvXVIJo5gIhnPhiBju1iNmLvLn85Ah1ZPYs5jBGo72awEzEC9dVwHqQHI9DxWoAYgSLQQKteGIESu/qhCJTYtT+PQBEoAkWgCBSBkotAEehUWwSKQBEoAkWg/BeBIlAEikARKAJFoFmealu4gVLy1Gt5dkARKAL9BzujPSurTmu/AAAAAElFTkSuQmCC);margin-bottom:.4em;-webkit-background-size:100% auto;background-size:100% auto}.ath-android .ath-action-icon{width:1.4em;height:1.4em;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAANlBMVEVmZmb///9mZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZW6fJrAAAAEXRSTlMAAAYHG21ub8fLz9DR8/T4+RrZ9owAAAB3SURBVHja7dNLDoAgDATQWv4gKve/rEajJOJiWLgg6WzpSyB0aHqHiNj6nL1lovb4C+hYzkSNAT7mryQFAVOeGAj4CjwEtgrWXpD/uZKtwEJApXt+Vn0flzRhgNiFZQkOXY0aADQZCOCPlsZJ46Rx0jhp3IiN2wGDHhxtldrlwQAAAABJRU5ErkJggg==);-webkit-background-size:100% auto;background-size:100% auto}.ath-container p{margin:0;padding:0;position:relative;z-index:2147483642;text-shadow:0 .1em 0 #fff;font-size:1.1em}.ath-ios.ath-phone:after{content:'';background:#eee;position:absolute;width:2em;height:2em;bottom:-.9em;left:50%;margin-left:-1em;-webkit-transform:scaleX(.9) rotate(45deg);-ms-transform:scaleX(.9) rotate(45deg);transform:scaleX(.9) rotate(45deg);-webkit-box-shadow:.2em .2em 0 #d1d1d1;box-shadow:.2em .2em 0 #d1d1d1}.ath-ios.ath-tablet:after{content:'';background:#eee;position:absolute;width:2em;height:2em;top:-.9em;left:50%;margin-left:-1em;-webkit-transform:scaleX(.9) rotate(45deg);-ms-transform:scaleX(.9) rotate(45deg);transform:scaleX(.9) rotate(45deg);z-index:2147483641}.ath-application-icon{position:relative;padding:0;border:0;margin:0 auto .2em auto;height:6em;width:6em;z-index:2147483642}.ath-container.ath-ios .ath-application-icon{border-radius:1em;-webkit-box-shadow:0 .2em .4em rgba(0,0,0,.3),inset 0 .07em 0 rgba(255,255,255,.5);box-shadow:0 .2em .4em rgba(0,0,0,.3),inset 0 .07em 0 rgba(255,255,255,.5);margin:0 auto .4em auto}@media only screen and (orientation:landscape){.ath-container.ath-phone{width:24em}.ath-android.ath-phone{margin-left:-12em}.ath-ios.ath-phone{margin-left:-12em}.ath-ios6:after{left:39%}.ath-ios8.ath-phone{left:auto;bottom:auto;right:.4em;top:1.8em}.ath-ios8.ath-phone:after{bottom:auto;top:-.9em;left:68%;z-index:2147483641;-webkit-box-shadow:none;box-shadow:none}}.am-checkbox,.am-checkbox-inline,.am-radio,.am-radio-inline{padding-left:22px;position:relative;-webkit-transition:color .25s linear;transition:color .25s linear;font-size:14px;line-height:1.5}label.am-checkbox,label.am-radio{font-weight:400}.am-ucheck-icons{color:#999;display:block;height:20px;top:0;left:0;position:absolute;width:20px;text-align:center;line-height:21px;font-size:18px;cursor:pointer}.am-checkbox .am-icon-checked,.am-checkbox .am-icon-unchecked,.am-checkbox-inline .am-icon-checked,.am-checkbox-inline .am-icon-unchecked,.am-radio .am-icon-checked,.am-radio .am-icon-unchecked,.am-radio-inline .am-icon-checked,.am-radio-inline .am-icon-unchecked{position:absolute;left:0;top:0;display:inline-table;margin:0;background-color:transparent;-webkit-transition:color .25s linear;transition:color .25s linear}.am-checkbox .am-icon-checked:before,.am-checkbox .am-icon-unchecked:before,.am-checkbox-inline .am-icon-checked:before,.am-checkbox-inline .am-icon-unchecked:before,.am-radio .am-icon-checked:before,.am-radio .am-icon-unchecked:before,.am-radio-inline .am-icon-checked:before,.am-radio-inline .am-icon-unchecked:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.am-checkbox .am-icon-checked,.am-checkbox-inline .am-icon-checked,.am-radio .am-icon-checked,.am-radio-inline .am-icon-checked{opacity:0}.am-checkbox .am-icon-checked:before,.am-checkbox-inline .am-icon-checked:before{content:"\f046"}.am-checkbox .am-icon-unchecked:before,.am-checkbox-inline .am-icon-unchecked:before{content:"\f096"}.am-radio .am-icon-checked:before,.am-radio-inline .am-icon-checked:before{content:"\f192"}.am-radio .am-icon-unchecked:before,.am-radio-inline .am-icon-unchecked:before{content:"\f10c"}.am-ucheck-checkbox,.am-ucheck-radio{position:absolute;left:0;top:0;margin:0;padding:0;width:20px;height:20px;opacity:0;outline:0!important}.am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons{color:#0e90d2}.am-ucheck-checkbox:checked+.am-ucheck-icons,.am-ucheck-radio:checked+.am-ucheck-icons{color:#0e90d2}.am-ucheck-checkbox:checked+.am-ucheck-icons .am-icon-unchecked,.am-ucheck-radio:checked+.am-ucheck-icons .am-icon-unchecked{opacity:0}.am-ucheck-checkbox:checked+.am-ucheck-icons .am-icon-checked,.am-ucheck-radio:checked+.am-ucheck-icons .am-icon-checked{opacity:1}.am-ucheck-checkbox:disabled+.am-ucheck-icons,.am-ucheck-radio:disabled+.am-ucheck-icons{cursor:default;color:#d8d8d8}.am-ucheck-checkbox:disabled:checked+.am-ucheck-icons .am-icon-unchecked,.am-ucheck-radio:disabled:checked+.am-ucheck-icons .am-icon-unchecked{opacity:0}.am-ucheck-checkbox:disabled:checked+.am-ucheck-icons .am-icon-checked,.am-ucheck-radio:disabled:checked+.am-ucheck-icons .am-icon-checked{opacity:1;color:#d8d8d8}.am-checkbox-inline.am-secondary .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox-inline.am-secondary .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox.am-secondary .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox.am-secondary .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio-inline.am-secondary .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio-inline.am-secondary .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio.am-secondary .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio.am-secondary .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons{color:#3bb4f2}.am-checkbox-inline.am-secondary .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-checkbox-inline.am-secondary .am-ucheck-radio:checked+.am-ucheck-icons,.am-checkbox.am-secondary .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-checkbox.am-secondary .am-ucheck-radio:checked+.am-ucheck-icons,.am-radio-inline.am-secondary .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-radio-inline.am-secondary .am-ucheck-radio:checked+.am-ucheck-icons,.am-radio.am-secondary .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-radio.am-secondary .am-ucheck-radio:checked+.am-ucheck-icons{color:#3bb4f2}.am-checkbox-inline.am-success .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox-inline.am-success .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox.am-success .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox.am-success .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio-inline.am-success .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio-inline.am-success .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio.am-success .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio.am-success .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons{color:#5eb95e}.am-checkbox-inline.am-success .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-checkbox-inline.am-success .am-ucheck-radio:checked+.am-ucheck-icons,.am-checkbox.am-success .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-checkbox.am-success .am-ucheck-radio:checked+.am-ucheck-icons,.am-radio-inline.am-success .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-radio-inline.am-success .am-ucheck-radio:checked+.am-ucheck-icons,.am-radio.am-success .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-radio.am-success .am-ucheck-radio:checked+.am-ucheck-icons{color:#5eb95e}.am-checkbox-inline.am-warning .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox-inline.am-warning .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox.am-warning .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox.am-warning .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio-inline.am-warning .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio-inline.am-warning .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio.am-warning .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio.am-warning .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons{color:#F37B1D}.am-checkbox-inline.am-warning .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-checkbox-inline.am-warning .am-ucheck-radio:checked+.am-ucheck-icons,.am-checkbox.am-warning .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-checkbox.am-warning .am-ucheck-radio:checked+.am-ucheck-icons,.am-radio-inline.am-warning .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-radio-inline.am-warning .am-ucheck-radio:checked+.am-ucheck-icons,.am-radio.am-warning .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-radio.am-warning .am-ucheck-radio:checked+.am-ucheck-icons{color:#F37B1D}.am-checkbox-inline.am-danger .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox-inline.am-danger .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox.am-danger .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-checkbox.am-danger .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio-inline.am-danger .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio-inline.am-danger .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio.am-danger .am-ucheck-checkbox:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons,.am-radio.am-danger .am-ucheck-radio:hover:not(.am-nohover):not(:disabled)+.am-ucheck-icons{color:#dd514c}.am-checkbox-inline.am-danger .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-checkbox-inline.am-danger .am-ucheck-radio:checked+.am-ucheck-icons,.am-checkbox.am-danger .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-checkbox.am-danger .am-ucheck-radio:checked+.am-ucheck-icons,.am-radio-inline.am-danger .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-radio-inline.am-danger .am-ucheck-radio:checked+.am-ucheck-icons,.am-radio.am-danger .am-ucheck-checkbox:checked+.am-ucheck-icons,.am-radio.am-danger .am-ucheck-radio:checked+.am-ucheck-icons{color:#dd514c}.am-field-error+.am-ucheck-icons{color:#dd514c}.am-field-valid+.am-ucheck-icons{color:#5eb95e}.am-selected{width:200px}.am-selected-btn{width:100%;padding-left:10px;text-align:right}.am-selected-btn.am-btn-default{background:0 0}.am-invalid .am-selected-btn{border-color:#dd514c}.am-selected-header{height:45px;background-color:#f2f2f2;border-bottom:1px solid #ddd;display:none}.am-selected-status{text-align:left;width:100%;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-selected-content{padding:10px 0}.am-selected-search{padding:0 10px 10px}.am-selected-search .am-form-field{padding:.5em}.am-selected-list{margin:0;padding:0;list-style:none;font-size:1.5rem}.am-selected-list li{position:relative;cursor:pointer;padding:5px 10px;-webkit-transition:background-color .15s;transition:background-color .15s}.am-selected-list li:hover{background-color:#f8f8f8}.am-selected-list li:hover .am-icon-check{opacity:.6}.am-selected-list li.am-checked .am-icon-check{opacity:1;color:#0e90d2}.am-selected-list li.am-disabled{opacity:.5;pointer-events:none;cursor:not-allowed}.am-selected-list .am-selected-list-header{margin-top:8px;font-size:1.3rem;color:#999;border-bottom:1px solid #e5e5e5;cursor:default}.am-selected-list .am-selected-list-header:hover{background:0 0}.am-selected-list .am-selected-list-header:first-child{margin-top:0}.am-selected-list .am-selected-text{display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;margin-right:30px}.am-selected-list .am-icon-check{position:absolute;right:8px;top:5px;color:#999;opacity:0;-webkit-transition:opacity .15s;transition:opacity .15s}.am-selected-hint{line-height:1.2;color:#dd514c}.am-selected-hint:not(:empty){margin-top:10px;border-top:1px solid #e5e5e5;padding:10px 10px 0}.am-selected-placeholder{opacity:.65}.am-fade{opacity:0;-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.am-fade.am-in{opacity:1}.am-collapse{display:none}.am-collapse.am-in{display:block}tr.am-collapse.am-in{display:table-row}tbody.am-collapse.am-in{display:table-row-group}.am-collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .3s ease;transition:height .3s ease}.am-sticky{position:fixed!important;z-index:1010;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}[data-am-sticky][class*=am-animation-]{-webkit-animation-duration:.2s;animation-duration:.2s}.am-dimmer-active{overflow:hidden}.am-dimmer{position:fixed;top:0;right:0;bottom:0;left:0;display:none;width:100%;height:100%;background-color:rgba(0,0,0,.6);z-index:1100;opacity:0}.am-dimmer.am-active{opacity:1}[data-am-collapse]{cursor:pointer}.am-datepicker{top:0;left:0;border-radius:0;background:#fff;-webkit-box-shadow:0 0 10px #ccc;box-shadow:0 0 10px #ccc;padding-bottom:10px;margin-top:10px;width:238px;color:#555;display:none}.am-datepicker>div{display:none}.am-datepicker table{width:100%}.am-datepicker tr.am-datepicker-header{font-size:1.6rem;color:#fff;background:#3bb4f2}.am-datepicker td,.am-datepicker th{text-align:center;font-weight:400;cursor:pointer}.am-datepicker th{height:48px}.am-datepicker td{font-size:1.4rem}.am-datepicker td.am-datepicker-day{height:34px;width:34px}.am-datepicker td.am-datepicker-day:hover{background:#F0F0F0;height:34px;width:34px}.am-datepicker td.am-datepicker-day.am-disabled{cursor:no-drop;color:#999;background:#fafafa}.am-datepicker td.am-datepicker-new,.am-datepicker td.am-datepicker-old{color:#89d7ff}.am-datepicker td.am-active,.am-datepicker td.am-active:hover{border-radius:0;color:#0084c7;background:#F0F0F0}.am-datepicker td span{display:block;width:79.33px;height:40px;line-height:40px;float:left;cursor:pointer}.am-datepicker td span:hover{background:#F0F0F0}.am-datepicker td span.am-active{color:#0084c7;background:#F0F0F0}.am-datepicker td span.am-disabled{cursor:no-drop;color:#999;background:#fafafa}.am-datepicker td span.am-datepicker-old{color:#89d7ff}.am-datepicker .am-datepicker-dow{height:40px;color:#0c80ba}.am-datepicker-caret{display:block!important;display:inline-block;width:0;height:0;vertical-align:middle;border-bottom:7px solid #3bb4f2;border-right:7px solid transparent;border-left:7px solid transparent;border-top:0 dotted;-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);position:absolute;top:-7px;left:6px}.am-datepicker-right .am-datepicker-caret{left:auto;right:7px}.am-datepicker-up .am-datepicker-caret{top:auto;bottom:-7px;display:inline-block;width:0;height:0;vertical-align:middle;border-top:7px solid #fff;border-right:7px solid transparent;border-left:7px solid transparent;border-bottom:0 dotted;-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}.am-datepicker-select{height:34px;line-height:34px;text-align:center;-webkit-transition:background-color .3s ease-out;transition:background-color .3s ease-out}.am-datepicker-select:hover{background:rgba(154,217,248,.5);color:#0c80ba}.am-datepicker-next,.am-datepicker-prev{width:34px;height:34px}.am-datepicker-next-icon,.am-datepicker-prev-icon{width:34px;height:34px;line-height:34px;display:inline-block;-webkit-transition:background-color .3s ease-out;transition:background-color .3s ease-out}.am-datepicker-next-icon:hover,.am-datepicker-prev-icon:hover{background:rgba(154,217,248,.5);color:#0c80ba}.am-datepicker-prev-icon:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f053"}.am-datepicker-next-icon:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f054"}.am-datepicker-dropdown{position:absolute;z-index:1120}@media only screen and (max-width:640px){.am-datepicker{width:100%}.am-datepicker td span{width:33.33%}.am-datepicker-caret{display:none!important}.am-datepicker-next,.am-datepicker-prev{width:44px;height:44px}}.am-datepicker-success tr.am-datepicker-header{background:#5eb95e}.am-datepicker-success td.am-datepicker-day.am-disabled{color:#999}.am-datepicker-success td.am-datepicker-new,.am-datepicker-success td.am-datepicker-old{color:#94df94}.am-datepicker-success td.am-active,.am-datepicker-success td.am-active:hover{color:#1b961b}.am-datepicker-success td span.am-datepicker-old{color:#94df94}.am-datepicker-success td span.am-active{color:#1b961b}.am-datepicker-success .am-datepicker-caret{border-bottom-color:#5eb95e}.am-datepicker-success .am-datepicker-dow{color:#367b36}.am-datepicker-success .am-datepicker-next-icon:hover,.am-datepicker-success .am-datepicker-prev-icon:hover,.am-datepicker-success .am-datepicker-select:hover{background:rgba(165,216,165,.5);color:#367b36}.am-datepicker-danger tr.am-datepicker-header{background:#dd514c}.am-datepicker-danger td.am-datepicker-day.am-disabled{color:#999}.am-datepicker-danger td.am-datepicker-new,.am-datepicker-danger td.am-datepicker-old{color:#f59490}.am-datepicker-danger td.am-active,.am-datepicker-danger td.am-active:hover{color:#c10802}.am-datepicker-danger td span.am-datepicker-old{color:#f59490}.am-datepicker-danger td span.am-active{color:#c10802}.am-datepicker-danger .am-datepicker-caret{border-bottom-color:#dd514c}.am-datepicker-danger .am-datepicker-dow{color:#a4241f}.am-datepicker-danger .am-datepicker-next-icon:hover,.am-datepicker-danger .am-datepicker-prev-icon:hover,.am-datepicker-danger .am-datepicker-select:hover{background:rgba(237,164,162,.5);color:#a4241f}.am-datepicker-warning tr.am-datepicker-header{background:#F37B1D}.am-datepicker-warning td.am-datepicker-day.am-disabled{color:#999}.am-datepicker-warning td.am-datepicker-new,.am-datepicker-warning td.am-datepicker-old{color:#ffad6d}.am-datepicker-warning td.am-active,.am-datepicker-warning td.am-active:hover{color:#aa4b00}.am-datepicker-warning td span.am-datepicker-old{color:#ffad6d}.am-datepicker-warning td span.am-active{color:#aa4b00}.am-datepicker-warning .am-datepicker-caret{border-bottom-color:#F37B1D}.am-datepicker-warning .am-datepicker-dow{color:#a14c09}.am-datepicker-warning .am-datepicker-next-icon:hover,.am-datepicker-warning .am-datepicker-prev-icon:hover,.am-datepicker-warning .am-datepicker-select:hover{background:rgba(248,180,126,.5);color:#a14c09}.am-datepicker>div{display:block}.am-datepicker>div span.am-datepicker-hour{width:59.5px}.am-datepicker-date{display:block}.am-datepicker-date.am-input-group{display:table}.am-datepicker-time-box{padding:30px 0 30px 0}.am-datepicker-time-box strong{font-size:5.2rem;display:inline-block;height:70px;width:70px;line-height:70px;font-weight:400}.am-datepicker-time-box strong:hover{border-radius:4px;background:#ECECEC}.am-datepicker-time-box em{display:inline-block;height:70px;width:20px;line-height:70px;font-size:5.2rem;font-style:normal}.am-datepicker-toggle{text-align:center;cursor:pointer;padding:10px 0}.am-datepicker-toggle:hover{background:#f0f0f0}@media print{*,:after,:before{background:0 0!important;color:#000!important;-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" [" attr(title) "] "}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{margin:.5cm}select{background:#fff!important}.am-topbar{display:none}.am-table td,.am-table th{background-color:#fff!important}.am-table{border-collapse:collapse!important}.am-table-bordered td,.am-table-bordered th{border:1px solid #ddd!important}}.am-print-block{display:none!important}@media print{.am-print-block{display:block!important}}.am-print-inline{display:none!important}@media print{.am-print-inline{display:inline!important}}.am-print-inline-block{display:none!important}@media print{.am-print-inline-block{display:inline-block!important}}@media print{.am-print-hide{display:none!important}}.lte9 #nprogress .nprogress-spinner{display:none!important}.lte8 .am-dimmer{background-color:#000;filter:alpha(opacity=60)}.lte8 .am-modal-actions{display:none}.lte8 .am-modal-actions.am-modal-active{display:block}.lte8 .am-offcanvas.am-active{background:#000}.lte8 .am-popover .am-popover-caret{border:8px solid transparent}.lte8 .am-popover-top .am-popover-caret{border-top:8px solid #333;border-bottom:none}.lte8 .am-popover-left .am-popover-caret{right:-8px;margin-top:-6px;border-left:8px solid #333;border-right:none}.lte8 .am-popover-right .am-popover-caret{left:-8px;margin-top:-6px;border-right:8px solid #333;border-left:none}.am-accordion-item{margin:0}.am-accordion-title{font-weight:400;cursor:pointer}.am-accordion-item.am-disabled .am-accordion-title{cursor:default;pointer-events:none}.am-accordion-bd{margin:0!important;padding:0!important;border:none!important}.am-accordion-content{margin-top:0;padding:.8rem 1rem 1.2rem;font-size:1.4rem}.am-accordion-default{margin:1rem;border-radius:2px;-webkit-box-shadow:0 0 0 1px rgba(0,0,0,.1);box-shadow:0 0 0 1px rgba(0,0,0,.1)}.am-accordion-default .am-accordion-item{border-top:1px solid rgba(0,0,0,.05)}.am-accordion-default .am-accordion-item:first-child{border-top:none}.am-accordion-default .am-accordion-title{color:rgba(0,0,0,.6);-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;padding:.8rem 1rem}.am-accordion-default .am-accordion-title:before{content:"\f0da";display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);-webkit-transition:-webkit-transform .2s ease;transition:-webkit-transform .2s ease;transition:transform .2s ease;transition:transform .2s ease,-webkit-transform .2s ease;-webkit-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);margin-right:5px}.am-accordion-default .am-accordion-title:hover{color:#0e90d2}.am-accordion-default .am-accordion-content{color:#666}.am-accordion-default .am-active .am-accordion-title{background-color:#eee;color:#0e90d2}.am-accordion-default .am-active .am-accordion-title:before{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.am-accordion-basic{margin:1rem}.am-accordion-basic .am-accordion-title{color:#333;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;padding:.8rem 0 0}.am-accordion-basic .am-accordion-title:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f0da";-webkit-transition:-webkit-transform .2s ease;transition:-webkit-transform .2s ease;transition:transform .2s ease;transition:transform .2s ease,-webkit-transform .2s ease;-webkit-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0);margin-right:.5rem}.am-accordion-basic .am-accordion-content{color:#666}.am-accordion-basic .am-active .am-accordion-title{color:#0e90d2}.am-accordion-basic .am-active .am-accordion-title:before{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.am-accordion-gapped{margin:.5rem 1rem}.am-accordion-gapped .am-accordion-item{border:1px solid #dedede;border-bottom:none;margin:.5rem 0}.am-accordion-gapped .am-accordion-item.am-active{border-bottom:1px solid #dedede}.am-accordion-gapped .am-accordion-title{color:rgba(0,0,0,.6);-webkit-transition:background-color .15s ease-out;transition:background-color .15s ease-out;border-bottom:1px solid #dedede;padding:.8rem 2rem .8rem 1rem;position:relative}.am-accordion-gapped .am-accordion-title:after{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f105";-webkit-transition:-webkit-transform .2s linear;transition:-webkit-transform .2s linear;transition:transform .2s linear;transition:transform .2s linear,-webkit-transform .2s linear;position:absolute;right:10px;top:50%;margin-top:-.8rem}.am-accordion-gapped .am-accordion-title:hover{color:rgba(0,0,0,.8)}.am-accordion-gapped .am-accordion-content{color:#666}.am-accordion-gapped .am-active .am-accordion-title{background-color:#f5f5f5;color:rgba(0,0,0,.8)}.am-accordion-gapped .am-active .am-accordion-title:after{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.am-divider{height:0;margin:1.5rem auto;overflow:hidden;clear:both}.am-divider-default{border-top:1px solid #ddd}.am-divider-dotted{border-top:1px dotted #ccc}.am-divider-dashed{border-top:1px dashed #ccc}.am-figure-zoomable{position:relative;cursor:pointer}.am-figure-zoomable:after{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f00e";position:absolute;top:1rem;right:1rem;color:#999;font-size:1.6rem;-webkit-transition:all .2s;transition:all .2s;pointer-events:none}.am-figure-zoomable:hover:after{color:#eee}.am-figure-default{margin:10px}.am-figure-default img{display:block;max-width:100%;height:auto;padding:2px;border:1px solid #eee;margin:10px auto}.am-figure-default figcaption{text-align:center;font-size:1.4rem;margin-bottom:15px;color:#333}.am-footer{text-align:center;padding:1em 0;font-size:1.6rem}.am-footer .am-switch-mode-ysp{cursor:pointer}.am-footer .am-footer-text{margin-top:10px;font-size:14px}.am-footer .am-footer-text-left{text-align:left;padding-left:10px}.am-modal-footer-hd{padding-bottom:10px}.am-footer-default{background-color:#fff}.am-footer-default a{color:#555}.am-footer-default .am-footer-switch{margin-bottom:10px;font-weight:700}.am-footer-default .am-footer-ysp{color:#555;cursor:pointer}.am-footer-default .am-footer-divider{color:#ccc}.am-footer-default .am-footer-desktop{color:#0e90d2}.am-footer-default .am-footer-miscs{color:#999;font-size:13px}.am-footer-default .am-footer-miscs p{margin:5px 0}@media only screen and (min-width:641px){.am-footer-default .am-footer-miscs p{display:inline-block;margin:5px}}.am-gallery{padding:5px 5px 0 5px;list-style:none}.am-gallery h3{margin:0}[data-am-gallery*=pureview] img{cursor:pointer}.am-gallery-default>li{padding:5px}.am-gallery-default .am-gallery-item img{width:100%;height:auto}.am-gallery-default .am-gallery-title{margin-top:10px;font-weight:400;font-size:1.4rem;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;color:#555}.am-gallery-default .am-gallery-desc{color:#999;font-size:1.2rem}.am-gallery-overlay>li{padding:5px}.am-gallery-overlay .am-gallery-item{position:relative}.am-gallery-overlay .am-gallery-item img{width:100%;height:auto}.am-gallery-overlay .am-gallery-title{font-weight:400;font-size:1.4rem;color:#FFF;position:absolute;bottom:0;width:100%;background-color:rgba(0,0,0,.5);text-indent:5px;height:30px;line-height:30px;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-gallery-overlay .am-gallery-desc{display:none}.am-gallery-bordered>li{padding:5px}.am-gallery-bordered .am-gallery-item{-webkit-box-shadow:0 0 3px rgba(0,0,0,.35);box-shadow:0 0 3px rgba(0,0,0,.35);padding:5px}.am-gallery-bordered .am-gallery-item img{width:100%;height:auto}.am-gallery-bordered .am-gallery-title{margin-top:10px;font-weight:400;font-size:1.4rem;color:#555;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-gallery-bordered .am-gallery-desc{color:#999;font-size:1.2rem}.am-gallery-imgbordered>li{padding:5px}.am-gallery-imgbordered .am-gallery-item img{width:100%;height:auto;border:3px solid #FFF;-webkit-box-shadow:0 0 3px rgba(0,0,0,.35);box-shadow:0 0 3px rgba(0,0,0,.35)}.am-gallery-imgbordered .am-gallery-title{margin-top:10px;font-weight:400;font-size:1.4rem;color:#555;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-gallery-imgbordered .am-gallery-desc{color:#999;font-size:1.2rem}.am-gotop a{display:inline-block;text-decoration:none}.am-gotop-default{text-align:center;margin:10px 0}.am-gotop-default a{background-color:#0e90d2;padding:.5em 1.5em;border-radius:0;color:#fff}.am-gotop-default a img{display:none}.am-gotop-fixed{position:fixed;right:10px;bottom:10px;z-index:1010;opacity:0;width:32px;min-height:32px;overflow:hidden;border-radius:0;text-align:center}.am-gotop-fixed.am-active{opacity:.9}.am-gotop-fixed.am-active:hover{opacity:1}.am-gotop-fixed a{display:block}.am-gotop-fixed .am-gotop-title{display:none}.am-gotop-fixed .am-gotop-icon-custom{display:inline-block;max-width:30px;vertical-align:middle}.am-gotop-fixed .am-gotop-icon{width:100%;line-height:32px;background-color:#555;vertical-align:middle;color:#ddd}.am-gotop-fixed .am-gotop-icon:hover{color:#fff}.am-with-fixed-navbar .am-gotop-fixed{bottom:60px}.am-header{position:relative;width:100%;height:49px;line-height:49px;padding:0 10px}.am-header h1{margin-top:0;margin-bottom:0}.am-header .am-header-title{margin:0 30%;font-size:2rem;font-weight:400;text-align:center;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-header .am-header-title img{margin-top:12px;height:25px;vertical-align:top}.am-header .am-header-nav{position:absolute;top:0}.am-header .am-header-nav img{height:16px;width:auto;vertical-align:middle}.am-header .am-header-left{left:10px}.am-header .am-header-right{right:10px}.am-header-fixed{position:fixed;top:0;left:0;right:0;width:100%;z-index:1010}.am-with-fixed-header{padding-top:49px}.am-header-default{background-color:#0e90d2}.am-header-default .am-header-title{color:#fff}.am-header-default .am-header-title a{color:#fff}.am-header-default .am-header-icon{font-size:20px}.am-header-default .am-header-nav{color:#eee}.am-header-default .am-header-nav>a{display:inline-block;min-width:36px;text-align:center;color:#eee}.am-header-default .am-header-nav>a+a{margin-left:5px}.am-header-default .am-header-nav .am-btn{margin-top:9px;height:31px;padding:0 .5em;line-height:30px;font-size:14px;vertical-align:top}.am-header-default .am-header-nav .am-btn .am-header-icon{font-size:inherit}.am-header-default .am-header-nav .am-btn-default{color:#999}.am-header-default .am-header-nav-title,.am-header-default .am-header-nav-title+.am-header-icon{font-size:14px}.am-intro{position:relative}.am-intro img{max-width:100%}.am-intro-hd{position:relative;height:45px;line-height:45px}.am-intro-title{font-size:18px;margin:0;font-weight:700}.am-intro-more-top{position:absolute;right:10px;top:0;font-size:1.4rem}.am-intro-bd{padding-top:15px;padding-bottom:15px;font-size:1.4rem}.am-intro-bd p:last-child{margin-bottom:0}.am-intro-more-bottom{clear:both;text-align:center}.am-intro-more-bottom .am-btn{font-size:14px}.am-intro-default .am-intro-hd{background-color:#0e90d2;color:#fff;padding:0 10px}.am-intro-default .am-intro-hd a{color:#eee}.am-intro-default .am-intro-right{padding-left:0}.am-list-news-hd{padding-top:1.2rem;padding-bottom:.8rem}.am-list-news-hd a{display:block}.am-list-news-hd h2{font-size:1.6rem;float:left;margin:0;height:2rem;line-height:2rem}.am-list-news-hd h3{margin:0}.am-list-news-hd .am-list-news-more{font-size:1.3rem;height:2rem;line-height:2rem}.am-list .am-list-item-dated a{padding-right:80px;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-list .am-list-item-dated a::after{display:none}.am-list .am-list-item-desced a,.am-list .am-list-item-thumbed a{padding-right:0}.am-list-news .am-list-item-hd{margin:0}.am-list-date{position:absolute;right:5px;font-size:1.3rem;top:1.3rem}.am-list-item-desced{padding-bottom:1rem}.am-list-item-desced>a{padding:1rem 0}.am-list-item-desced .am-list-date{position:static}.am-list-item-thumbed{padding-top:1em}.am-list-news-ft{text-align:center}.am-list-news .am-titlebar{margin-left:0;margin-right:0}.am-list-news .am-titlebar~.am-list-news-bd .am-list>li:first-child{border-top:none}.am-list-news-default{margin:10px}.am-list-news-default .am-g{margin-left:auto;margin-right:auto}.am-list-news-default .am-list-item-hd{font-weight:400}.am-list-news-default .am-list-date{color:#999}.am-list-news-default .am-list>li{border-color:#dedede}.am-list-news-default .am-list .am-list-item-desced{padding-top:1rem;padding-bottom:1rem}.am-list-news-default .am-list .am-list-item-desced>a{padding:0}.am-list-news-default .am-list .am-list-item-desced .am-list-item-text{margin-top:.5rem;color:#757575}.am-list-news-default .am-list .am-list-item-text{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;line-height:1.3em;-webkit-line-clamp:2;max-height:2.6em}.am-list-news-default .am-list .am-list-item-thumb-top .am-list-thumb{padding:0;margin-bottom:.8rem}.am-list-news-default .am-list .am-list-item-thumb-top .am-list-main{padding:0}.am-list-news-default .am-list .am-list-item-thumb-left .am-list-thumb{padding-left:0}.am-list-news-default .am-list .am-list-item-desced .am-list-main{padding:0}.am-list-news-default .am-list .am-list-item-thumb-right .am-list-thumb{padding-right:0}.am-list-news-default .am-list .am-list-item-thumb-bottom-left .am-list-item-hd{clear:both;padding-bottom:.5rem}.am-list-news-default .am-list .am-list-item-thumb-bottom-left .am-list-thumb{padding-left:0}.am-list-news-default .am-list .am-list-item-thumb-bottom-right .am-list-item-hd{clear:both;padding-bottom:.5rem}.am-list-news-default .am-list .am-list-item-thumb-bottom-right .am-list-thumb{padding-right:0}.am-list-news-default .am-list .am-list-thumb img{width:100%;display:block}@media only screen and (max-width:640px){.am-list-news-default .am-list-item-thumb-left .am-list-thumb,.am-list-news-default .am-list-item-thumb-right .am-list-thumb{max-height:80px;overflow:hidden}.am-list-news-default .am-list-item-thumb-bottom-left .am-list-item-text,.am-list-news-default .am-list-item-thumb-bottom-right .am-list-item-text{-webkit-line-clamp:3;max-height:3.9em}.am-list-news-default .am-list-item-thumb-bottom-left .am-list-thumb,.am-list-news-default .am-list-item-thumb-bottom-right .am-list-thumb{max-height:60px;overflow:hidden}}.am-map{width:100%;height:300px}.am-map-default #bd-map{width:100%;height:100%;overflow:hidden;margin:0;font-size:14px;line-height:1.4!important}.am-map-default .BMap_bubble_title{font-weight:700}.am-map-default #BMap_mask{width:100%}.am-mechat{margin:1rem}.am-mechat .section-cbox-wap .cbox-post-wap .post-action-wap .action-function-wap .function-list-wap .list-upload-wap .upload-mutual-wap{-webkit-box-sizing:content-box;box-sizing:content-box}.am-menu{position:relative;padding:0;margin:0}.am-menu ul{padding:0;margin:0}.am-menu li{list-style:none}.am-menu a:after,.am-menu a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.am-menu-sub{z-index:1050}.am-menu-toggle{display:none;z-index:1015}.am-menu-toggle img{display:inline-block;height:16px;width:auto;vertical-align:middle}.am-menu-nav a{display:block;padding:.8rem 0;-webkit-transition:all .45s;transition:all .45s}.am-menu-default .am-menu-nav{padding-top:8px;padding-bottom:8px}.am-menu-default .am-menu-nav a{text-align:center;height:36px;line-height:36px;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;padding:0;color:#0e90d2}.am-menu-default .am-menu-nav>.am-parent>a{position:relative;-webkit-transition:.15s;transition:.15s}.am-menu-default .am-menu-nav>.am-parent>a:after{content:"\f107";margin-left:5px;-webkit-transition:.15s;transition:.15s}.am-menu-default .am-menu-nav>.am-parent>a:before{position:absolute;top:100%;margin-top:-16px;left:50%;margin-left:-12px;content:"\f0d8";display:none;color:#f1f1f1;font-size:24px}.am-menu-default .am-menu-nav>.am-parent.am-open>a{color:#095f8a}.am-menu-default .am-menu-nav>.am-parent.am-open>a:before{display:block}.am-menu-default .am-menu-nav>.am-parent.am-open>a:after{-webkit-transform:rotate(-180deg);-ms-transform:rotate(-180deg);transform:rotate(-180deg)}.am-menu-default .am-menu-sub{position:absolute;left:5px;right:5px;background-color:#f1f1f1;border-radius:0;padding-top:8px;padding-bottom:8px}.am-menu-default .am-menu-sub>li>a{color:#555}@media only screen and (min-width:641px){.am-menu-default .am-menu-nav li{width:auto;float:left;clear:none;display:inline}.am-menu-default .am-menu-nav a{padding-left:1.5rem;padding-right:.5rem}}.am-menu-dropdown1{position:relative}.am-menu-dropdown1 .am-menu-toggle{position:absolute;right:5px;top:-47px;display:block;width:44px;height:44px;line-height:44px;text-align:center;color:#fff}.am-menu-dropdown1 a{-webkit-transition:all .4s;transition:all .4s;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-menu-dropdown1 .am-menu-nav{position:absolute;left:0;right:0;z-index:1050}.am-menu-dropdown1 .am-menu-nav a{padding:.8rem}.am-menu-dropdown1 .am-menu-nav>li{width:100%}.am-menu-dropdown1 .am-menu-nav>li.am-parent>a{position:relative}.am-menu-dropdown1 .am-menu-nav>li.am-parent>a::before{content:"\f067";position:absolute;right:1rem;top:1.4rem}.am-menu-dropdown1 .am-menu-nav>li.am-parent.am-open>a{background-color:#0c80ba;border-bottom:none;color:#fff}.am-menu-dropdown1 .am-menu-nav>li.am-parent.am-open>a:before{content:"\f068"}.am-menu-dropdown1 .am-menu-nav>li.am-parent.am-open>a:after{content:"";display:inline-block;width:0;height:0;vertical-align:middle;border-top:8px solid #0c80ba;border-right:8px solid transparent;border-left:8px solid transparent;border-bottom:0 dotted;-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);position:absolute;top:100%;left:50%;margin-left:-4px}.am-menu-dropdown1 .am-menu-nav>li>a{border-bottom:1px solid #0b76ac;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);background-color:#0e90d2;color:#fff;height:49px;line-height:49px;padding:0;text-indent:10px}.am-menu-dropdown1 .am-menu-sub{background-color:#fff}.am-menu-dropdown1 .am-menu-sub a{color:#555;height:44px;line-height:44px;text-indent:5px;padding:0}.am-menu-dropdown1 .am-menu-sub a:before{content:"\f105";color:#aaa;font-size:16px;margin-right:5px}.am-menu-dropdown2 .am-menu-toggle{position:absolute;right:5px;top:-47px;display:block;width:44px;height:44px;line-height:44px;text-align:center;color:#fff}.am-menu-dropdown2 .am-menu-nav{position:absolute;left:0;right:0;background-color:#f5f5f5;-webkit-box-shadow:0 0 5px rgba(0,0,0,.2);box-shadow:0 0 5px rgba(0,0,0,.2);z-index:1050;padding-top:8px;padding-bottom:8px}.am-menu-dropdown2 .am-menu-nav a{height:38px;line-height:38px;padding:0;text-align:center}.am-menu-dropdown2 .am-menu-nav>li>a{color:#333}.am-menu-dropdown2 .am-menu-nav>li.am-parent>a{position:relative}.am-menu-dropdown2 .am-menu-nav>li.am-parent>a:after{content:"\f107";margin-left:5px;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s}.am-menu-dropdown2 .am-menu-nav>li.am-parent.am-open>a{position:relative}.am-menu-dropdown2 .am-menu-nav>li.am-parent.am-open>a:after{color:#0e90d2;-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.am-menu-dropdown2 .am-menu-nav>li.am-parent.am-open>a:before{position:absolute;top:100%;margin-top:-16px;left:50%;margin-left:-12px;font-size:24px;content:"\f0d8";color:rgba(0,0,0,.2)}.am-menu-dropdown2 .am-menu-sub{position:absolute;left:5px;right:5px;padding:8px 0;border-radius:2px;-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15);background-color:#fff;z-index:1055}.am-menu-dropdown2 .am-menu-sub a{padding:0;height:35px;color:#555;line-height:35px}@media only screen and (min-width:641px){.am-menu-dropdown2 .am-menu-toggle{display:none!important}.am-menu-dropdown2 .am-menu-nav{position:static;display:block}.am-menu-dropdown2 .am-menu-nav>li{float:none;width:auto;display:inline-block}.am-menu-dropdown2 .am-menu-nav>li a{padding-left:1.5rem;padding-right:1.5rem}.am-menu-dropdown2 .am-menu-sub{left:auto;right:auto}.am-menu-dropdown2 .am-menu-sub>li{float:none;width:auto}.am-menu-dropdown2 .am-menu-sub a{padding-left:2rem;padding-right:2rem}}.am-menu-slide1 .am-menu-toggle{position:absolute;right:5px;top:-47px;display:block;width:44px;height:44px;line-height:44px;text-align:center;color:#fff}.am-menu-slide1 .am-menu-nav{background-color:#f5f5f5;padding-top:8px;padding-bottom:8px}.am-menu-slide1 .am-menu-nav.am-in:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f0d8";font-size:24px;color:#f5f5f5;position:absolute;right:16px;top:-16px}.am-menu-slide1 .am-menu-nav a{line-height:38px;height:38px;display:block;padding:0;text-align:center}.am-menu-slide1 .am-menu-nav>li>a{color:#333;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-menu-slide1 .am-menu-nav>.am-parent>a{position:relative;-webkit-transition:.15s;transition:.15s}.am-menu-slide1 .am-menu-nav>.am-parent>a:after{content:"\f107";margin-left:5px;-webkit-transition:.15s;transition:.15s}.am-menu-slide1 .am-menu-nav>.am-parent>a:before{position:absolute;top:100%;margin-top:-16px;left:50%;margin-left:-12px;content:"\f0d8";display:none;color:#0e90d2;font-size:24px}.am-menu-slide1 .am-menu-nav>.am-parent.am-open>a{color:#0e90d2}.am-menu-slide1 .am-menu-nav>.am-parent.am-open>a:before{display:block}.am-menu-slide1 .am-menu-nav>.am-parent.am-open>a:after{-webkit-transform:rotate(-180deg);-ms-transform:rotate(-180deg);transform:rotate(-180deg)}.am-menu-slide1 .am-menu-sub{position:absolute;left:5px;right:5px;background-color:#0e90d2;border-radius:0;padding-top:8px;padding-bottom:8px}.am-menu-slide1 .am-menu-sub>li>a{color:#fff}@media only screen and (min-width:641px){.am-menu-slide1 .am-menu-toggle{display:none!important}.am-menu-slide1 .am-menu-nav{background-color:#f5f5f5;display:block}.am-menu-slide1 .am-menu-nav.am-in:before{display:none}.am-menu-slide1 .am-menu-nav li{width:auto;clear:none}.am-menu-slide1 .am-menu-nav li a{padding-left:1.5rem;padding-right:1.5rem}}.am-menu-offcanvas1 .am-menu-toggle{position:absolute;right:5px;top:-47px;display:block;width:44px;height:44px;line-height:44px;text-align:center;color:#fff}.am-menu-offcanvas1 .am-menu-nav{border-bottom:1px solid rgba(0,0,0,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.05);box-shadow:0 1px 0 rgba(255,255,255,.05)}.am-menu-offcanvas1 .am-menu-nav>li>a{height:44px;line-height:44px;text-indent:15px;padding:0;position:relative;color:#ccc;border-top:1px solid rgba(0,0,0,.3);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);text-shadow:0 1px 0 rgba(0,0,0,.5)}.am-menu-offcanvas1 .am-menu-nav>.am-open>a,.am-menu-offcanvas1 .am-menu-nav>li>a:focus,.am-menu-offcanvas1 .am-menu-nav>li>a:hover{background-color:#474747;color:#fff;outline:0}.am-menu-offcanvas1 .am-menu-nav>.am-active>a{background-color:#1a1a1a;color:#fff}.am-menu-offcanvas1 .am-menu-nav>.am-parent>a{-webkit-transition:all .3s;transition:all .3s}.am-menu-offcanvas1 .am-menu-nav>.am-parent>a:after{content:"\f104";position:absolute;right:1.5rem;top:1.3rem}.am-menu-offcanvas1 .am-menu-nav>.am-parent.am-open>a:after{content:"\f107"}.am-menu-offcanvas1 .am-menu-sub{border-top:1px solid rgba(0,0,0,.3);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);padding:5px 0 5px 15px;background-color:#1a1a1a;font-size:1.4rem}.am-menu-offcanvas1 .am-menu-sub a{color:#eee}.am-menu-offcanvas1 .am-menu-sub a:hover{color:#fff}.am-menu-offcanvas1 .am-nav-divider{border-top:1px solid #1a1a1a}.am-menu-offcanvas2 .am-menu-toggle{position:absolute;right:5px;top:-47px;display:block;width:44px;height:44px;line-height:44px;text-align:center;color:#fff}.am-menu-offcanvas2 .am-menu-nav{padding:10px 5px}.am-menu-offcanvas2 .am-menu-nav>li{padding:5px}.am-menu-offcanvas2 .am-menu-nav>li>a{-webkit-transition:all .3s;transition:all .3s;background-color:#404040;color:#ccc;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;border:1px solid rgba(0,0,0,.3);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);text-shadow:0 1px 0 rgba(0,0,0,.5);height:44px;line-height:44px;padding:0;text-align:center}.am-menu-offcanvas2 .am-menu-nav>li>a:focus,.am-menu-offcanvas2 .am-menu-nav>li>a:hover{background-color:#262626;color:#fff;outline:0}.am-menu-offcanvas2 .am-menu-nav>.am-active>a{background-color:#262626;color:#fff}.am-menu-stack .am-menu-nav{border-bottom:1px solid #dedede;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.05);box-shadow:0 1px 0 rgba(255,255,255,.05)}.am-menu-stack .am-menu-nav>.am-parent>a{-webkit-transition:all .3s;transition:all .3s}.am-menu-stack .am-menu-nav>.am-parent>a:after{content:"\f105";position:absolute;right:1.5rem;top:1.3rem;-webkit-transition:all .15s;transition:all .15s}.am-menu-stack .am-menu-nav>.am-parent.am-open>a:after{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.am-menu-stack .am-menu-nav>li>a{position:relative;color:#333;background-color:#f5f5f5;border-top:1px solid #dedede;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.05);height:49px;line-height:49px;text-indent:10px;padding:0}.am-menu-stack .am-menu-nav>.am-open>a,.am-menu-stack .am-menu-nav>li>a:focus,.am-menu-stack .am-menu-nav>li>a:hover{background-color:#e5e5e5;color:#222;outline:0}.am-menu-stack .am-menu-sub{padding:0;font-size:1.4rem;border-top:1px solid #dedede}.am-menu-stack .am-menu-sub a{border-bottom:1px solid #dedede;padding-left:2rem;color:#444}.am-menu-stack .am-menu-sub a:hover{color:#333}.am-menu-stack .am-menu-sub li:last-child a{border-bottom:none}.am-menu-stack .am-menu-sub>li>a{height:44px;line-height:44px;text-indent:15px;padding:0}@media only screen and (min-width:641px){.am-menu-stack .am-menu-nav{background-color:#f5f5f5}.am-menu-stack .am-menu-nav>li{float:left;width:auto;clear:none!important;display:inline-block}.am-menu-stack .am-menu-nav>li a{padding-left:1.5rem;padding-right:1.5rem}.am-menu-stack .am-menu-nav>li.am-parent>a:after{position:static;content:"\f107"}.am-menu-stack .am-menu-nav>li.am-parent.am-open a{border-bottom:none}.am-menu-stack .am-menu-nav>li.am-parent.am-open a:after{-webkit-transform:rotateX(-180deg);transform:rotateX(-180deg)}.am-menu-stack .am-menu-nav>li.am-parent.am-open .am-menu-sub{background-color:#e5e5e5}.am-menu-stack .am-menu-sub{position:absolute;left:0;right:0;background-color:#ddd;border-top:none}.am-menu-stack .am-menu-sub li{width:auto;float:left;clear:none}}.am-navbar{position:fixed;left:0;bottom:0;width:100%;height:49px;line-height:49px;z-index:1010}.am-navbar ul{padding-left:0;margin:0;list-style:none;width:100%}.am-navbar .am-navbar-nav{padding-left:8px;padding-right:8px;text-align:center;overflow:hidden}.am-navbar .am-navbar-nav li{display:table-cell;width:1%;float:none}.am-navbar-nav{position:relative;z-index:1015}.am-navbar-nav a{display:inline-block;width:100%;height:49px;line-height:20px}.am-navbar-nav a img{display:block;vertical-align:middle;height:24px;width:24px;margin:4px auto 0}.am-navbar-nav a [class*=am-icon]{width:24px;height:24px;margin:4px auto 0;display:block;line-height:24px}.am-navbar-nav a [class*=am-icon]:before{font-size:22px;vertical-align:middle}.am-navbar-nav a .am-navbar-label{padding-top:2px;line-height:1;font-size:12px;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-navbar-more [class*=am-icon-]{-webkit-transition:.15s;transition:.15s}.am-navbar-more.am-active [class*=am-icon-]{-webkit-transform:rotateX(-180deg);transform:rotateX(-180deg)}.am-navbar-actions{position:absolute;bottom:49px;right:0;left:0;z-index:1009;opacity:0;-webkit-transition:.3s;transition:.3s;-webkit-transform:translate(0,100%);-ms-transform:translate(0,100%);transform:translate(0,100%)}.am-navbar-actions.am-active{opacity:1;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.am-navbar-actions li{line-height:42px;position:relative}.am-navbar-actions li a{display:block;width:100%;height:40px;-webkit-box-shadow:inset 0 1px rgba(220,220,220,.25);box-shadow:inset 0 1px rgba(220,220,220,.25);padding-left:20px;padding-right:36px}.am-navbar-actions li a :after{font-family:FontAwesome,sans-serif;content:"\f105";display:inline-block;position:absolute;top:0;right:20px}.am-navbar-actions li a img{vertical-align:middle;height:20px;width:20px;display:inline}#am-navbar-qrcode{width:220px;height:220px;margin-left:-110px}#am-navbar-qrcode .am-modal-bd{padding:10px}#am-navbar-qrcode canvas{display:block;width:200px;height:200px}.am-with-fixed-navbar{padding-bottom:54px}.am-navbar-default a{color:#fff}.am-navbar-default .am-navbar-nav{background-color:#0e90d2}.am-navbar-default .am-navbar-actions{background-color:#0d86c4}.am-navbar-default .am-navbar-actions a{border-bottom:1px solid #0b6fa2}.am-pagination{position:relative}.am-pagination-default{margin-left:10px;margin-right:10px;font-size:1.6rem}.am-pagination-default .am-pagination-next,.am-pagination-default .am-pagination-prev{float:none}.am-pagination-select{margin-left:10px;margin-right:10px;font-size:1.6rem}.am-pagination-select>li>a{line-height:36px;background-color:#eee;padding:0 15px;border:0;color:#555}.am-pagination-select .am-pagination-select{position:absolute;top:0;left:50%;margin-left:-35px;width:70px;height:36px;text-align:center;border-radius:0}.am-pagination-select .am-pagination-select select{display:block;border:0;line-height:36px;width:70px;height:36px;border-radius:0;color:#555;background-color:#eee;-webkit-appearance:none;-moz-appearance:none;appearance:none;padding-left:18px}.am-paragraph p{margin:10px 0}.am-paragraph img{max-width:100%}.am-paragraph h1,.am-paragraph h2,.am-paragraph h3,.am-paragraph h4,.am-paragraph h5,.am-paragraph h6{color:#222}.am-paragraph table{max-width:none}.am-paragraph-table-container{overflow:hidden;background:#eee;max-width:none}.am-paragraph-table-container table{width:100%;max-width:none}.am-paragraph-table-container table th{background:#bce5fb;height:40px;border:1px solid #999;text-align:center}.am-paragraph-table-container table td{border:1px solid #999;text-align:center;vertical-align:middle;background:#fff}.am-paragraph-table-container table td p{text-indent:0;font-size:1.4rem}.am-paragraph-table-container table td a{font-size:1.4rem}.am-paragraph-default{margin:0 10px;color:#333;background-color:transparent}.am-paragraph-default p{font-size:1.4rem}.am-paragraph-default img{max-width:98%;display:block;margin:5px auto;border:1px solid #eee;padding:2px}.am-paragraph-default a{color:#0e90d2}.am-slider-a1{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-a1 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-a1 .am-viewport{max-height:300px}.am-slider-a1 .am-control-nav{width:100%;position:absolute;bottom:5px;text-align:center;line-height:0}.am-slider-a1 .am-control-nav li{margin:0 6px;display:inline-block}.am-slider-a1 .am-control-nav li a{width:8px;height:8px;display:block;background-color:rgba(0,0,0,.5);cursor:pointer;text-indent:-9999px;border-radius:50%;-webkit-box-shadow:inset 0 0 3px rgba(0,0,0,.3);box-shadow:inset 0 0 3px rgba(0,0,0,.3)}.am-slider-a1 .am-control-nav li a:hover{background-color:rgba(0,0,0,.7)}.am-slider-a1 .am-control-nav li a.am-active{background-color:#0e90d2;cursor:default}.am-slider-a1 .am-direction-nav,.am-slider-a1 .am-pauseplay{display:none}.am-slider-a2{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-a2 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-a2 .am-viewport{max-height:300px}.am-slider-a2 .am-control-nav{width:100%;position:absolute;bottom:5px;text-align:center;line-height:0}.am-slider-a2 .am-control-nav li{margin:0 6px;display:inline-block}.am-slider-a2 .am-control-nav li a{width:8px;height:8px;display:block;background-color:rgba(0,0,0,.5);cursor:pointer;text-indent:-9999px;-webkit-box-shadow:inset 0 0 3px rgba(0,0,0,.3);box-shadow:inset 0 0 3px rgba(0,0,0,.3)}.am-slider-a2 .am-control-nav li a:hover{background-color:rgba(0,0,0,.7)}.am-slider-a2 .am-control-nav li a.am-active{background:#0e93d7;cursor:default}.am-slider-a2 .am-direction-nav,.am-slider-a2 .am-pauseplay{display:none}.am-slider-a3{margin-bottom:20px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-a3 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-a3 .am-viewport{max-height:300px}.am-slider-a3 .am-control-nav{width:100%;position:absolute;bottom:-20px;text-align:center;height:20px;background-color:#000;padding-top:5px;line-height:0}.am-slider-a3 .am-control-nav li{margin:0 6px;display:inline-block}.am-slider-a3 .am-control-nav li a{width:8px;height:8px;display:block;background-color:rgba(0,0,0,.5);cursor:pointer;text-indent:-9999px;border-radius:50%;-webkit-box-shadow:inset 0 0 3px rgba(200,200,200,.3);box-shadow:inset 0 0 3px rgba(200,200,200,.3)}.am-slider-a3 .am-control-nav li a:hover{background-color:rgba(0,0,0,.7)}.am-slider-a3 .am-control-nav li a.am-active{background:#0e90d2;cursor:default}.am-slider-a3 .am-direction-nav,.am-slider-a3 .am-pauseplay{display:none}.am-slider-a4{margin-bottom:30px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-a4 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-a4 .am-viewport{max-height:300px}.am-slider-a4 .am-control-nav{width:100%;position:absolute;bottom:-15px;text-align:center;line-height:0}.am-slider-a4 .am-control-nav li{margin:0 6px;display:inline-block}.am-slider-a4 .am-control-nav li a{width:8px;height:8px;display:block;background-color:rgba(0,0,0,.5);cursor:pointer;text-indent:-9999px;border-radius:50%;-webkit-box-shadow:inset 0 0 3px rgba(0,0,0,.3);box-shadow:inset 0 0 3px rgba(0,0,0,.3)}.am-slider-a4 .am-control-nav li a:hover{background-color:rgba(0,0,0,.7)}.am-slider-a4 .am-control-nav li a.am-active{background-color:#0e90d2;cursor:default}.am-slider-a4 .am-direction-nav,.am-slider-a4 .am-pauseplay{display:none}.am-slider-a5{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-a5 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-a5 .am-viewport{max-height:300px}.am-slider-a5 .am-control-nav{width:100%;position:absolute;text-align:center;height:6px;display:table;bottom:0;font-size:0;line-height:0}.am-slider-a5 .am-control-nav li{display:table-cell}.am-slider-a5 .am-control-nav li a{width:100%;height:6px;display:block;background-color:rgba(0,0,0,.5);cursor:pointer;text-indent:-9999px}.am-slider-a5 .am-control-nav li a:hover{background-color:rgba(0,0,0,.7)}.am-slider-a5 .am-control-nav li a.am-active{background-color:#0e90d2;cursor:default}.am-slider-a5 .am-direction-nav,.am-slider-a5 .am-pauseplay{display:none}.am-slider-b1{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-b1 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-b1 .am-viewport{max-height:300px}.am-slider-b1 .am-direction-nav a{-webkit-box-sizing:content-box;box-sizing:content-box;display:block;width:24px;height:24px;padding:8px 0;margin:-20px 0 0;position:absolute;top:50%;z-index:10;overflow:hidden;opacity:.45;cursor:pointer;color:#fff;text-shadow:1px 1px 0 rgba(255,255,255,.3);background-color:rgba(0,0,0,.5);font-size:0;text-align:center;-webkit-transition:all .3s ease;transition:all .3s ease}.am-slider-b1 .am-direction-nav a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f053";font-size:24px}.am-slider-b1 .am-direction-nav a.am-prev{left:0;padding-right:5px;border-bottom-right-radius:5px;border-top-right-radius:5px}.am-slider-b1 .am-direction-nav a.am-next{right:0;padding-left:5px;border-bottom-left-radius:5px;border-top-left-radius:5px}.am-slider-b1 .am-direction-nav a.am-next:before{content:"\f054"}.am-slider-b1 .am-direction-nav .am-disabled{opacity:0!important;cursor:default}.am-slider-b1:hover .am-prev{opacity:.7}.am-slider-b1:hover .am-prev:hover{opacity:1}.am-slider-b1:hover .am-next{opacity:.7}.am-slider-b1:hover .am-next:hover{opacity:1}.am-slider-b1 .am-control-nav,.am-slider-b1 .am-pauseplay{display:none}.am-slider-b2{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-b2 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-b2 .am-viewport{max-height:300px}.am-slider-b2 .am-direction-nav a{-webkit-box-sizing:content-box;box-sizing:content-box;display:block;width:24px;height:24px;padding:4px;margin:-16px 0 0;position:absolute;top:50%;z-index:10;overflow:hidden;opacity:.45;cursor:pointer;color:#fff;text-shadow:1px 1px 0 rgba(255,255,255,.3);background-color:rgba(0,0,0,.5);font-size:0;text-align:center;border-radius:50%;-webkit-transition:all .3s ease;transition:all .3s ease}.am-slider-b2 .am-direction-nav a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f053";font-size:16px;line-height:24px}.am-slider-b2 .am-direction-nav a.am-prev{left:5px}.am-slider-b2 .am-direction-nav a.am-next{right:5px}.am-slider-b2 .am-direction-nav a.am-next:before{content:"\f054"}.am-slider-b2 .am-direction-nav .am-disabled{opacity:0!important;cursor:default}.am-slider-b2:hover .am-prev{opacity:.7}.am-slider-b2:hover .am-prev:hover{opacity:1}.am-slider-b2:hover .am-next{opacity:.7}.am-slider-b2:hover .am-next:hover{opacity:1}.am-slider-b2 .am-control-nav,.am-slider-b2 .am-pauseplay{display:none}.am-slider-b3{margin:15px 30px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-b3 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-b3 .am-viewport{max-height:300px}.am-slider-b3 .am-direction-nav a{-webkit-box-sizing:content-box;box-sizing:content-box;display:block;width:24px;height:24px;padding:4px;margin:-16px 0 0;position:absolute;top:50%;z-index:10;overflow:hidden;opacity:.45;cursor:pointer;color:#333;text-shadow:1px 1px 0 rgba(255,255,255,.3);font-size:0;-webkit-transition:all .3s ease;transition:all .3s ease}.am-slider-b3 .am-direction-nav a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f053";font-size:24px}.am-slider-b3 .am-direction-nav a.am-prev{left:-25px}.am-slider-b3 .am-direction-nav a.am-next{right:-25px;text-align:right}.am-slider-b3 .am-direction-nav a.am-next:before{content:"\f054"}.am-slider-b3 .am-direction-nav .am-disabled{opacity:0!important;cursor:default}.am-slider-b3:hover .am-prev{opacity:.7}.am-slider-b3:hover .am-prev:hover{opacity:1}.am-slider-b3:hover .am-next{opacity:.7}.am-slider-b3:hover .am-next:hover{opacity:1}.am-slider-b3 .am-control-nav,.am-slider-b3 .am-pauseplay{display:none}.am-slider-b4{margin:15px 20px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-b4 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-b4 .am-viewport{max-height:300px}.am-slider-b4 .am-direction-nav a{position:absolute;top:50%;z-index:10;display:block;-webkit-box-sizing:content-box;box-sizing:content-box;width:24px;height:24px;margin:-16px 0 0;padding:4px;overflow:hidden;opacity:.45;background-color:rgba(0,0,0,.8);cursor:pointer;text-shadow:1px 1px 0 rgba(255,255,255,.3);font-size:0;border-radius:50%;text-align:center;color:#fff;-webkit-transition:all .3s ease;transition:all .3s ease}.am-slider-b4 .am-direction-nav a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f053";font-size:20px;line-height:24px}.am-slider-b4 .am-direction-nav a.am-prev{left:-15px}.am-slider-b4 .am-direction-nav a.am-next{right:-15px}.am-slider-b4 .am-direction-nav a.am-next:before{content:"\f054"}.am-slider-b4 .am-direction-nav .am-disabled{opacity:0!important;cursor:default}.am-slider-b4:hover .am-prev{opacity:.7}.am-slider-b4:hover .am-prev:hover{opacity:.9}.am-slider-b4:hover .am-next{opacity:.7}.am-slider-b4:hover .am-next:hover{opacity:.9}.am-slider-b4 .am-control-nav,.am-slider-b4 .am-pauseplay{display:none}.am-slider-c1{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-c1 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-c1 .am-viewport{max-height:300px}.am-slider-c1 .am-control-nav{position:absolute;bottom:0;display:table;width:100%;height:6px;font-size:0;line-height:0;text-align:center}.am-slider-c1 .am-control-nav li{display:table-cell;width:1%}.am-slider-c1 .am-control-nav li a{width:100%;height:6px;display:block;background-color:rgba(0,0,0,.7);cursor:pointer;text-indent:-9999px}.am-slider-c1 .am-control-nav li a:hover{background:rgba(0,0,0,.8)}.am-slider-c1 .am-control-nav li a.am-active{background-color:#0e90d2;cursor:default}.am-slider-c1 .am-slider-desc{background-color:rgba(0,0,0,.6);position:absolute;bottom:6px;padding:8px;width:100%;color:#fff;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-slider-c1 .am-direction-nav,.am-slider-c1 .am-pauseplay{display:none}.am-slider-c2{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-c2 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-c2 .am-viewport{max-height:300px}.am-slider-c2 .am-control-nav{position:absolute;bottom:15px;right:0;height:6px;text-align:center;font-size:0;line-height:0}.am-slider-c2 .am-control-nav li{display:inline-block;margin-right:6px}.am-slider-c2 .am-control-nav li a{width:6px;height:6px;display:block;background-color:rgba(255,255,255,.4);cursor:pointer;text-indent:-9999px}.am-slider-c2 .am-control-nav li a:hover{background:rgba(230,230,230,.4)}.am-slider-c2 .am-control-nav li a.am-active{background-color:#0e90d2;cursor:default}.am-slider-c2 .am-slider-desc{background-color:rgba(0,0,0,.6);position:absolute;bottom:0;padding:8px 60px 8px 8px;width:100%;color:#fff;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-slider-c2 .am-direction-nav,.am-slider-c2 .am-pauseplay{display:none}.am-slider-c3{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-c3 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-c3 .am-viewport{max-height:300px}.am-slider-c3 .am-slider-desc{background-color:rgba(0,0,0,.6);position:absolute;bottom:10px;right:60px;height:30px;left:0;padding-right:5px;color:#fff;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-slider-c3 .am-slider-counter{margin-right:5px;display:inline-block;height:30px;background-color:#0e90d2;width:40px;text-align:center;line-height:30px;color:#eee;font-size:1rem}.am-slider-c3 .am-slider-counter .am-active{font-size:1.8rem;font-weight:700;color:#fff}.am-slider-c3 .am-direction-nav a{-webkit-box-sizing:content-box;box-sizing:content-box;display:block;width:24px;height:24px;padding:4px 0;margin:-16px 0 0;position:absolute;top:50%;z-index:10;overflow:hidden;opacity:.45;cursor:pointer;color:#fff;text-shadow:1px 1px 0 rgba(255,255,255,.3);background-color:rgba(0,0,0,.5);font-size:0;text-align:center;-webkit-transition:all .3s ease;transition:all .3s ease}.am-slider-c3 .am-direction-nav a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f053";font-size:16px;line-height:24px}.am-slider-c3 .am-direction-nav a.am-prev{left:0;padding-right:5px}.am-slider-c3 .am-direction-nav a.am-next{right:0;padding-left:5px}.am-slider-c3 .am-direction-nav a.am-next:before{content:"\f054"}.am-slider-c3 .am-direction-nav .am-disabled{opacity:0!important;cursor:default}.am-slider-c3:hover .am-prev{opacity:.7}.am-slider-c3:hover .am-prev:hover{opacity:1}.am-slider-c3:hover .am-next{opacity:.7}.am-slider-c3:hover .am-next:hover{opacity:1}.am-slider-c3 .am-control-nav,.am-slider-c3 .am-pauseplay{display:none}.am-slider-c4{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-c4 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-c4 .am-viewport{max-height:300px}.am-slider-c4 .am-slider-desc{width:100%;background-color:rgba(0,0,0,.6);position:absolute;bottom:0;right:0;left:0;padding:8px 40px;color:#fff;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-slider-c4 .am-direction-nav a{-webkit-box-sizing:content-box;box-sizing:content-box;display:block;width:24px;height:24px;padding:4px 0;margin:0;position:absolute;bottom:4px;z-index:10;overflow:hidden;opacity:.45;cursor:pointer;text-shadow:1px 1px 0 rgba(255,255,255,.3);font-size:0;text-align:center;color:rgba(0,0,0,.7);-webkit-transition:all .3s ease;transition:all .3s ease}.am-slider-c4 .am-direction-nav a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f053";font-size:24px}.am-slider-c4 .am-direction-nav a.am-prev{left:0;padding-right:5px}.am-slider-c4 .am-direction-nav a.am-next{right:0;padding-left:5px}.am-slider-c4 .am-direction-nav a.am-next:before{content:"\f054"}.am-slider-c4 .am-direction-nav .am-disabled{opacity:0!important;cursor:default}.am-slider-c4:hover .am-prev{opacity:.7}.am-slider-c4:hover .am-prev:hover{opacity:1}.am-slider-c4:hover .am-next{opacity:.7}.am-slider-c4:hover .am-next:hover{opacity:1}.am-slider-c4 .am-control-nav,.am-slider-c4 .am-pauseplay{display:none}.am-slider-d1{-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-d1 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-d1 .am-viewport{max-height:300px}.am-slider-d1 .am-slider-desc{padding:8px 35px;width:100%;color:#fff;background-color:#0e90d2}.am-slider-d1 .am-slider-title{font-weight:400;margin-bottom:2px;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-slider-d1 .am-slider-more{color:#eee;font-size:1.3rem}.am-slider-d1 .am-direction-nav a{-webkit-box-sizing:content-box;box-sizing:content-box;display:block;width:24px;height:24px;margin:0;position:absolute;bottom:18px;z-index:10;overflow:hidden;opacity:.45;cursor:pointer;text-shadow:1px 1px 0 rgba(255,255,255,.3);font-size:0;text-align:center;border:1px solid rgba(255,255,255,.9);color:rgba(255,255,255,.9);border-radius:50%;-webkit-transition:all 3s ease;transition:all 3s ease}.am-slider-d1 .am-direction-nav a:before{display:inline-block;font:normal normal normal 1.6rem/1 FontAwesome,sans-serif;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f053";font-size:16px;line-height:24px}.am-slider-d1 .am-direction-nav a.am-prev{left:5px}.am-slider-d1 .am-direction-nav a.am-next{right:5px}.am-slider-d1 .am-direction-nav a.am-next:before{content:"\f054"}.am-slider-d1 .am-direction-nav .am-disabled{opacity:0!important;cursor:default}.am-slider-d1:hover .am-prev{opacity:.7}.am-slider-d1:hover .am-prev:hover{opacity:1}.am-slider-d1:hover .am-next{opacity:.7}.am-slider-d1:hover .am-next:hover{opacity:1}.am-slider-d1 .am-control-nav,.am-slider-d1 .am-pauseplay{display:none}.am-slider-d2{margin-bottom:20px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-d2 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-d2 .am-viewport{max-height:300px}.am-slider-d2 .am-slider-desc{position:absolute;left:10px;bottom:20px;right:50px;color:#fff}.am-slider-d2 .am-slider-content{background-color:rgba(0,0,0,.7);padding:10px 6px;margin-bottom:10px}.am-slider-d2 .am-slider-content p{margin:0;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;font-size:1.4rem}.am-slider-d2 .am-slider-title{font-weight:400;margin-bottom:5px;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-slider-d2 .am-slider-more{color:#eee;font-size:1.3rem;background-color:#0e90d2;padding:2px 10px}.am-slider-d2 .am-control-nav{width:100%;position:absolute;bottom:-15px;text-align:center}.am-slider-d2 .am-control-nav li{margin:0 6px;display:inline-block}.am-slider-d2 .am-control-nav li a{width:8px;height:8px;display:block;background-color:rgba(0,0,0,.5);cursor:pointer;text-indent:-9999px;border-radius:50%;font-size:0;line-height:0;-webkit-box-shadow:inset 0 0 3px rgba(0,0,0,.3);box-shadow:inset 0 0 3px rgba(0,0,0,.3)}.am-slider-d2 .am-control-nav li a:hover{background:rgba(0,0,0,.5)}.am-slider-d2 .am-control-nav li a.am-active{background:#0e90d2;cursor:default}.am-slider-d2 .am-direction-nav,.am-slider-d2 .am-pauseplay{display:none}.am-slider-d3{margin-bottom:10px;-webkit-box-shadow:0 1px 4px rgba(0,0,0,.2);box-shadow:0 1px 4px rgba(0,0,0,.2)}.am-slider-d3 .am-viewport{max-height:2000px;-webkit-transition:all 1s ease;transition:all 1s ease}.loading .am-slider-d3 .am-viewport{max-height:300px}.am-slider-d3 .am-slider-desc{position:absolute;bottom:0;color:#fff;width:100%;background-color:rgba(0,0,0,.7);padding:8px 5px}.am-slider-d3 .am-slider-desc p{margin:0;font-size:1.3rem;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-slider-d3 .am-slider-title{font-weight:400;margin-bottom:5px;display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-slider-d3 .am-control-thumbs{position:static;overflow:hidden}.am-slider-d3 .am-control-thumbs li{padding:12px 4px 4px;position:relative}.am-slider-d3 .am-control-thumbs img{width:100%;display:block;opacity:.85;cursor:pointer}.am-slider-d3 .am-control-thumbs img:hover{opacity:1}.am-slider-d3 .am-control-thumbs .am-active{opacity:1;cursor:default}.am-slider-d3 .am-control-thumbs .am-active+i{position:absolute;top:0;left:50%;content:"";display:inline-block;width:0;height:0;vertical-align:middle;border-top:8px solid rgba(0,0,0,.7);border-right:8px solid transparent;border-left:8px solid transparent;border-bottom:0 dotted;-webkit-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg);margin-left:-4px;-webkit-transition:all .2s;transition:all .2s}.am-slider-d3 .am-direction-nav,.am-slider-d3 .am-pauseplay{display:none}.am-slider-d3 .am-control-thumbs{display:table}.am-slider-d3 .am-control-thumbs li{display:table-cell;width:1%}[data-am-widget=tabs]{margin:10px}[data-am-widget=tabs] .am-tabs-nav{width:100%;padding:0;margin:0;list-style:none;text-align:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}[data-am-widget=tabs] .am-tabs-nav li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}[data-am-widget=tabs] .am-tabs-nav a{display:block;word-wrap:normal;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.am-tabs-default .am-tabs-nav{line-height:40px;background-color:#eee}.am-tabs-default .am-tabs-nav a{color:#222;line-height:42px}.am-tabs-default .am-tabs-nav>.am-active a{background-color:#0e90d2;color:#fff}.am-tabs-d2 .am-tabs-nav{background-color:#eee}.am-tabs-d2 .am-tabs-nav li{height:42px}.am-tabs-d2 .am-tabs-nav a{color:#222;line-height:42px}.am-tabs-d2 .am-tabs-nav>.am-active{position:relative;background-color:#fcfcfc;border-bottom:2px solid #0e90d2}.am-tabs-d2 .am-tabs-nav>.am-active a{line-height:40px;color:#0e90d2}.am-tabs-d2 .am-tabs-nav>.am-active:after{position:absolute;width:0;height:0;bottom:0;left:50%;margin-left:-5px;border:6px rgba(0,0,0,0) solid;content:"";z-index:1;border-bottom-color:#0e90d2}.am-titlebar{margin-top:20px;height:45px;font-size:100%}.am-titlebar h2{margin-top:0;margin-bottom:0;font-size:1.6rem}.am-titlebar .am-titlebar-title img{height:24px;width:auto}.am-titlebar-default{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-left:10px;margin-right:10px;background-color:transparent;border-bottom:1px solid #dedede;line-height:44px}.am-titlebar-default a{color:#0e90d2}.am-titlebar-default .am-titlebar-title{position:relative;padding-left:12px;color:#0e90d2;font-size:1.8rem;text-align:left;font-weight:700}.am-titlebar-default .am-titlebar-title:before{content:"";position:absolute;left:2px;top:8px;bottom:8px;border-left:3px solid #0e90d2}.am-titlebar-default .am-titlebar-nav{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;text-align:right}.am-titlebar-default .am-titlebar-nav a{margin-right:10px}.am-titlebar-default .am-titlebar-nav a:last-child{margin-right:5px}.am-titlebar-multi{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;background-color:#f5f5f5;border-top:2px solid #3bb4f2;border-bottom:1px solid #e8e8e8}.am-titlebar-multi a{color:#0e90d2}.am-titlebar-multi .am-titlebar-title{padding-left:10px;color:#0e90d2;font-size:1.8rem;text-align:left;font-weight:700;line-height:42px}.am-titlebar-multi .am-titlebar-nav{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;text-align:right;line-height:42px}.am-titlebar-multi .am-titlebar-nav a{margin-right:10px}.am-titlebar-cols{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding-left:10px;background-color:#f5f5f5;color:#555;font-size:18px;border-top:2px solid #e1e1e1;line-height:41px}.am-titlebar-cols a{color:#555}.am-titlebar-cols .am-titlebar-title{color:#0e90d2;margin-right:15px;border-bottom:2px solid #0e90d2;font-weight:700}.am-titlebar-cols .am-titlebar-title a{color:#0e90d2}.am-titlebar-cols .am-titlebar-nav{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.am-titlebar-cols .am-titlebar-nav a{display:inline-block;margin-right:15px;line-height:41px;border-bottom:2px solid transparent}.am-titlebar-cols .am-titlebar-nav a:hover{color:#3c3c3c;border-bottom-color:#0e90d2}.am-titlebar-cols .am-titlebar-nav a:last-child{margin-right:10px}.am-wechatpay .am-wechatpay-btn{margin-top:1rem;margin-bottom:1rem} \ No newline at end of file diff --git a/web/assets/common/css/fullcalendar.min.css b/web/assets/common/css/fullcalendar.min.css new file mode 100644 index 0000000..f9fa1c1 --- /dev/null +++ b/web/assets/common/css/fullcalendar.min.css @@ -0,0 +1,5 @@ +/*! + * FullCalendar v0.0.0 Stylesheet + * Docs & License: http://fullcalendar.io/ + * (c) 2016 Adam Shaw + */.fc-icon,body .fc{font-size:1em}.fc-button-group,.fc-icon{display:inline-block}.fc-bg,.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-icon,.fc-unselectable{-khtml-user-select:none;-webkit-touch-callout:none}.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}.fc th,.fc-basic-view td.fc-week-number,.fc-icon,.fc-toolbar{text-align:center}.fc-unthemed .fc-content,.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-list-view,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff}.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666}.fc-unthemed .fc-today{background:#fcf8e3}.fc-highlight{background:#bce8f1;opacity:.3}.fc-bgevent{background:#8fdf82;opacity:.3}.fc-nonbusiness{background:#d7d7d7}.fc-icon{height:1em;line-height:1em;overflow:hidden;font-family:"Courier New",Courier,monospace;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon:after{position:relative}.fc-icon-left-single-arrow:after{content:"\02039";font-weight:700;font-size:200%;top:-7%}.fc-icon-right-single-arrow:after{content:"\0203A";font-weight:700;font-size:200%;top:-7%}.fc-icon-left-double-arrow:after{content:"\000AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\000BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\000D7";font-size:200%;top:6%}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;font-size:1em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;box-shadow:none}.fc-event.fc-draggable,.fc-event[href],.fc-popover .fc-header .fc-close,a[data-goto]{cursor:pointer}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-unthemed .fc-popover{border-width:1px;border-style:solid}.fc-unthemed .fc-popover .fc-header .fc-close{font-size:.9em;margin-top:2px}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-bg table,.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc table{width:100%;box-sizing:border-box;table-layout:fixed;border-collapse:collapse;border-spacing:0;font-size:1em}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}a[data-goto]:hover{text-decoration:underline}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:5}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent;border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{-webkit-overflow-scrolling:touch}.fc-row.fc-rigid,.fc-time-grid-event{overflow:hidden}.fc-scroller>.fc-day-grid,.fc-scroller>.fc-time-grid{position:relative;width:100%}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad;font-weight:400}.fc-event,.fc-event-dot{background-color:#3a87ad}.fc-event,.fc-event:hover,.ui-widget .fc-event{color:#fff;text-decoration:none}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:4;display:none}.fc-event.fc-allow-mouse-resize .fc-resizer,.fc-event.fc-selected .fc-resizer{display:block}.fc-event.fc-selected .fc-resizer:before{content:"";position:absolute;z-index:9999;top:50%;left:50%;width:40px;height:40px;margin-left:-20px;margin-top:-20px}.fc-event.fc-selected{z-index:9999!important;box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event.fc-selected.fc-dragging{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-h-event.fc-selected:before{content:"";position:absolute;z-index:3;top:-10px;bottom:-10px;left:0;right:0}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-ltr .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-end-resizer{cursor:w-resize;left:-1px}.fc-ltr .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-start-resizer{cursor:e-resize;right:-1px}.fc-h-event.fc-allow-mouse-resize .fc-resizer{width:7px;top:-1px;bottom:-1px}.fc-h-event.fc-selected .fc-resizer{border-radius:4px;border-width:1px;width:6px;height:6px;border-style:solid;border-color:inherit;background:#fff;top:50%;margin-top:-4px}.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,.fc-rtl .fc-h-event.fc-selected .fc-end-resizer{margin-left:-4px}.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,.fc-rtl .fc-h-event.fc-selected .fc-start-resizer{margin-right:-4px}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}tr:first-child>td>.fc-day-grid-event{margin-top:2px}.fc-day-grid-event.fc-selected:after{content:"";position:absolute;z-index:1;top:-1px;right:-1px;bottom:-1px;left:-1px;background:#000;opacity:.25}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer{margin-left:-2px}.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer{margin-right:-2px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc-limited{display:none}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-now-indicator{position:absolute;border:0 solid red}.fc-unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.fc-toolbar{margin-bottom:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc-toolbar .fc-center{display:inline-block}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-day-top.fc-other-month{opacity:.3}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:2px}.fc-basic-view th.fc-day-number,.fc-basic-view th.fc-week-number{padding:0 2px}.fc-ltr .fc-basic-view .fc-day-top .fc-day-number{float:right}.fc-rtl .fc-basic-view .fc-day-top .fc-day-number{float:left}.fc-ltr .fc-basic-view .fc-day-top .fc-week-number{float:left;border-radius:0 0 3px}.fc-rtl .fc-basic-view .fc-day-top .fc-week-number{float:right;border-radius:0 0 0 3px}.fc-basic-view .fc-day-top .fc-week-number{min-width:1.5em;text-align:center;background-color:#f2f2f2;color:grey}.fc-basic-view td.fc-week-number>*{display:inline-block;min-width:1.25em}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.ui-widget td.fc-axis{font-weight:400}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-content-col{position:relative}.fc-time-grid .fc-content-skeleton{position:absolute;z-index:3;top:0;left:0;right:0}.fc-time-grid .fc-business-container{position:relative;z-index:1}.fc-time-grid .fc-bgevent-container{position:relative;z-index:2}.fc-time-grid .fc-highlight-container{z-index:3;position:relative}.fc-time-grid .fc-event-container{position:relative;z-index:4}.fc-time-grid .fc-now-indicator-line{z-index:5}.fc-time-grid .fc-helper-container{position:relative;z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event.fc-selected{overflow:visible}.fc-time-grid-event.fc-selected .fc-bg{display:none}.fc-time-grid-event .fc-content{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after{content:"="}.fc-time-grid-event.fc-selected .fc-resizer{border-radius:5px;border-width:1px;width:8px;height:8px;border-style:solid;border-color:inherit;background:#fff;left:50%;margin-left:-5px;bottom:-5px}.fc-time-grid .fc-now-indicator-line{border-top-width:1px;left:0;right:0}.fc-time-grid .fc-now-indicator-arrow{margin-top:-5px}.fc-ltr .fc-time-grid .fc-now-indicator-arrow{left:0;border-width:5px 0 5px 6px;border-top-color:transparent;border-bottom-color:transparent}.fc-rtl .fc-time-grid .fc-now-indicator-arrow{right:0;border-width:5px 6px 5px 0;border-top-color:transparent;border-bottom-color:transparent}.fc-event-dot{display:inline-block;width:10px;height:10px;border-radius:5px}.fc-rtl .fc-list-view{direction:rtl}.fc-list-view{border-width:1px;border-style:solid}.fc .fc-list-table{table-layout:auto}.fc-list-table td{border-width:1px 0 0;padding:8px 14px}.fc-list-table tr:first-child td{border-top-width:0}.fc-list-heading{border-bottom-width:1px}.fc-list-heading td{font-weight:700}.fc-ltr .fc-list-heading-main{float:left}.fc-ltr .fc-list-heading-alt,.fc-rtl .fc-list-heading-main{float:right}.fc-rtl .fc-list-heading-alt{float:left}.fc-list-item.fc-has-url{cursor:pointer}.fc-list-item:hover td{background-color:#f5f5f5}.fc-list-item-marker,.fc-list-item-time{white-space:nowrap;width:1px}.fc-ltr .fc-list-item-marker{padding-right:0}.fc-rtl .fc-list-item-marker{padding-left:0}.fc-list-item-title a{text-decoration:none;color:inherit}.fc-list-item-title a[href]:hover{text-decoration:underline}.fc-list-empty-wrap2{position:absolute;top:0;left:0;right:0;bottom:0}.fc-list-empty-wrap1{width:100%;height:100%;display:table}.fc-list-empty{display:table-cell;vertical-align:middle;text-align:center}.fc-unthemed .fc-list-empty{background-color:#eee} \ No newline at end of file diff --git a/web/assets/common/css/fullcalendar.print.css b/web/assets/common/css/fullcalendar.print.css new file mode 100644 index 0000000..ad1a023 --- /dev/null +++ b/web/assets/common/css/fullcalendar.print.css @@ -0,0 +1,208 @@ +/*! + * FullCalendar v0.0.0 Print Stylesheet + * Docs & License: http://fullcalendar.io/ + * (c) 2016 Adam Shaw + */ + +/* + * Include this stylesheet on your page to get a more printer-friendly calendar. + * When including this stylesheet, use the media='print' attribute of the tag. + * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. + */ + +.fc { + max-width: 100% !important; +} + + +/* Global Event Restyling +--------------------------------------------------------------------------------------------------*/ + +.fc-event { + background: #fff !important; + color: #000 !important; + page-break-inside: avoid; +} + +.fc-event .fc-resizer { + display: none; +} + + +/* Table & Day-Row Restyling +--------------------------------------------------------------------------------------------------*/ + +.fc th, +.fc td, +.fc hr, +.fc thead, +.fc tbody, +.fc-row { + border-color: #ccc !important; + background: #fff !important; +} + +/* kill the overlaid, absolutely-positioned components */ +/* common... */ +.fc-bg, +.fc-bgevent-skeleton, +.fc-highlight-skeleton, +.fc-helper-skeleton, +/* for timegrid. within cells within table skeletons... */ +.fc-bgevent-container, +.fc-business-container, +.fc-highlight-container, +.fc-helper-container { + display: none; +} + +/* don't force a min-height on rows (for DayGrid) */ +.fc tbody .fc-row { + height: auto !important; /* undo height that JS set in distributeHeight */ + min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */ +} + +.fc tbody .fc-row .fc-content-skeleton { + position: static; /* undo .fc-rigid */ + padding-bottom: 0 !important; /* use a more border-friendly method for this... */ +} + +.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */ + padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */ +} + +.fc tbody .fc-row .fc-content-skeleton table { + /* provides a min-height for the row, but only effective for IE, which exaggerates this value, + making it look more like 3em. for other browers, it will already be this tall */ + height: 1em; +} + + +/* Undo month-view event limiting. Display all events and hide the "more" links +--------------------------------------------------------------------------------------------------*/ + +.fc-more-cell, +.fc-more { + display: none !important; +} + +.fc tr.fc-limited { + display: table-row !important; +} + +.fc td.fc-limited { + display: table-cell !important; +} + +.fc-popover { + display: none; /* never display the "more.." popover in print mode */ +} + + +/* TimeGrid Restyling +--------------------------------------------------------------------------------------------------*/ + +/* undo the min-height 100% trick used to fill the container's height */ +.fc-time-grid { + min-height: 0 !important; +} + +/* don't display the side axis at all ("all-day" and time cells) */ +.fc-agenda-view .fc-axis { + display: none; +} + +/* don't display the horizontal lines */ +.fc-slats, +.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */ + display: none !important; /* important overrides inline declaration */ +} + +/* let the container that holds the events be naturally positioned and create real height */ +.fc-time-grid .fc-content-skeleton { + position: static; +} + +/* in case there are no events, we still want some height */ +.fc-time-grid .fc-content-skeleton table { + height: 4em; +} + +/* kill the horizontal spacing made by the event container. event margins will be done below */ +.fc-time-grid .fc-event-container { + margin: 0 !important; +} + + +/* TimeGrid *Event* Restyling +--------------------------------------------------------------------------------------------------*/ + +/* naturally position events, vertically stacking them */ +.fc-time-grid .fc-event { + position: static !important; + margin: 3px 2px !important; +} + +/* for events that continue to a future day, give the bottom border back */ +.fc-time-grid .fc-event.fc-not-end { + border-bottom-width: 1px !important; +} + +/* indicate the event continues via "..." text */ +.fc-time-grid .fc-event.fc-not-end:after { + content: "..."; +} + +/* for events that are continuations from previous days, give the top border back */ +.fc-time-grid .fc-event.fc-not-start { + border-top-width: 1px !important; +} + +/* indicate the event is a continuation via "..." text */ +.fc-time-grid .fc-event.fc-not-start:before { + content: "..."; +} + +/* time */ + +/* undo a previous declaration and let the time text span to a second line */ +.fc-time-grid .fc-event .fc-time { + white-space: normal !important; +} + +/* hide the the time that is normally displayed... */ +.fc-time-grid .fc-event .fc-time span { + display: none; +} + +/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */ +.fc-time-grid .fc-event .fc-time:after { + content: attr(data-full); +} + + +/* Vertical Scroller & Containers +--------------------------------------------------------------------------------------------------*/ + +/* kill the scrollbars and allow natural height */ +.fc-scroller, +.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */ +.fc-time-grid-container { /* */ + overflow: visible !important; + height: auto !important; +} + +/* kill the horizontal border/padding used to compensate for scrollbars */ +.fc-row { + border: 0 !important; + margin: 0 !important; +} + + +/* Button Controls +--------------------------------------------------------------------------------------------------*/ + +.fc-button-group, +.fc button { + display: none; /* don't display any button-related controls */ +} diff --git a/web/assets/common/fonts/FontAwesome.otf b/web/assets/common/fonts/FontAwesome.otf new file mode 100644 index 0000000..d4de13e Binary files /dev/null and b/web/assets/common/fonts/FontAwesome.otf differ diff --git a/web/assets/common/fonts/fontawesome-webfont.eot b/web/assets/common/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..c7b00d2 Binary files /dev/null and b/web/assets/common/fonts/fontawesome-webfont.eot differ diff --git a/web/assets/common/fonts/fontawesome-webfont.ttf b/web/assets/common/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..f221e50 Binary files /dev/null and b/web/assets/common/fonts/fontawesome-webfont.ttf differ diff --git a/web/assets/common/fonts/fontawesome-webfont.woff b/web/assets/common/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..6e7483c Binary files /dev/null and b/web/assets/common/fonts/fontawesome-webfont.woff differ diff --git a/web/assets/common/fonts/fontawesome-webfont.woff2 b/web/assets/common/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..7eb74fd Binary files /dev/null and b/web/assets/common/fonts/fontawesome-webfont.woff2 differ diff --git a/web/assets/common/i/favicon.ico b/web/assets/common/i/favicon.ico new file mode 100644 index 0000000..580ed73 Binary files /dev/null and b/web/assets/common/i/favicon.ico differ diff --git a/web/assets/common/js/Sortable.min.js b/web/assets/common/js/Sortable.min.js new file mode 100644 index 0000000..4ae0349 --- /dev/null +++ b/web/assets/common/js/Sortable.min.js @@ -0,0 +1,3 @@ +/*! Sortable 1.8.0-rc1 - MIT | git://github.com/SortableJS/Sortable.git */ + +!function(t){"use strict";"function"==typeof define&&define.amd?define(t):"undefined"!=typeof module&&void 0!==module.exports?module.exports=t():window.Sortable=t()}(function(){"use strict";if("undefined"==typeof window||!window.document)return function(){throw new Error("Sortable.js requires a window with a document")};var T,S,E,x,A,B,h,w,y,D,c,i,N,P,l,s,d,u,C,X,Y,k,R,t,o,I=[],M=!1,H=!1,O=!1,r=/\s+/g,L="Sortable"+(new Date).getTime(),F=window,f=F.document,W=F.parseInt,U=F.setTimeout,e=F.jQuery||F.Zepto,n=F.Polymer,a={capture:!1,passive:!1},p="draggable"in f.createElement("div"),g=!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)&&((t=f.createElement("x")).style.cssText="pointer-events:auto","auto"===t.style.pointerEvents),j=!1,v=!1,z=Math.abs,m=Math.min,_=[],b=[],V=function(t,e){var n=it(t),i=W(n.width),o=ct(t,0,e),r=ct(t,1,e),a=o&&it(o),l=r&&it(r),s=a&&W(a.marginLeft)+W(a.marginRight)+o.getBoundingClientRect().width,c=l&&W(l.marginLeft)+W(l.marginRight)+r.getBoundingClientRect().width;return"flex"===n.display?"column"===n.flexDirection||"column-reverse"===n.flexDirection?"vertical":"horizontal":o&&("block"===a.display||"grid"===a.display||i<=s&&"none"===n.float||r&&"none"===n.float&&i*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,touchStartThreshold:W(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==K.supportPointer&&("PointerEvent"in window||window.navigator&&"msPointerEnabled"in window.navigator)};for(var i in n)!(i in e)&&(e[i]=n[i]);for(var o in"direction"in e||(e.direction=function(){return V(t,e)}),Q(e),null==e.invertedSwapThreshold&&(e.invertedSwapThreshold=e.swapThreshold),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&p,tt(t,"mousedown",this._onTapStart),tt(t,"touchstart",this._onTapStart),e.supportPointer&&tt(t,"pointerdown",this._onTapStart),this.nativeDraggable&&(tt(t,"dragover",this),tt(t,"dragenter",this)),b.push(this._onDragOver),e.store&&e.store.get&&this.sort(e.store.get(this)||[])}function $(t,e,n,i){if(t){n=n||f;do{if(">*"===e&&t.parentNode===n||ft(t,e)||i&&t===n)return t;if(t===n)break}while(t=(o=t).host&&o!==f&&o.host.nodeType?o.host:o.parentNode)}var o;return null}function tt(t,e,n){t.addEventListener(e,n,a)}function et(t,e,n){t.removeEventListener(e,n,a)}function nt(t,e,n){if(t&&e)if(t.classList)t.classList[n?"add":"remove"](e);else{var i=(" "+t.className+" ").replace(r," ").replace(" "+e+" "," ");t.className=(i+(n?" "+e:"")).replace(r," ")}}function it(t,e,n){var i=t&&t.style;if(i){if(void 0===n)return f.defaultView&&f.defaultView.getComputedStyle?n=f.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in i||(e="-webkit-"+e),i[e]=n+("string"==typeof n?"":"px")}}function ot(t,e,n){if(t){var i=t.getElementsByTagName(e),o=0,r=i.length;if(n)for(;o*"!==e&&!ft(t,e)||n++;return n}function ft(t,e){if(t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e)}catch(t){return!1}return!1}function pt(n,i){return function(){if(!o){var t=arguments,e=this;o=U(function(){1===t.length?n.call(e,t[0]):n.apply(e,t),o=void 0},i)}}}function gt(t,e){if(t&&e)for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function vt(t){return n&&n.dom?n.dom(t).cloneNode(!0):e?e(t).clone(!0)[0]:t.cloneNode(!0)}function mt(t){return U(t,0)}function _t(t){return clearTimeout(t)}function bt(t){K.active&&t.cancelable&&t.preventDefault()}return K.prototype={constructor:K,_isAligned:!0,_computeIsAligned:function(t,e){var n,i,o,r,a,l,s;v||T&&T.parentNode===this.el&&(!0!==e&&!1!==e&&(e=!!$(t.target,null,T,!0)),this._isAligned=!M&&(e||this._isAligned&&(n=t.clientX,i=t.clientY,this.el,o=this._getDirection(t,null),this.options,r=R||T.getBoundingClientRect(),a="vertical"===o?r.left:r.top,l="vertical"===o?r.right:r.bottom,a<(s="vertical"===o?n:i)&&s=this.options.touchStartThreshold&&this._disableDelayedDrag()},_disableDelayedDrag:function(){var t=this.el.ownerDocument;clearTimeout(this._dragStartTimer),et(t,"mouseup",this._disableDelayedDrag),et(t,"touchend",this._disableDelayedDrag),et(t,"touchcancel",this._disableDelayedDrag),et(t,"mousemove",this._delayedDragTouchMoveHandler),et(t,"touchmove",this._delayedDragTouchMoveHandler),et(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){(e=e||("touch"==t.pointerType?t:null))?(u={target:T,clientX:e.clientX,clientY:e.clientY},this._onDragStart(u,"touch")):this.nativeDraggable?(tt(T,"dragend",this),tt(A,"dragstart",this._onDragStart)):this._onDragStart(u,!0);try{f.selection?mt(function(){f.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(){if(A&&T){this.nativeDraggable&&(tt(f,"dragover",this._handleAutoScroll),tt(f,"dragover",J));var t=this.options;nt(T,t.dragClass,!1),nt(T,t.ghostClass,!0),it(T,"transform",""),(K.active=this)._isAligned=!0,rt(this,A,"start",T,A,A,c)}else this._nulling()},_emulateDragOver:function(t){if(C){if(this._lastX===C.clientX&&this._lastY===C.clientY&&!t)return;this._lastX=C.clientX,this._lastY=C.clientY,g||it(E,"display","none");for(var e=f.elementFromPoint(C.clientX,C.clientY),n=e,i=!!$(e,null,T,!0);e&&e.shadowRoot;)n=e=e.shadowRoot.elementFromPoint(C.clientX,C.clientY);if(n)do{if(n[L]){for(var o=b.length;o--;)b[o]({clientX:C.clientX,clientY:C.clientY,target:e,rootEl:n});if(!this.options.dragoverBubble)break}e=n}while(n=n.parentNode);T.parentNode[L]._computeIsAligned(C,i),g||it(E,"display","")}},_onTouchMove:function(t){if(u){var e=this.options,n=e.fallbackTolerance,i=e.fallbackOffset,o=t.touches?t.touches[0]:t,r=o.clientX-u.clientX+i.x,a=o.clientY-u.clientY+i.y,l=t.touches?"translate3d("+r+"px,"+a+"px,0)":"translate("+r+"px,"+a+"px)";if(this.options.supportPointer&&"touchmove"===t.type)return;if(!K.active){if(n&&m(z(o.clientX-this._lastX),z(o.clientY-this._lastY))0||window.navigator.pointerEnabled&&window.navigator.maxTouchPoints>0||!1,s.support.mutationobserver=window.MutationObserver||window.WebKitMutationObserver||null,s.support.formValidation="function"==typeof document.createElement("form").checkValidity,s.utils={},s.utils.debounce=function(t,e,i){var n;return function(){var s=this,o=arguments,a=function(){n=null,i||t.apply(s,o)},r=i&&!n;clearTimeout(n),n=setTimeout(a,e),r&&t.apply(s,o)}},s.utils.isInView=function(t,e){var i=n(t),s=!(!i.width()&&!i.height())&&"none"!==i.css("display");if(!s)return!1;var a=o.scrollLeft(),r=o.scrollTop(),l=i.offset(),c=l.left,u=l.top;return e=n.extend({topOffset:0,leftOffset:0},e),u+i.height()>=r&&u-e.topOffset<=r+o.height()&&c+i.width()>=a&&c-e.leftOffset<=a+o.width()},s.utils.parseOptions=s.utils.options=function(t){if(n.isPlainObject(t))return t;var e=t?t.indexOf("{"):-1,i={};if(e!=-1)try{i=new Function("","var json = "+t.substr(e)+"; return JSON.parse(JSON.stringify(json));")()}catch(s){}return i},s.utils.generateGUID=function(t){var e=t+"-"||"am-";do e+=Math.random().toString(36).substring(2,7);while(document.getElementById(e));return e},s.utils.getAbsoluteUrl=function(){var t;return function(e){return t||(t=document.createElement("a")),t.href=e,t.href}}(),s.plugin=function(t,e,i){var o=n.fn[t];i=i||{},n.fn[t]=function(o){var a,r=Array.prototype.slice.call(arguments,0),l=r.slice(1),c=this.each(function(){var c=n(this),u="amui."+t,h=i.dataOptions||"data-am-"+t,d=c.data(u),p=n.extend({},s.utils.parseOptions(c.attr(h)),"object"==typeof o&&o);(d||"destroy"!==o)&&(d||c.data(u,d=new e(this,p)),i.methodCall?i.methodCall.call(c,r,d):(i.before&&i.before.call(c,r,d),"string"==typeof o&&(a="function"==typeof d[o]?d[o].apply(d,l):d[o]),i.after&&i.after.call(c,r,d)))});return void 0===a?c:a},n.fn[t].Constructor=e,n.fn[t].noConflict=function(){return n.fn[t]=o,this},s[t]=e},n.fn.emulateTransitionEnd=function(t){var e=!1,i=this;n(this).one(s.support.transition.end,function(){e=!0});var o=function(){e||n(i).trigger(s.support.transition.end),i.transitionEndTimmer=void 0};return this.transitionEndTimmer=setTimeout(o,t),this},n.fn.redraw=function(){return this.each(function(){this.offsetHeight})},n.fn.transitionEnd=function(t){function e(s){t.call(this,s),i&&n.off(i,e)}var i=s.support.transition.end,n=this;return t&&i&&n.on(i,e),this},n.fn.removeClassRegEx=function(){return this.each(function(t){var e=n(this).attr("class");if(!e||!t)return!1;var i=[];e=e.split(" ");for(var s=0,o=e.length;s=window.innerWidth)return 0;var t=n('
    ');n(document.body).append(t);var e=t[0].offsetWidth-t[0].clientWidth;return t.remove(),e},s.utils.imageLoader=function(t,e){function i(){e(t[0])}function n(){if(this.one("load",i),/MSIE (\d+\.\d+);/.test(navigator.userAgent)){var t=this.attr("src"),e=t.match(/\?/)?"&":"?";e+="random="+(new Date).getTime(),this.attr("src",t+e)}}return t.attr("src")?void(t[0].complete||4===t[0].readyState?i():n.call(t)):void i()},s.template=function(t,e){var i=s.template;return i.cache[t]||(i.cache[t]=function(){var e=t,n=/^[\w\-]+$/.test(t)?i.get(t):(e="template(string)",t),s=1,o=("try { "+(i.variable?"var "+i.variable+" = this.stash;":"with (this.stash) { ")+"this.ret += '"+n.replace(/<%/g,"\x11").replace(/%>/g,"\x13").replace(/'(?![^\x11\x13]+?\x13)/g,"\\x27").replace(/^\s*|\s*$/g,"").replace(/\n/g,function(){return"';\nthis.line = "+ ++s+"; this.ret += '\\n"}).replace(/\x11-(.+?)\x13/g,"' + ($1) + '").replace(/\x11=(.+?)\x13/g,"' + this.escapeHTML($1) + '").replace(/\x11(.+?)\x13/g,"'; $1; this.ret += '")+"'; "+(i.variable?"":"}")+"return this.ret;} catch (e) { throw 'TemplateError: ' + e + ' (on "+e+"' + ' line ' + this.line + ')'; } //@ sourceURL="+e+"\n").replace(/this\.ret \+= '';/g,""),a=new Function(o),r={"&":"&","<":"<",">":">",'"':""","'":"'"},l=function(t){return(""+t).replace(/[&<>\'\"]/g,function(t){return r[t]})};return function(t){return a.call(i.context={escapeHTML:l,line:1,ret:"",stash:t})}}()),e?i.cache[t](e):i.cache[t]},s.template.cache={},s.template.get=function(t){if(t){var e=document.getElementById(t);return e&&e.innerHTML||""}},s.DOMWatchers=[],s.DOMReady=!1,s.ready=function(t){s.DOMWatchers.push(t),s.DOMReady&&t(document)},s.DOMObserve=function(t,e,i){var o=s.support.mutationobserver;o&&(e=n.isPlainObject(e)?e:{childList:!0,subtree:!0},i="function"==typeof i&&i||function(){},n(t).each(function(){var t=this,a=n(t);if(!a.data("am.observer"))try{var r=new o(s.utils.debounce(function(e,n){i.call(t,e,n),a.trigger("changed.dom.amui")},50));r.observe(t,e),a.data("am.observer",r)}catch(l){}}))},n.fn.DOMObserve=function(t,e){return this.each(function(){s.DOMObserve(this,t,e)})},s.support.touch&&r.addClass("am-touch"),n(document).on("changed.dom.amui",function(t){var e=t.target;n.each(s.DOMWatchers,function(t,i){i(e)})}),n(function(){var t=n(document.body);s.DOMReady=!0,n.each(s.DOMWatchers,function(t,e){e(document)}),s.DOMObserve("[data-am-observe]"),r.removeClass("no-js").addClass("js"),s.support.animation&&r.addClass("cssanimations"),window.navigator.standalone&&r.addClass("am-standalone"),n(".am-topbar-fixed-top").length&&t.addClass("am-with-topbar-fixed-top"),n(".am-topbar-fixed-bottom").length&&t.addClass("am-with-topbar-fixed-bottom");var e=n(".am-layout");e.find('[class*="md-block-grid"]').alterClass("md-block-grid-*"),e.find('[class*="lg-block-grid"]').alterClass("lg-block-grid"),n("[data-am-widget]").each(function(){var t=n(this);0===t.parents(".am-layout").length&&t.addClass("am-no-layout")})}),t.exports=s},function(t,e,i){"use strict";function n(t,e,i){return setTimeout(l(t,i),e)}function s(t,e,i){return!!Array.isArray(t)&&(o(t,i[e],i),!0)}function o(t,e,i){var n;if(t)if(t.forEach)t.forEach(e,i);else if(void 0!==t.length)for(n=0;n\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",s=window.console&&(window.console.warn||window.console.log);return s&&s.call(window.console,n,i),t.apply(this,arguments)}}function r(t,e,i){var n,s=e.prototype;n=t.prototype=Object.create(s),n.constructor=t,n._super=s,i&&ut(n,i)}function l(t,e){return function(){return t.apply(e,arguments)}}function c(t,e){return typeof t==ft?t.apply(e?e[0]||void 0:void 0,e):t}function u(t,e){return void 0===t?e:t}function h(t,e,i){o(f(e),function(e){t.addEventListener(e,i,!1)})}function d(t,e,i){o(f(e),function(e){t.removeEventListener(e,i,!1)})}function p(t,e){for(;t;){if(t==e)return!0;t=t.parentNode}return!1}function m(t,e){return t.indexOf(e)>-1}function f(t){return t.trim().split(/\s+/g)}function v(t,e,i){if(t.indexOf&&!i)return t.indexOf(e);for(var n=0;ni[e]}):n.sort()),n}function w(t,e){for(var i,n,s=e[0].toUpperCase()+e.slice(1),o=0;o1&&!i.firstMultiple?i.firstMultiple=F(e):1===s&&(i.firstMultiple=!1);var o=i.firstInput,a=i.firstMultiple,r=a?a.center:o.center,l=e.center=A(n);e.timeStamp=yt(),e.deltaTime=e.timeStamp-o.timeStamp,e.angle=N(r,l),e.distance=P(r,l),k(i,e),e.offsetDirection=M(e.deltaX,e.deltaY);var c=$(e.deltaTime,e.deltaX,e.deltaY);e.overallVelocityX=c.x,e.overallVelocityY=c.y,e.overallVelocity=gt(c.x)>gt(c.y)?c.x:c.y,e.scale=a?O(a.pointers,n):1,e.rotation=a?I(a.pointers,n):0,e.maxPointers=i.prevInput?e.pointers.length>i.prevInput.maxPointers?e.pointers.length:i.prevInput.maxPointers:e.pointers.length,D(i,e);var u=t.element;p(e.srcEvent.target,u)&&(u=e.srcEvent.target),e.target=u}function k(t,e){var i=e.center,n=t.offsetDelta||{},s=t.prevDelta||{},o=t.prevInput||{};e.eventType!==Mt&&o.eventType!==Nt||(s=t.prevDelta={x:o.deltaX||0,y:o.deltaY||0},n=t.offsetDelta={x:i.x,y:i.y}),e.deltaX=s.x+(i.x-n.x),e.deltaY=s.y+(i.y-n.y)}function D(t,e){var i,n,s,o,a=t.lastInterval||e,r=e.timeStamp-a.timeStamp;if(e.eventType!=It&&(r>$t||void 0===a.velocity)){var l=e.deltaX-a.deltaX,c=e.deltaY-a.deltaY,u=$(r,l,c);n=u.x,s=u.y,i=gt(u.x)>gt(u.y)?u.x:u.y,o=M(l,c),t.lastInterval=e}else i=a.velocity,n=a.velocityX,s=a.velocityY,o=a.direction;e.velocity=i,e.velocityX=n,e.velocityY=s,e.direction=o}function F(t){for(var e=[],i=0;i=gt(e)?t<0?Lt:_t:e<0?zt:Rt}function P(t,e,i){i||(i=Bt);var n=e[i[0]]-t[i[0]],s=e[i[1]]-t[i[1]];return Math.sqrt(n*n+s*s)}function N(t,e,i){i||(i=Bt);var n=e[i[0]]-t[i[0]],s=e[i[1]]-t[i[1]];return 180*Math.atan2(s,n)/Math.PI}function I(t,e){return N(e[1],e[0],Ut)+N(t[1],t[0],Ut)}function O(t,e){return P(e[0],e[1],Ut)/P(t[0],t[1],Ut)}function L(){this.evEl=Xt,this.evWin=Yt,this.pressed=!1,x.apply(this,arguments)}function _(){this.evEl=Gt,this.evWin=Kt,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function z(){this.evTarget=Qt,this.evWin=te,this.started=!1,x.apply(this,arguments)}function R(t,e){var i=g(t.touches),n=g(t.changedTouches);return e&(Nt|It)&&(i=y(i.concat(n),"identifier",!0)),[i,n]}function q(){this.evTarget=ie,this.targetIds={},x.apply(this,arguments)}function W(t,e){var i=g(t.touches),n=this.targetIds;if(e&(Mt|Pt)&&1===i.length)return n[i[0].identifier]=!0,[i,i];var s,o,a=g(t.changedTouches),r=[],l=this.target;if(o=i.filter(function(t){return p(t.target,l)}),e===Mt)for(s=0;s-1&&n.splice(t,1)};setTimeout(s,ne)}}function V(t){for(var e=t.srcEvent.clientX,i=t.srcEvent.clientY,n=0;n-1&&this.requireFail.splice(e,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(t){return!!this.simultaneous[t.id]},emit:function(t){function e(e){i.manager.emit(e,t)}var i=this,n=this.state;n=ge&&e(i.options.event+G(n))},tryEmit:function(t){return this.canEmit()?this.emit(t):void(this.state=be)},canEmit:function(){for(var t=0;te.threshold&&s&e.direction},attrTest:function(t){return Q.prototype.attrTest.call(this,t)&&(this.state&fe||!(this.state&fe)&&this.directionTest(t))},emit:function(t){this.pX=t.deltaX,this.pY=t.deltaY;var e=K(t.direction);e&&(t.additionalEvent=this.options.event+e),this._super.emit.call(this,t)}}),r(et,Q,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[ue]},attrTest:function(t){return this._super.attrTest.call(this,t)&&(Math.abs(t.scale-1)>this.options.threshold||this.state&fe)},emit:function(t){if(1!==t.scale){var e=t.scale<1?"in":"out";t.additionalEvent=this.options.event+e}this._super.emit.call(this,t)}}),r(it,Z,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[le]},process:function(t){var e=this.options,i=t.pointers.length===e.pointers,s=t.distancee.time;if(this._input=t,!s||!i||t.eventType&(Nt|It)&&!o)this.reset();else if(t.eventType&Mt)this.reset(),this._timer=n(function(){this.state=ye,this.tryEmit()},e.time,this);else if(t.eventType&Nt)return ye;return be},reset:function(){clearTimeout(this._timer)},emit:function(t){this.state===ye&&(t&&t.eventType&Nt?this.manager.emit(this.options.event+"up",t):(this._input.timeStamp=yt(),this.manager.emit(this.options.event,this._input)))}}),r(nt,Q,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[ue]},attrTest:function(t){return this._super.attrTest.call(this,t)&&(Math.abs(t.rotation)>this.options.threshold||this.state&fe)}}),r(st,Q,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:qt|Wt,pointers:1},getTouchAction:function(){return tt.prototype.getTouchAction.call(this)},attrTest:function(t){var e,i=this.options.direction;return i&(qt|Wt)?e=t.overallVelocity:i&qt?e=t.overallVelocityX:i&Wt&&(e=t.overallVelocityY),this._super.attrTest.call(this,t)&&i&t.offsetDirection&&t.distance>this.options.threshold&&t.maxPointers==this.options.pointers&>(e)>this.options.velocity&&t.eventType&Nt},emit:function(t){var e=K(t.offsetDirection);e&&this.manager.emit(this.options.event+e,t),this.manager.emit(this.options.event,t)}}),r(ot,Z,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ce]},process:function(t){var e=this.options,i=t.pointers.length===e.pointers,s=t.distanceAdd to Home Screen.",android:'To add this web app to the home screen open the browser option menu and tap on Add to homescreen. The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon icon.'},zh_cn:{ios:"\u5982\u8981\u628a\u5e94\u7528\u7a0b\u5f0f\u52a0\u81f3\u4e3b\u5c4f\u5e55,\u8bf7\u70b9\u51fb%icon, \u7136\u540e\u52a0\u81f3\u4e3b\u5c4f\u5e55",android:'To add this web app to the home screen open the browser option menu and tap on Add to homescreen. The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon icon.'},zh_tw:{ios:"\u5982\u8981\u628a\u61c9\u7528\u7a0b\u5f0f\u52a0\u81f3\u4e3b\u5c4f\u5e55, \u8acb\u9ede\u64ca%icon, \u7136\u5f8c\u52a0\u81f3\u4e3b\u5c4f\u5e55.",android:'To add this web app to the home screen open the browser option menu and tap on Add to homescreen. The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon icon.'}};for(var p in s.intl)s.intl[p.substr(0,2)]=s.intl[p];s.defaults={appID:"org.cubiq.addtohome",fontSize:15,debug:!1,logging:!1,modal:!1,mandatory:!1,autostart:!0,skipFirstVisit:!1,startDelay:1,lifespan:15,displayPace:1440,maxDisplayCount:0,icon:!0,message:"",validLocation:[],onInit:null,onShow:null,onRemove:null,onAdd:null,onPrivate:null,privateModeOverride:!1,detectHomescreen:!1};var m=window.navigator.userAgent,f=window.navigator;o(s,{hasToken:"#ath"==document.location.hash||h.test(document.location.href)||d.test(document.location.search),isRetina:window.devicePixelRatio&&window.devicePixelRatio>1,isIDevice:/iphone|ipod|ipad/i.test(m), +isMobileChrome:m.indexOf("Android")>-1&&/Chrome\/[.0-9]*/.test(m)&&m.indexOf("Version")==-1,isMobileIE:m.indexOf("Windows Phone")>-1,language:f.language&&f.language.toLowerCase().replace("-","_")||""}),s.language=s.language&&s.language in s.intl?s.language:"en_us",s.isMobileSafari=s.isIDevice&&m.indexOf("Safari")>-1&&m.indexOf("CriOS")<0,s.OS=s.isIDevice?"ios":s.isMobileChrome?"android":s.isMobileIE?"windows":"unsupported",s.OSVersion=m.match(/(OS|Android) (\d+[_\.]\d+)/),s.OSVersion=s.OSVersion&&s.OSVersion[2]?+s.OSVersion[2].replace("_","."):0,s.isStandalone="standalone"in window.navigator&&window.navigator.standalone,s.isTablet=s.isMobileSafari&&m.indexOf("iPad")>-1||s.isMobileChrome&&m.indexOf("Mobile")<0,s.isCompatible=s.isMobileSafari&&s.OSVersion>=6||s.isMobileChrome;var v={lastDisplayTime:0,returningVisitor:!1,displayCount:0,optedout:!1,added:!1};s.removeSession=function(t){try{if(!localStorage)throw new Error("localStorage is not defined");localStorage.removeItem(t||s.defaults.appID)}catch(e){}},s.doLog=function(t){this.options.logging&&console.log(t)},s.Class=function(t){if(this.doLog=s.doLog,this.options=o({},s.defaults),o(this.options,t),this.options.debug&&(this.options.logging=!0),l){if(this.options.mandatory=this.options.mandatory&&("standalone"in window.navigator||this.options.debug),this.options.modal=this.options.modal||this.options.mandatory,this.options.mandatory&&(this.options.startDelay=-.5),this.options.detectHomescreen=this.options.detectHomescreen===!0?"hash":this.options.detectHomescreen,this.options.debug&&(s.isCompatible=!0,s.OS="string"==typeof this.options.debug?this.options.debug:"unsupported"==s.OS?"android":s.OS,s.OSVersion="ios"==s.OS?"8":"4"),this.container=document.documentElement,this.session=this.getItem(this.options.appID),this.session=this.session?JSON.parse(this.session):void 0,!s.hasToken||s.isCompatible&&this.session||(s.hasToken=!1,a()),!s.isCompatible)return void this.doLog("Add to homescreen: not displaying callout because device not supported");this.session=this.session||v;try{if(!localStorage)throw new Error("localStorage is not defined");localStorage.setItem(this.options.appID,JSON.stringify(this.session)),s.hasLocalStorage=!0}catch(e){s.hasLocalStorage=!1,this.options.onPrivate&&this.options.onPrivate.call(this)}for(var i=!this.options.validLocation.length,n=this.options.validLocation.length;n--;)if(this.options.validLocation[n].test(document.location.href)){i=!0;break}if(this.getItem("addToHome")&&this.optOut(),this.session.optedout)return void this.doLog("Add to homescreen: not displaying callout because user opted out");if(this.session.added)return void this.doLog("Add to homescreen: not displaying callout because already added to the homescreen");if(!i)return void this.doLog("Add to homescreen: not displaying callout because not a valid location");if(s.isStandalone)return this.session.added||(this.session.added=!0,this.updateSession(),this.options.onAdd&&s.hasLocalStorage&&this.options.onAdd.call(this)),void this.doLog("Add to homescreen: not displaying callout because in standalone mode");if(this.options.detectHomescreen){if(s.hasToken)return a(),this.session.added||(this.session.added=!0,this.updateSession(),this.options.onAdd&&s.hasLocalStorage&&this.options.onAdd.call(this)),void this.doLog("Add to homescreen: not displaying callout because URL has token, so we are likely coming from homescreen");"hash"==this.options.detectHomescreen?history.replaceState("",window.document.title,document.location.href+"#ath"):"smartURL"==this.options.detectHomescreen?history.replaceState("",window.document.title,document.location.href.replace(/(\/)?$/,"/ath$1")):history.replaceState("",window.document.title,document.location.href+(document.location.search?"&":"?")+"ath=")}if(!this.session.returningVisitor&&(this.session.returningVisitor=!0,this.updateSession(),this.options.skipFirstVisit))return void this.doLog("Add to homescreen: not displaying callout because skipping first visit");if(!this.options.privateModeOverride&&!s.hasLocalStorage)return void this.doLog("Add to homescreen: not displaying callout because browser is in private mode");this.ready=!0,this.options.onInit&&this.options.onInit.call(this),this.options.autostart&&(this.doLog("Add to homescreen: autostart displaying callout"),this.show())}},s.Class.prototype={events:{load:"_delayedShow",error:"_delayedShow",orientationchange:"resize",resize:"resize",scroll:"resize",click:"remove",touchmove:"_preventDefault",transitionend:"_removeElements",webkitTransitionEnd:"_removeElements",MSTransitionEnd:"_removeElements"},handleEvent:function(t){var e=this.events[t.type];e&&this[e](t)},show:function(t){if(this.options.autostart&&!c)return void setTimeout(this.show.bind(this),50);if(this.shown)return void this.doLog("Add to homescreen: not displaying callout because already shown on screen");var e=Date.now(),i=this.session.lastDisplayTime;if(t!==!0){if(!this.ready)return void this.doLog("Add to homescreen: not displaying callout because not ready");if(e-i<6e4*this.options.displayPace)return void this.doLog("Add to homescreen: not displaying callout because displayed recently");if(this.options.maxDisplayCount&&this.session.displayCount>=this.options.maxDisplayCount)return void this.doLog("Add to homescreen: not displaying callout because displayed too many times already")}this.shown=!0,this.session.lastDisplayTime=e,this.session.displayCount++,this.updateSession(),this.applicationIcon||("ios"==s.OS?this.applicationIcon=document.querySelector('head link[rel^=apple-touch-icon][sizes="152x152"],head link[rel^=apple-touch-icon][sizes="144x144"],head link[rel^=apple-touch-icon][sizes="120x120"],head link[rel^=apple-touch-icon][sizes="114x114"],head link[rel^=apple-touch-icon]'):this.applicationIcon=document.querySelector('head link[rel^="shortcut icon"][sizes="196x196"],head link[rel^=apple-touch-icon]'));var n="";"object"==typeof this.options.message&&s.language in this.options.message?n=this.options.message[s.language][s.OS]:"object"==typeof this.options.message&&s.OS in this.options.message?n=this.options.message[s.OS]:this.options.message in s.intl?n=s.intl[this.options.message][s.OS]:""!==this.options.message?n=this.options.message:s.OS in s.intl[s.language]&&(n=s.intl[s.language][s.OS]),n="

    "+n.replace("%icon",'icon')+"

    ",this.viewport=document.createElement("div"),this.viewport.className="ath-viewport",this.options.modal&&(this.viewport.className+=" ath-modal"),this.options.mandatory&&(this.viewport.className+=" ath-mandatory"),this.viewport.style.position="absolute",this.element=document.createElement("div"),this.element.className="ath-container ath-"+s.OS+" ath-"+s.OS+(s.OSVersion+"").substr(0,1)+" ath-"+(s.isTablet?"tablet":"phone"),this.element.style.cssText="-webkit-transition-property:-webkit-transform,opacity;-webkit-transition-duration:0s;-webkit-transition-timing-function:ease-out;transition-property:transform,opacity;transition-duration:0s;transition-timing-function:ease-out;",this.element.style.webkitTransform="translate3d(0,-"+window.innerHeight+"px,0)",this.element.style.transform="translate3d(0,-"+window.innerHeight+"px,0)",this.options.icon&&this.applicationIcon&&(this.element.className+=" ath-icon",this.img=document.createElement("img"),this.img.className="ath-application-icon",this.img.addEventListener("load",this,!1),this.img.addEventListener("error",this,!1),this.img.src=this.applicationIcon.href,this.element.appendChild(this.img)),this.element.innerHTML+=n,this.viewport.style.left="-99999em",this.viewport.appendChild(this.element),this.container.appendChild(this.viewport),this.img?this.doLog("Add to homescreen: not displaying callout because waiting for img to load"):this._delayedShow()},_delayedShow:function(t){setTimeout(this._show.bind(this),1e3*this.options.startDelay+500)},_show:function(){var t=this;this.updateViewport(),window.addEventListener("resize",this,!1),window.addEventListener("scroll",this,!1),window.addEventListener("orientationchange",this,!1),this.options.modal&&document.addEventListener("touchmove",this,!0),this.options.mandatory||setTimeout(function(){t.element.addEventListener("click",t,!0)},1e3),setTimeout(function(){t.element.style.webkitTransitionDuration="1.2s",t.element.style.transitionDuration="1.2s",t.element.style.webkitTransform="translate3d(0,0,0)",t.element.style.transform="translate3d(0,0,0)"},0),this.options.lifespan&&(this.removeTimer=setTimeout(this.remove.bind(this),1e3*this.options.lifespan)),this.options.onShow&&this.options.onShow.call(this)},remove:function(){clearTimeout(this.removeTimer),this.img&&(this.img.removeEventListener("load",this,!1),this.img.removeEventListener("error",this,!1)),window.removeEventListener("resize",this,!1),window.removeEventListener("scroll",this,!1),window.removeEventListener("orientationchange",this,!1),document.removeEventListener("touchmove",this,!0),this.element.removeEventListener("click",this,!0),this.element.addEventListener("transitionend",this,!1),this.element.addEventListener("webkitTransitionEnd",this,!1),this.element.addEventListener("MSTransitionEnd",this,!1),this.element.style.webkitTransitionDuration="0.3s",this.element.style.opacity="0"},_removeElements:function(){this.element.removeEventListener("transitionend",this,!1),this.element.removeEventListener("webkitTransitionEnd",this,!1),this.element.removeEventListener("MSTransitionEnd",this,!1),this.container.removeChild(this.viewport),this.shown=!1,this.options.onRemove&&this.options.onRemove.call(this)},updateViewport:function(){if(this.shown){this.viewport.style.width=window.innerWidth+"px",this.viewport.style.height=window.innerHeight+"px",this.viewport.style.left=window.scrollX+"px",this.viewport.style.top=window.scrollY+"px";var t=document.documentElement.clientWidth;this.orientation=t>document.documentElement.clientHeight?"landscape":"portrait";var e="ios"==s.OS?"portrait"==this.orientation?screen.width:screen.height:screen.width;this.scale=screen.width>t?1:e/window.innerWidth,this.element.style.fontSize=this.options.fontSize/this.scale+"px"}},resize:function(){clearTimeout(this.resizeTimer),this.resizeTimer=setTimeout(this.updateViewport.bind(this),100)},updateSession:function(){s.hasLocalStorage!==!1&&localStorage&&localStorage.setItem(this.options.appID,JSON.stringify(this.session))},clearSession:function(){this.session=v,this.updateSession()},getItem:function(t){try{if(!localStorage)throw new Error("localStorage is not defined");return localStorage.getItem(t)}catch(e){s.hasLocalStorage=!1}},optOut:function(){this.session.optedout=!0,this.updateSession()},optIn:function(){this.session.optedout=!1,this.updateSession()},clearDisplayCount:function(){this.session.displayCount=0,this.updateSession()},_preventDefault:function(t){t.preventDefault(),t.stopPropagation()}},s.VERSION="3.2.2",t.exports=r.addToHomescreen=s},function(t,e,i){"use strict";var n=i(1),s=i(2),o=function(t,e){var i=this;this.options=n.extend({},o.DEFAULTS,e),this.$element=n(t),this.$element.addClass("am-fade am-in").on("click.alert.amui",".am-close",function(){i.close()})};o.DEFAULTS={removeElement:!0},o.prototype.close=function(){function t(){e.trigger("closed.alert.amui").remove()}var e=this.$element;e.trigger("close.alert.amui").removeClass("am-in"),s.support.transition&&e.hasClass("am-fade")?e.one(s.support.transition.end,t).emulateTransitionEnd(200):t()},s.plugin("alert",o),n(document).on("click.alert.amui.data-api","[data-am-alert]",function(t){var e=n(t.target);e.is(".am-close")&&n(this).alert("close")}),t.exports=o},function(t,e,i){"use strict";var n=i(1),s=i(2),o=function(t,e){this.$element=n(t),this.options=n.extend({},o.DEFAULTS,e),this.isLoading=!1,this.hasSpinner=!1};o.DEFAULTS={loadingText:"loading...",disabledClassName:"am-disabled",activeClassName:"am-active",spinner:void 0},o.prototype.setState=function(t,e){var i=this.$element,o="disabled",a=i.data(),r=this.options,l=i.is("input")?"val":"html",c="am-btn-"+t+" "+r.disabledClassName;t+="Text",r.resetText||(r.resetText=i[l]()),s.support.animation&&r.spinner&&"html"===l&&!this.hasSpinner&&(r.loadingText=''+r.loadingText,this.hasSpinner=!0),e=e||(void 0===a[t]?r[t]:a[t]),i[l](e),setTimeout(n.proxy(function(){"loadingText"===t?(i.addClass(c).attr(o,o),this.isLoading=!0):this.isLoading&&(i.removeClass(c).removeAttr(o),this.isLoading=!1)},this),0)},o.prototype.toggle=function(){var t=!0,e=this.$element,i=this.$element.parent('[class*="am-btn-group"]'),n=o.DEFAULTS.activeClassName;if(i.length){var s=this.$element.find("input");"radio"==s.prop("type")&&(s.prop("checked")&&e.hasClass(n)?t=!1:i.find("."+n).removeClass(n)),t&&s.prop("checked",!e.hasClass(n)).trigger("change")}t&&(e.toggleClass(n),e.hasClass(n)||e.blur())},s.plugin("button",o,{dataOptions:"data-am-loading",methodCall:function(t,e){"toggle"===t[0]?e.toggle():"string"==typeof t[0]&&e.setState.apply(e,t)}}),n(document).on("click.button.amui.data-api","[data-am-button]",function(t){t.preventDefault();var e=n(t.target);e.hasClass("am-btn")||(e=e.closest(".am-btn")),e.button("toggle")}),s.ready(function(t){n("[data-am-loading]",t).button(),n("[data-am-button]",t).find("input:checked").each(function(){n(this).parent("label").addClass(o.DEFAULTS.activeClassName)})}),t.exports=s.button=o},function(t,e,i){"use strict";function n(t){return this.each(function(){var e=s(this),i=e.data("amui.collapse"),n=s.extend({},a.DEFAULTS,o.utils.options(e.attr("data-am-collapse")),"object"==typeof t&&t);!i&&n.toggle&&"open"===t&&(t=!t),i||e.data("amui.collapse",i=new a(this,n)),"string"==typeof t&&i[t]()})}var s=i(1),o=i(2),a=function(t,e){this.$element=s(t),this.options=s.extend({},a.DEFAULTS,e),this.transitioning=null,this.options.parent&&(this.$parent=s(this.options.parent)),this.options.toggle&&this.toggle()};a.DEFAULTS={toggle:!0},a.prototype.open=function(){if(!this.transitioning&&!this.$element.hasClass("am-in")){var t=s.Event("open.collapse.amui");if(this.$element.trigger(t),!t.isDefaultPrevented()){var e=this.$parent&&this.$parent.find("> .am-panel > .am-in");if(e&&e.length){var i=e.data("amui.collapse");if(i&&i.transitioning)return;n.call(e,"close"),i||e.data("amui.collapse",null)}this.$element.removeClass("am-collapse").addClass("am-collapsing").height(0),this.transitioning=1;var a=function(){this.$element.removeClass("am-collapsing").addClass("am-collapse am-in").height("").trigger("opened.collapse.amui"),this.transitioning=0};if(!o.support.transition)return a.call(this);var r=this.$element[0].scrollHeight;this.$element.one(o.support.transition.end,s.proxy(a,this)).emulateTransitionEnd(300).css({height:r})}}},a.prototype.close=function(){if(!this.transitioning&&this.$element.hasClass("am-in")){var t=s.Event("close.collapse.amui");if(this.$element.trigger(t),!t.isDefaultPrevented()){this.$element.height(this.$element.height()).redraw(),this.$element.addClass("am-collapsing").removeClass("am-collapse am-in"),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.trigger("closed.collapse.amui").removeClass("am-collapsing").addClass("am-collapse")};return o.support.transition?void this.$element.height(0).one(o.support.transition.end,s.proxy(e,this)).emulateTransitionEnd(300):e.call(this)}}},a.prototype.toggle=function(){this[this.$element.hasClass("am-in")?"close":"open"]()},s.fn.collapse=n,s(document).on("click.collapse.amui.data-api","[data-am-collapse]",function(t){var e,i=s(this),a=o.utils.options(i.attr("data-am-collapse")),r=a.target||t.preventDefault()||(e=i.attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,""),l=s(r),c=l.data("amui.collapse"),u=c?"toggle":a,h=a.parent,d=h&&s(h);c&&c.transitioning||(d&&d.find("[data-am-collapse]").not(i).addClass("am-collapsed"),i[l.hasClass("am-in")?"addClass":"removeClass"]("am-collapsed")),n.call(l,u)}),t.exports=o.collapse=a},function(t,e,i){"use strict";var n=i(1),s=i(2),o=n(document),a=function(t,e){if(this.$element=n(t),this.options=n.extend({},a.DEFAULTS,e),this.format=r.parseFormat(this.options.format),this.$element.data("date",this.options.date),this.language=this.getLocale(this.options.locale),this.theme=this.options.theme,this.$picker=n(r.template).appendTo("body").on({click:n.proxy(this.click,this)}),this.isInput=this.$element.is("input"),this.component=!!this.$element.is(".am-datepicker-date")&&this.$element.find(".am-datepicker-add-on"),this.isInput?this.$element.on({"click.datepicker.amui":n.proxy(this.open,this),"keyup.datepicker.amui":n.proxy(this.update,this)}):this.component?this.component.on("click.datepicker.amui",n.proxy(this.open,this)):this.$element.on("click.datepicker.amui",n.proxy(this.open,this)),this.minViewMode=this.options.minViewMode,"string"==typeof this.minViewMode)switch(this.minViewMode){case"months":this.minViewMode=1;break;case"years":this.minViewMode=2;break;default:this.minViewMode=0}if(this.viewMode=this.options.viewMode,"string"==typeof this.viewMode)switch(this.viewMode){case"months":this.viewMode=1;break;case"years":this.viewMode=2;break;default:this.viewMode=0}this.startViewMode=this.viewMode,this.weekStart=(this.options.weekStart||a.locales[this.language].weekStart||0)%7,this.weekEnd=(this.weekStart+6)%7,this.onRender=this.options.onRender,this.setTheme(),this.fillDow(),this.fillMonths(),this.update(),this.showMode()};a.DEFAULTS={locale:"zh_CN",format:"yyyy-mm-dd",weekStart:void 0,viewMode:0,minViewMode:0,date:"",theme:"",autoClose:1,onRender:function(t){return""}},a.prototype.open=function(t){this.$picker.show(),this.height=this.component?this.component.outerHeight():this.$element.outerHeight(),this.place(),n(window).on("resize.datepicker.amui",n.proxy(this.place,this)),t&&(t.stopPropagation(),t.preventDefault());var e=this;o.on("mousedown.datapicker.amui touchstart.datepicker.amui",function(t){0===n(t.target).closest(".am-datepicker").length&&e.close()}),this.$element.trigger({type:"open.datepicker.amui",date:this.date})},a.prototype.close=function(){this.$picker.hide(),n(window).off("resize.datepicker.amui",this.place),this.viewMode=this.startViewMode,this.showMode(),this.isInput||n(document).off("mousedown.datapicker.amui touchstart.datepicker.amui",this.close),this.$element.trigger({type:"close.datepicker.amui",date:this.date})},a.prototype.set=function(){var t,e=r.formatDate(this.date,this.format);this.isInput?t=this.$element.attr("value",e):(this.component&&(t=this.$element.find("input").attr("value",e)),this.$element.data("date",e)),t&&t.trigger("change")},a.prototype.setValue=function(t){"string"==typeof t?this.date=r.parseDate(t,this.format):this.date=new Date(t),this.set(),this.viewDate=new Date(this.date.getFullYear(),this.date.getMonth(),1,0,0,0,0),this.fill()},a.prototype.place=function(){var t=this.component?this.component.offset():this.$element.offset(),e=this.component?this.component.width():this.$element.width(),i=t.top+this.height,n=t.left,s=o.width()-t.left-e,a=this.isOutView();if(this.$picker.removeClass("am-datepicker-right"),this.$picker.removeClass("am-datepicker-up"),o.width()>640){if(a.outRight)return this.$picker.addClass("am-datepicker-right"),void this.$picker.css({top:i,left:"auto",right:s});a.outBottom&&(this.$picker.addClass("am-datepicker-up"),i=t.top-this.$picker.outerHeight(!0))}else n=0;this.$picker.css({top:i,left:n})},a.prototype.update=function(t){this.date=r.parseDate("string"==typeof t?t:this.isInput?this.$element.prop("value"):this.$element.data("date"),this.format),this.viewDate=new Date(this.date.getFullYear(),this.date.getMonth(),1,0,0,0,0),this.fill()},a.prototype.fillDow=function(){for(var t=this.weekStart,e="";t'+a.locales[this.language].daysMin[t++%7]+"";e+="",this.$picker.find(".am-datepicker-days thead").append(e)},a.prototype.fillMonths=function(){for(var t="",e=0;e<12;)t+=''+a.locales[this.language].monthsShort[e++]+"";this.$picker.find(".am-datepicker-months td").append(t)},a.prototype.fill=function(){var t=new Date(this.viewDate),e=t.getFullYear(),i=t.getMonth(),n=this.date.valueOf(),s=new Date(e,i-1,28,0,0,0,0),o=r.getDaysInMonth(s.getFullYear(),s.getMonth()),l=this.$picker.find(".am-datepicker-days .am-datepicker-select");"zh_CN"===this.language?l.text(e+a.locales[this.language].year[0]+" "+a.locales[this.language].months[i]):l.text(a.locales[this.language].months[i]+" "+e),s.setDate(o),s.setDate(o-(s.getDay()-this.weekStart+7)%7);var c=new Date(s);c.setDate(c.getDate()+42),c=c.valueOf();for(var u,h,d,p=[];s.valueOf()"),u=this.onRender(s,0),h=s.getFullYear(),d=s.getMonth(),di&&h===e||h>e)&&(u+=" am-datepicker-new"),s.valueOf()===n&&(u+=" am-active"),p.push(''+s.getDate()+""),s.getDay()===this.weekEnd&&p.push(""),s.setDate(s.getDate()+1);this.$picker.find(".am-datepicker-days tbody").empty().append(p.join(""));var m=this.date.getFullYear(),f=this.$picker.find(".am-datepicker-months").find(".am-datepicker-select").text(e);f=f.end().find("span").removeClass("am-active").removeClass("am-disabled");for(var v=0;v<12;)this.onRender(t.setFullYear(e,v),1)&&f.eq(v).addClass("am-disabled"),v++;m===e&&f.eq(this.date.getMonth()).removeClass("am-disabled").addClass("am-active"),p="",e=10*parseInt(e/10,10);var g,y=this.$picker.find(".am-datepicker-years").find(".am-datepicker-select").text(e+"-"+(e+9)).end().find("td"),w=new Date(this.viewDate);e-=1;for(var b=-1;b<11;b++)g=this.onRender(w.setFullYear(e),2),p+=''+e+"",e+=1;y.html(p)},a.prototype.click=function(t){t.stopPropagation(),t.preventDefault();var e,i,s=this.$picker.find(".am-datepicker-days").find(".am-active"),o=this.$picker.find(".am-datepicker-months"),a=o.find(".am-active").index(),l=n(t.target).closest("span, td, th");if(1===l.length)switch(l[0].nodeName.toLowerCase()){case"th":switch(l[0].className){case"am-datepicker-switch":this.showMode(1);break;case"am-datepicker-prev":case"am-datepicker-next":this.viewDate["set"+r.modes[this.viewMode].navFnc].call(this.viewDate,this.viewDate["get"+r.modes[this.viewMode].navFnc].call(this.viewDate)+r.modes[this.viewMode].navStep*("am-datepicker-prev"===l[0].className?-1:1)),this.fill(),this.set()}break;case"span":if(l.is(".am-disabled"))return;l.is(".am-datepicker-month")?(e=l.parent().find("span").index(l),l.is(".am-active")?this.viewDate.setMonth(e,s.text()):this.viewDate.setMonth(e)):(i=parseInt(l.text(),10)||0,l.is(".am-active")?this.viewDate.setFullYear(i,a,s.text()):this.viewDate.setFullYear(i)),0!==this.viewMode&&(this.date=new Date(this.viewDate),this.$element.trigger({type:"changeDate.datepicker.amui",date:this.date,viewMode:r.modes[this.viewMode].clsName})),this.showMode(-1),this.fill(),this.set();break;case"td":if(l.is(".am-datepicker-day")&&!l.is(".am-disabled")){var c=parseInt(l.text(),10)||1;e=this.viewDate.getMonth(),l.is(".am-datepicker-old")?e-=1:l.is(".am-datepicker-new")&&(e+=1),i=this.viewDate.getFullYear(),this.date=new Date(i,e,c,0,0,0,0),this.viewDate=new Date(i,e,Math.min(28,c),0,0,0,0),this.fill(),this.set(),this.$element.trigger({type:"changeDate.datepicker.amui",date:this.date,viewMode:r.modes[this.viewMode].clsName}),this.options.autoClose&&this.close()}}},a.prototype.mousedown=function(t){t.stopPropagation(),t.preventDefault()},a.prototype.showMode=function(t){t&&(this.viewMode=Math.max(this.minViewMode,Math.min(2,this.viewMode+t))),this.$picker.find(">div").hide().filter(".am-datepicker-"+r.modes[this.viewMode].clsName).show()},a.prototype.isOutView=function(){var t=this.component?this.component.offset():this.$element.offset(),e={outRight:!1,outBottom:!1},i=this.$picker,n=t.left+i.outerWidth(!0),s=t.top+i.outerHeight(!0)+this.$element.innerHeight();return n>o.width()&&(e.outRight=!0),s>o.height()&&(e.outBottom=!0),e},a.prototype.getLocale=function(t){return t||(t=navigator.language&&navigator.language.split("-"),t[1]=t[1].toUpperCase(),t=t.join("_")),a.locales[t]||(t="en_US"),t},a.prototype.setTheme=function(){this.theme&&this.$picker.addClass("am-datepicker-"+this.theme)},a.locales={en_US:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekStart:0},zh_CN:{days:["\u661f\u671f\u65e5","\u661f\u671f\u4e00","\u661f\u671f\u4e8c","\u661f\u671f\u4e09","\u661f\u671f\u56db","\u661f\u671f\u4e94","\u661f\u671f\u516d"],daysShort:["\u5468\u65e5","\u5468\u4e00","\u5468\u4e8c","\u5468\u4e09","\u5468\u56db","\u5468\u4e94","\u5468\u516d"],daysMin:["\u65e5","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d"],months:["\u4e00\u6708","\u4e8c\u6708","\u4e09\u6708","\u56db\u6708","\u4e94\u6708","\u516d\u6708","\u4e03\u6708","\u516b\u6708","\u4e5d\u6708","\u5341\u6708","\u5341\u4e00\u6708","\u5341\u4e8c\u6708"],monthsShort:["\u4e00\u6708","\u4e8c\u6708","\u4e09\u6708","\u56db\u6708","\u4e94\u6708","\u516d\u6708","\u4e03\u6708","\u516b\u6708","\u4e5d\u6708","\u5341\u6708","\u5341\u4e00\u6708","\u5341\u4e8c\u6708"],weekStart:1,year:["\u5e74"]}};var r={modes:[{clsName:"days",navFnc:"Month",navStep:1},{clsName:"months",navFnc:"FullYear",navStep:1},{clsName:"years",navFnc:"FullYear",navStep:10}],isLeapYear:function(t){return t%4===0&&t%100!==0||t%400===0},getDaysInMonth:function(t,e){return[31,r.isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]},parseFormat:function(t){var e=t.match(/[.\/\-\s].*?/),i=t.split(/\W+/);if(!e||!i||0===i.length)throw new Error("Invalid date format.");return{separator:e,parts:i}},parseDate:function(t,e){var i,n=t.split(e.separator);if(t=new Date,t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0),n.length===e.parts.length){for(var s=t.getFullYear(),o=t.getDate(),a=t.getMonth(),r=0,l=e.parts.length;r
    ',contTemplate:''};r.template='
    '+r.headTemplate+'
    '+r.headTemplate+r.contTemplate+'
    '+r.headTemplate+r.contTemplate+"
    ",s.plugin("datepicker",a),s.ready(function(t){n("[data-am-datepicker]").datepicker()}),t.exports=s.datepicker=a},function(t,e,i){"use strict";var n=i(1),s=i(2),o=n(document),a=s.support.transition,r=function(){this.id=s.utils.generateGUID("am-dimmer"),this.$element=n(r.DEFAULTS.tpl,{id:this.id}),this.inited=!1,this.scrollbarWidth=0,this.$used=n([])};r.DEFAULTS={tpl:'
    '},r.prototype.init=function(){return this.inited||(n(document.body).append(this.$element),this.inited=!0,o.trigger("init.dimmer.amui"),this.$element.on("touchmove.dimmer.amui",function(t){t.preventDefault()})),this},r.prototype.open=function(t){this.inited||this.init();var e=this.$element;return t&&(this.$used=this.$used.add(n(t))),this.checkScrollbar().setScrollbar(),e.show().trigger("open.dimmer.amui"),a&&e.off(a.end),setTimeout(function(){e.addClass("am-active")},0),this},r.prototype.close=function(t,e){function i(){s.hide(),this.resetScrollbar()}if(this.$used=this.$used.not(n(t)),!e&&this.$used.length)return this;var s=this.$element;return s.removeClass("am-active").trigger("close.dimmer.amui"),i.call(this),this},r.prototype.checkScrollbar=function(){return this.scrollbarWidth=s.utils.measureScrollbar(),this},r.prototype.setScrollbar=function(){var t=n(document.body),e=parseInt(t.css("padding-right")||0,10);return this.scrollbarWidth&&t.css("padding-right",e+this.scrollbarWidth),t.addClass("am-dimmer-active"),this},r.prototype.resetScrollbar=function(){return n(document.body).css("padding-right","").removeClass("am-dimmer-active"),this},t.exports=s.dimmer=new r},function(t,e,i){"use strict";var n=i(1),s=i(2),o=s.support.animation,a=function(t,e){this.options=n.extend({},a.DEFAULTS,e),e=this.options,this.$element=n(t),this.$toggle=this.$element.find(e.selector.toggle),this.$dropdown=this.$element.find(e.selector.dropdown),this.$boundary=e.boundary===window?n(window):this.$element.closest(e.boundary),this.$justify=e.justify&&n(e.justify).length&&n(e.justify)||void 0,!this.$boundary.length&&(this.$boundary=n(window)),this.active=!!this.$element.hasClass("am-active"),this.animating=null,this.events()};a.DEFAULTS={animation:"am-animation-slide-top-fixed",boundary:window,justify:void 0,selector:{dropdown:".am-dropdown-content",toggle:".am-dropdown-toggle"},trigger:"click"},a.prototype.toggle=function(){this.clear(),this.animating||this[this.active?"close":"open"]()},a.prototype.open=function(t){var e=this.$toggle,i=this.$element,s=this.$dropdown;if(!e.is(".am-disabled, :disabled")&&!this.active){i.trigger("open.dropdown.amui").addClass("am-active"),e.trigger("focus"),this.checkDimensions(t);var a=n.proxy(function(){i.trigger("opened.dropdown.amui"),this.active=!0,this.animating=0},this);o?(this.animating=1,s.addClass(this.options.animation).on(o.end+".open.dropdown.amui",n.proxy(function(){a(),s.removeClass(this.options.animation)},this))):a()}},a.prototype.close=function(){if(this.active){var t="am-dropdown-animation",e=this.$element,i=this.$dropdown;e.trigger("close.dropdown.amui");var s=n.proxy(function(){e.removeClass("am-active").trigger("closed.dropdown.amui"),this.active=!1,this.animating=0,this.$toggle.blur()},this);o?(i.removeClass(this.options.animation),i.addClass(t),this.animating=1,i.one(o.end+".close.dropdown.amui",function(){i.removeClass(t),s()})):s()}},a.prototype.enable=function(){this.$toggle.prop("disabled",!1)},a.prototype.disable=function(){this.$toggle.prop("disabled",!0)},a.prototype.checkDimensions=function(t){if(this.$dropdown.length){var e=this.$dropdown;t&&t.offset&&e.offset(t.offset);var i=e.offset(),s=e.outerWidth(),o=this.$boundary.width(),a=n.isWindow(this.boundary)&&this.$boundary.offset()?this.$boundary.offset().left:0;this.$justify&&e.css({"min-width":this.$justify.css("width")}),s+(i.left-a)>o&&this.$element.addClass("am-dropdown-flip")}},a.prototype.clear=function(){n("[data-am-dropdown]").not(this.$element).each(function(){var t=n(this).data("amui.dropdown");t&&t.close()})},a.prototype.events=function(){var t="dropdown.amui",e=this.$toggle;e.on("click."+t,n.proxy(function(t){t.preventDefault(),this.toggle()},this)),n(document).on("keydown.dropdown.amui",n.proxy(function(t){27===t.keyCode&&this.active&&this.close()},this)).on("click.outer.dropdown.amui",n.proxy(function(t){!this.active||this.$element[0]!==t.target&&this.$element.find(t.target).length||this.close()},this))},s.plugin("dropdown",a),s.ready(function(t){n("[data-am-dropdown]",t).dropdown()}),n(document).on("click.dropdown.amui.data-api",".am-dropdown form",function(t){ +t.stopPropagation()}),t.exports=s.dropdown=a},function(t,e,i){(function(e){var n=i(1),s=i(2),o=!0;n.flexslider=function(t,i){var s=n(t);s.vars=n.extend({},n.flexslider.defaults,i);var a,r=s.vars.namespace,l=window.navigator&&window.navigator.msPointerEnabled&&window.MSGesture,c=("ontouchstart"in window||l||window.DocumentTouch&&document instanceof DocumentTouch)&&s.vars.touch,u="click touchend MSPointerUp keyup",h="",d="vertical"===s.vars.direction,p=s.vars.reverse,m=s.vars.itemWidth>0,f="fade"===s.vars.animation,v=""!==s.vars.asNavFor,g={};n.data(t,"flexslider",s),g={init:function(){s.animating=!1,s.currentSlide=parseInt(s.vars.startAt?s.vars.startAt:0,10),isNaN(s.currentSlide)&&(s.currentSlide=0),s.animatingTo=s.currentSlide,s.atEnd=0===s.currentSlide||s.currentSlide===s.last,s.containerSelector=s.vars.selector.substr(0,s.vars.selector.search(" ")),s.slides=n(s.vars.selector,s),s.container=n(s.containerSelector,s),s.count=s.slides.length,s.syncExists=n(s.vars.sync).length>0,"slide"===s.vars.animation&&(s.vars.animation="swing"),s.prop=d?"top":"marginLeft",s.args={},s.manualPause=!1,s.stopped=!1,s.started=!1,s.startTimeout=null,s.transitions=!s.vars.video&&!f&&s.vars.useCSS&&function(){var t=document.createElement("div"),e=["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"];for(var i in e)if(void 0!==t.style[e[i]])return s.pfx=e[i].replace("Perspective","").toLowerCase(),s.prop="-"+s.pfx+"-transform",!0;return!1}(),s.ensureAnimationEnd="",""!==s.vars.controlsContainer&&(s.controlsContainer=n(s.vars.controlsContainer).length>0&&n(s.vars.controlsContainer)),""!==s.vars.manualControls&&(s.manualControls=n(s.vars.manualControls).length>0&&n(s.vars.manualControls)),""!==s.vars.customDirectionNav&&(s.customDirectionNav=2===n(s.vars.customDirectionNav).length&&n(s.vars.customDirectionNav)),s.vars.randomize&&(s.slides.sort(function(){return Math.round(Math.random())-.5}),s.container.empty().append(s.slides)),s.doMath(),s.setup("init"),s.vars.controlNav&&g.controlNav.setup(),s.vars.directionNav&&g.directionNav.setup(),s.vars.keyboard&&(1===n(s.containerSelector).length||s.vars.multipleKeyboard)&&n(document).bind("keyup",function(t){var e=t.keyCode;if(!s.animating&&(39===e||37===e)){var i=39===e?s.getTarget("next"):37===e&&s.getTarget("prev");s.flexAnimate(i,s.vars.pauseOnAction)}}),s.vars.mousewheel&&s.bind("mousewheel",function(t,e,i,n){t.preventDefault();var o=e<0?s.getTarget("next"):s.getTarget("prev");s.flexAnimate(o,s.vars.pauseOnAction)}),s.vars.pausePlay&&g.pausePlay.setup(),s.vars.slideshow&&s.vars.pauseInvisible&&g.pauseInvisible.init(),s.vars.slideshow&&(s.vars.pauseOnHover&&s.hover(function(){s.manualPlay||s.manualPause||s.pause()},function(){s.manualPause||s.manualPlay||s.stopped||s.play()}),s.vars.pauseInvisible&&g.pauseInvisible.isHidden()||(s.vars.initDelay>0?s.startTimeout=setTimeout(s.play,s.vars.initDelay):s.play())),v&&g.asNav.setup(),c&&s.vars.touch&&g.touch(),(!f||f&&s.vars.smoothHeight)&&n(window).bind("resize orientationchange focus",g.resize),s.find("img").attr("draggable","false"),setTimeout(function(){s.vars.start(s)},200)},asNav:{setup:function(){s.asNav=!0,s.animatingTo=Math.floor(s.currentSlide/s.move),s.currentItem=s.currentSlide,s.slides.removeClass(r+"active-slide").eq(s.currentItem).addClass(r+"active-slide"),l?(t._slider=s,s.slides.each(function(){var t=this;t._gesture=new MSGesture,t._gesture.target=t,t.addEventListener("MSPointerDown",function(t){t.preventDefault(),t.currentTarget._gesture&&t.currentTarget._gesture.addPointer(t.pointerId)},!1),t.addEventListener("MSGestureTap",function(t){t.preventDefault();var e=n(this),i=e.index();n(s.vars.asNavFor).data("flexslider").animating||e.hasClass("active")||(s.direction=s.currentItem'),s.pagingCount>1)for(var a=0;a":''+o+"","thumbnails"===s.vars.controlNav&&!0===s.vars.thumbCaptions){var c=e.attr("data-thumbcaption");""!==c&&void 0!==c&&(t+=''+c+"")}s.controlNavScaffold.append("
  • "+t+"
  • "),o++}s.controlsContainer?n(s.controlsContainer).append(s.controlNavScaffold):s.append(s.controlNavScaffold),g.controlNav.set(),g.controlNav.active(),s.controlNavScaffold.delegate("a, img",u,function(t){if(t.preventDefault(),""===h||h===t.type){var e=n(this),i=s.controlNav.index(e);e.hasClass(r+"active")||(s.direction=i>s.currentSlide?"next":"prev",s.flexAnimate(i,s.vars.pauseOnAction))}""===h&&(h=t.type),g.setToClearWatchedEvent()})},setupManual:function(){s.controlNav=s.manualControls,g.controlNav.active(),s.controlNav.bind(u,function(t){if(t.preventDefault(),""===h||h===t.type){var e=n(this),i=s.controlNav.index(e);e.hasClass(r+"active")||(i>s.currentSlide?s.direction="next":s.direction="prev",s.flexAnimate(i,s.vars.pauseOnAction))}""===h&&(h=t.type),g.setToClearWatchedEvent()})},set:function(){var t="thumbnails"===s.vars.controlNav?"img":"a";s.controlNav=n("."+r+"control-nav li "+t,s.controlsContainer?s.controlsContainer:s)},active:function(){s.controlNav.removeClass(r+"active").eq(s.animatingTo).addClass(r+"active")},update:function(t,e){s.pagingCount>1&&"add"===t?s.controlNavScaffold.append(n('
  • '+s.count+"
  • ")):1===s.pagingCount?s.controlNavScaffold.find("li").remove():s.controlNav.eq(e).closest("li").remove(),g.controlNav.set(),s.pagingCount>1&&s.pagingCount!==s.controlNav.length?s.update(e,t):g.controlNav.active()}},directionNav:{setup:function(){var t=n('");s.customDirectionNav?s.directionNav=s.customDirectionNav:s.controlsContainer?(n(s.controlsContainer).append(t),s.directionNav=n("."+r+"direction-nav li a",s.controlsContainer)):(s.append(t),s.directionNav=n("."+r+"direction-nav li a",s)),g.directionNav.update(),s.directionNav.bind(u,function(t){t.preventDefault();var e;""!==h&&h!==t.type||(e=n(this).hasClass(r+"next")?s.getTarget("next"):s.getTarget("prev"),s.flexAnimate(e,s.vars.pauseOnAction)),""===h&&(h=t.type),g.setToClearWatchedEvent()})},update:function(){var t=r+"disabled";1===s.pagingCount?s.directionNav.addClass(t).attr("tabindex","-1"):s.vars.animationLoop?s.directionNav.removeClass(t).removeAttr("tabindex"):0===s.animatingTo?s.directionNav.removeClass(t).filter("."+r+"prev").addClass(t).attr("tabindex","-1"):s.animatingTo===s.last?s.directionNav.removeClass(t).filter("."+r+"next").addClass(t).attr("tabindex","-1"):s.directionNav.removeClass(t).removeAttr("tabindex")}},pausePlay:{setup:function(){var t=n('
    ');s.controlsContainer?(s.controlsContainer.append(t),s.pausePlay=n("."+r+"pauseplay a",s.controlsContainer)):(s.append(t),s.pausePlay=n("."+r+"pauseplay a",s)),g.pausePlay.update(s.vars.slideshow?r+"pause":r+"play"),s.pausePlay.bind(u,function(t){t.preventDefault(),""!==h&&h!==t.type||(n(this).hasClass(r+"pause")?(s.manualPause=!0,s.manualPlay=!1,s.pause()):(s.manualPause=!1,s.manualPlay=!0,s.play())),""===h&&(h=t.type),g.setToClearWatchedEvent()})},update:function(t){"play"===t?s.pausePlay.removeClass(r+"pause").addClass(r+"play").html(s.vars.playText):s.pausePlay.removeClass(r+"play").addClass(r+"pause").html(s.vars.pauseText)}},touch:function(){function i(e){e.stopPropagation(),s.animating?e.preventDefault():(s.pause(),t._gesture.addPointer(e.pointerId),C=0,u=d?s.h:s.w,v=Number(new Date),c=m&&p&&s.animatingTo===s.last?0:m&&p?s.limit-(s.itemW+s.vars.itemMargin)*s.move*s.animatingTo:m&&s.currentSlide===s.last?s.limit:m?(s.itemW+s.vars.itemMargin)*s.move*s.currentSlide:p?(s.last-s.currentSlide+s.cloneOffset)*u:(s.currentSlide+s.cloneOffset)*u)}function n(i){i.stopPropagation();var n=i.target._slider;if(n){var s=-i.translationX,o=-i.translationY;return C+=d?o:s,h=C,b=d?Math.abs(C)500)&&(i.preventDefault(),!f&&n.transitions&&(n.vars.animationLoop||(h=C/(0===n.currentSlide&&C<0||n.currentSlide===n.last&&C>0?Math.abs(C)/u+2:1)),n.setProps(c+h,"setTouch"))))}}function o(t){t.stopPropagation();var e=t.target._slider;if(e){if(e.animatingTo===e.currentSlide&&!b&&null!==h){var i=p?-h:h,n=i>0?e.getTarget("next"):e.getTarget("prev");e.canAdvance(n)&&(Number(new Date)-v<550&&Math.abs(i)>50||Math.abs(i)>u/2)?e.flexAnimate(n,e.vars.pauseOnAction):f||e.flexAnimate(e.currentSlide,e.vars.pauseOnAction,!0)}a=null,r=null,h=null,c=null,C=0}}var a,r,c,u,h,v,g,y,w,b=!1,T=0,x=0,C=0;l?(t.style.msTouchAction="none",t._gesture=new MSGesture,t._gesture.target=t,t.addEventListener("MSPointerDown",i,!1),t._slider=s,t.addEventListener("MSGestureChange",n,!1),t.addEventListener("MSGestureEnd",o,!1)):(g=function(e){s.animating?e.preventDefault():(window.navigator.msPointerEnabled||1===e.touches.length)&&(s.pause(),u=d?s.h:s.w,v=Number(new Date),T=e.touches[0].pageX,x=e.touches[0].pageY,c=m&&p&&s.animatingTo===s.last?0:m&&p?s.limit-(s.itemW+s.vars.itemMargin)*s.move*s.animatingTo:m&&s.currentSlide===s.last?s.limit:m?(s.itemW+s.vars.itemMargin)*s.move*s.currentSlide:p?(s.last-s.currentSlide+s.cloneOffset)*u:(s.currentSlide+s.cloneOffset)*u,a=d?x:T,r=d?T:x,t.addEventListener("touchmove",y,!1),t.addEventListener("touchend",w,!1))},y=function(t){T=t.touches[0].pageX,x=t.touches[0].pageY,h=d?a-x:a-T,b=d?Math.abs(h)e)&&(t.preventDefault(),!f&&s.transitions&&(s.vars.animationLoop||(h/=0===s.currentSlide&&h<0||s.currentSlide===s.last&&h>0?Math.abs(h)/u+2:1),s.setProps(c+h,"setTouch")))},w=function(e){if(t.removeEventListener("touchmove",y,!1),s.animatingTo===s.currentSlide&&!b&&null!==h){var i=p?-h:h,n=i>0?s.getTarget("next"):s.getTarget("prev");s.canAdvance(n)&&(Number(new Date)-v<550&&Math.abs(i)>50||Math.abs(i)>u/2)?s.flexAnimate(n,s.vars.pauseOnAction):f||s.flexAnimate(s.currentSlide,s.vars.pauseOnAction,!0)}t.removeEventListener("touchend",w,!1),a=null,r=null,h=null,c=null},t.addEventListener("touchstart",g,!1))},resize:function(){!s.animating&&s.is(":visible")&&(m||s.doMath(),f?g.smoothHeight():m?(s.slides.width(s.computedW),s.update(s.pagingCount),s.setProps()):d?(s.viewport.height(s.h),s.setProps(s.h,"setTotal")):(s.vars.smoothHeight&&g.smoothHeight(),s.newSlides.width(s.computedW),s.setProps(s.computedW,"setTotal")))},smoothHeight:function(t){if(!d||f){var e=f?s:s.viewport;t?e.animate({height:s.slides.eq(s.animatingTo).innerHeight()},t):e.innerHeight(s.slides.eq(s.animatingTo).innerHeight())}},sync:function(t){var e=n(s.vars.sync).data("flexslider"),i=s.animatingTo;switch(t){case"animate":e.flexAnimate(i,s.vars.pauseOnAction,!1,!0);break;case"play":e.playing||e.asNav||e.play();break;case"pause":e.pause()}},uniqueID:function(t){return t.filter("[id]").add(t.find("[id]")).each(function(){var t=n(this);t.attr("id",t.attr("id")+"_clone")}),t},pauseInvisible:{visProp:null,init:function(){var t=g.pauseInvisible.getHiddenProp();if(t){var e=t.replace(/[H|h]idden/,"")+"visibilitychange";document.addEventListener(e,function(){g.pauseInvisible.isHidden()?s.startTimeout?clearTimeout(s.startTimeout):s.pause():s.started?s.play():s.vars.initDelay>0?setTimeout(s.play,s.vars.initDelay):s.play()})}},isHidden:function(){var t=g.pauseInvisible.getHiddenProp();return!!t&&document[t]},getHiddenProp:function(){var t=["webkit","moz","ms","o"];if("hidden"in document)return"hidden";for(var e=0;es.currentSlide?"next":"prev"),v&&1===s.pagingCount&&(s.direction=s.currentItems.limit&&1!==s.visible?s.limit:y):h=0===s.currentSlide&&t===s.count-1&&s.vars.animationLoop&&"next"!==s.direction?p?(s.count+s.cloneOffset)*w:0:s.currentSlide===s.last&&0===t&&s.vars.animationLoop&&"prev"!==s.direction?p?0:(s.count+1)*w:p?(s.count-1-t+s.cloneOffset)*w:(t+s.cloneOffset)*w,s.setProps(h,"",s.vars.animationSpeed),s.transitions?(s.vars.animationLoop&&s.atEnd||(s.animating=!1,s.currentSlide=s.animatingTo),s.container.unbind("webkitTransitionEnd transitionend"),s.container.bind("webkitTransitionEnd transitionend",function(){clearTimeout(s.ensureAnimationEnd),s.wrapup(w)}),clearTimeout(s.ensureAnimationEnd),s.ensureAnimationEnd=setTimeout(function(){s.wrapup(w)},s.vars.animationSpeed+100)):s.container.animate(s.args,s.vars.animationSpeed,s.vars.easing,function(){s.wrapup(w)})}s.vars.smoothHeight&&g.smoothHeight(s.vars.animationSpeed)}},s.wrapup=function(t){f||m||(0===s.currentSlide&&s.animatingTo===s.last&&s.vars.animationLoop?s.setProps(t,"jumpEnd"):s.currentSlide===s.last&&0===s.animatingTo&&s.vars.animationLoop&&s.setProps(t,"jumpStart")),s.animating=!1,s.currentSlide=s.animatingTo,s.vars.after(s)},s.animateSlides=function(){!s.animating&&o&&s.flexAnimate(s.getTarget("next"))},s.pause=function(){clearInterval(s.animatedSlides),s.animatedSlides=null,s.playing=!1,s.vars.pausePlay&&g.pausePlay.update("play"),s.syncExists&&g.sync("pause")},s.play=function(){s.playing&&clearInterval(s.animatedSlides),s.animatedSlides=s.animatedSlides||setInterval(s.animateSlides,s.vars.slideshowSpeed),s.started=s.playing=!0,s.vars.pausePlay&&g.pausePlay.update("pause"),s.syncExists&&g.sync("play")},s.stop=function(){s.pause(),s.stopped=!0},s.canAdvance=function(t,e){var i=v?s.pagingCount-1:s.last;return!!e||(!(!v||s.currentItem!==s.count-1||0!==t||"prev"!==s.direction)||(!v||0!==s.currentItem||t!==s.pagingCount-1||"next"===s.direction)&&(!(t===s.currentSlide&&!v)&&(!!s.vars.animationLoop||(!s.atEnd||0!==s.currentSlide||t!==i||"next"===s.direction)&&(!s.atEnd||s.currentSlide!==i||0!==t||"next"!==s.direction))))},s.getTarget=function(t){return s.direction=t,"next"===t?s.currentSlide===s.last?0:s.currentSlide+1:0===s.currentSlide?s.last:s.currentSlide-1},s.setProps=function(t,e,i){var n=function(){var i=t?t:(s.itemW+s.vars.itemMargin)*s.move*s.animatingTo,n=function(){if(m)return"setTouch"===e?t:p&&s.animatingTo===s.last?0:p?s.limit-(s.itemW+s.vars.itemMargin)*s.move*s.animatingTo:s.animatingTo===s.last?s.limit:i;switch(e){case"setTotal":return p?(s.count-1-s.currentSlide+s.cloneOffset)*t:(s.currentSlide+s.cloneOffset)*t;case"setTouch":return p?t:t;case"jumpEnd":return p?t:s.count*t;case"jumpStart":return p?s.count*t:t;default:return t}}();return n*-1+"px"}();s.transitions&&(n=d?"translate3d(0,"+n+",0)":"translate3d("+n+",0,0)",i=void 0!==i?i/1e3+"s":"0s",s.container.css("-"+s.pfx+"-transition-duration",i),s.container.css("transition-duration",i)),s.args[s.prop]=n,(s.transitions||void 0===i)&&s.container.css(s.args),s.container.css("transform",n)},s.setup=function(t){if(f)s.slides.css({width:"100%","float":"left",marginRight:"-100%",position:"relative"}),"init"===t&&(c?s.slides.css({opacity:0,display:"block",webkitTransition:"opacity "+s.vars.animationSpeed/1e3+"s ease",zIndex:1}).eq(s.currentSlide).css({opacity:1,zIndex:2}):0==s.vars.fadeFirstSlide?s.slides.css({opacity:0,display:"block",zIndex:1}).eq(s.currentSlide).css({zIndex:2}).css({opacity:1}):s.slides.css({opacity:0,display:"block",zIndex:1}).eq(s.currentSlide).css({zIndex:2}).animate({opacity:1},s.vars.animationSpeed,s.vars.easing)),s.vars.smoothHeight&&g.smoothHeight();else{var e,i;"init"===t&&(s.viewport=n('
    ').css({overflow:"hidden",position:"relative"}).appendTo(s).append(s.container),s.cloneCount=0,s.cloneOffset=0,p&&(i=n.makeArray(s.slides).reverse(),s.slides=n(i),s.container.empty().append(s.slides))),s.vars.animationLoop&&!m&&(s.cloneCount=2,s.cloneOffset=1,"init"!==t&&s.container.find(".clone").remove(),s.container.append(g.uniqueID(s.slides.first().clone().addClass("clone")).attr("aria-hidden","true")).prepend(g.uniqueID(s.slides.last().clone().addClass("clone")).attr("aria-hidden","true"))),s.newSlides=n(s.vars.selector,s),e=p?s.count-1-s.currentSlide+s.cloneOffset:s.currentSlide+s.cloneOffset,d&&!m?(s.container.height(200*(s.count+s.cloneCount)+"%").css("position","absolute").width("100%"),setTimeout(function(){s.newSlides.css({display:"block"}),s.doMath(),s.viewport.height(s.h),s.setProps(e*s.h,"init")},"init"===t?100:0)):(s.container.width(200*(s.count+s.cloneCount)+"%"),s.setProps(e*s.computedW,"init"),setTimeout(function(){s.doMath(),s.newSlides.css({width:s.computedW,marginRight:s.computedM,"float":"left",display:"block"}),s.vars.smoothHeight&&g.smoothHeight()},"init"===t?100:0))}m||s.slides.removeClass(r+"active-slide").eq(s.currentSlide).addClass(r+"active-slide"),s.vars.init(s)},s.doMath=function(){var t=s.slides.first(),e=s.vars.itemMargin,i=s.vars.minItems,n=s.vars.maxItems;s.w=void 0===s.viewport?s.width():s.viewport.width(),s.h=t.height(),s.boxPadding=t.outerWidth()-t.width(),m?(s.itemT=s.vars.itemWidth+e,s.itemM=e,s.minW=i?i*s.itemT:s.w,s.maxW=n?n*s.itemT-e:s.w,s.itemW=s.minW>s.w?(s.w-e*(i-1))/i:s.maxWs.w?s.w:s.vars.itemWidth,s.visible=Math.floor(s.w/s.itemW),s.move=s.vars.move>0&&s.vars.moves.w?s.itemW*(s.count-1)+e*(s.count-1):(s.itemW+e)*s.count-s.w-e):(s.itemW=s.w,s.itemM=e,s.pagingCount=s.count,s.last=s.count-1),s.computedW=s.itemW-s.boxPadding,s.computedM=s.itemM},s.update=function(t,e){s.doMath(),m||(ts.controlNav.length?g.controlNav.update("add"):("remove"===e&&!m||s.pagingCounts.last&&(s.currentSlide-=1,s.animatingTo-=1),g.controlNav.update("remove",s.last))),s.vars.directionNav&&g.directionNav.update()},s.addSlide=function(t,e){var i=n(t);s.count+=1,s.last=s.count-1,d&&p?void 0!==e?s.slides.eq(s.count-e).after(i):s.container.prepend(i):void 0!==e?s.slides.eq(e).before(i):s.container.append(i),s.update(e,"add"),s.slides=n(s.vars.selector+":not(.clone)",s),s.setup(),s.vars.added(s)},s.removeSlide=function(t){var e=isNaN(t)?s.slides.index(n(t)):t;s.count-=1,s.last=s.count-1,isNaN(t)?n(t,s.slides).remove():d&&p?s.slides.eq(s.last).remove():s.slides.eq(t).remove(),s.doMath(),s.update(e,"remove"),s.slides=n(s.vars.selector+":not(.clone)",s),s.setup(),s.vars.removed(s)},g.init()},n(window).blur(function(t){o=!1}).focus(function(t){o=!0}),n.flexslider.defaults={namespace:"am-",selector:".am-slides > li",animation:"slide",easing:"swing",direction:"horizontal",reverse:!1,animationLoop:!0,smoothHeight:!1,startAt:0,slideshow:!0,slideshowSpeed:5e3,animationSpeed:600,initDelay:0,randomize:!1,fadeFirstSlide:!0,thumbCaptions:!1,pauseOnAction:!0,pauseOnHover:!1,pauseInvisible:!0,useCSS:!0,touch:!0,video:!1,controlNav:!0,directionNav:!0,prevText:" ",nextText:" ",keyboard:!0,multipleKeyboard:!1,mousewheel:!1,pausePlay:!1,pauseText:"Pause",playText:"Play",controlsContainer:"",manualControls:"",customDirectionNav:"",sync:"",asNavFor:"",itemWidth:0,itemMargin:0,minItems:1,maxItems:0,move:0,allowOneSlide:!0,start:function(){},before:function(){},after:function(){},end:function(){},added:function(){},removed:function(){},init:function(){}},n.fn.flexslider=function(t){var e=Array.prototype.slice.call(arguments,1);if(void 0===t&&(t={}),"object"==typeof t)return this.each(function(){var e=n(this),i=t.selector?t.selector:".am-slides > li",s=e.find(i);1===s.length&&t.allowOneSlide===!1||0===s.length?(s.fadeIn(400),t.start&&t.start(e)):void 0===e.data("flexslider")&&new n.flexslider(this,t)});var i,s=n(this).data("flexslider");switch(t){case"next":s.flexAnimate(s.getTarget("next"),!0);break;case"prev":case"previous":s.flexAnimate(s.getTarget("prev"),!0);break;default:"number"==typeof t?s.flexAnimate(t,!0):"string"==typeof t&&(i="function"==typeof s[t]?s[t].apply(s,e):s[t])}return void 0===i?this:i},s.ready(function(t){n("[data-am-flexslider]",t).each(function(t,e){var i=n(e),o=s.utils.parseOptions(i.data("amFlexslider"));o.before=function(t){t._pausedTimer&&(window.clearTimeout(t._pausedTimer),t._pausedTimer=null)},o.after=function(t){var e=t.vars.playAfterPaused;!e||isNaN(e)||t.playing||t.manualPause||t.manualPlay||t.stopped||(t._pausedTimer=window.setTimeout(function(){t.play()},e))},i.flexslider(o)})}),t.exports=n.flexslider}).call(e,i(12).setImmediate)},function(t,e,i){(function(t,n){function s(t,e){this._id=t,this._clearFn=e}var o=i(13).nextTick,a=Function.prototype.apply,r=Array.prototype.slice,l={},c=0;e.setTimeout=function(){return new s(a.call(setTimeout,window,arguments),clearTimeout)},e.setInterval=function(){return new s(a.call(setInterval,window,arguments),clearInterval)},e.clearTimeout=e.clearInterval=function(t){t.close()},s.prototype.unref=s.prototype.ref=function(){},s.prototype.close=function(){this._clearFn.call(window,this._id)},e.enroll=function(t,e){clearTimeout(t._idleTimeoutId),t._idleTimeout=e},e.unenroll=function(t){clearTimeout(t._idleTimeoutId),t._idleTimeout=-1},e._unrefActive=e.active=function(t){clearTimeout(t._idleTimeoutId);var e=t._idleTimeout;e>=0&&(t._idleTimeoutId=setTimeout(function(){t._onTimeout&&t._onTimeout()},e))},e.setImmediate="function"==typeof t?t:function(t){var i=c++,n=!(arguments.length<2)&&r.call(arguments,1);return l[i]=!0,o(function(){l[i]&&(n?t.apply(null,n):t.call(null),e.clearImmediate(i))}),i},e.clearImmediate="function"==typeof n?n:function(t){delete l[t]}}).call(e,i(12).setImmediate,i(12).clearImmediate)},function(t,e){function i(t){if(l===setTimeout)return setTimeout(t,0);try{return l(t,0)}catch(e){try{return l.call(null,t,0)}catch(e){return l.call(this,t,0)}}}function n(t){if(c===clearTimeout)return clearTimeout(t);try{return c(t)}catch(e){try{return c.call(null,t)}catch(e){return c.call(this,t)}}}function s(){p&&h&&(p=!1,h.length?d=h.concat(d):m=-1,d.length&&o())}function o(){if(!p){var t=i(s);p=!0;for(var e=d.length;e;){for(h=d,d=[];++m1)for(var n=1;n0&&(a=s?s/2.5*(c/8):0,l=Math.abs(t)+a,r=l/c),{destination:Math.round(a),duration:r}};var s=t("transform");return e.extend(e,{hasTransform:s!==!1,hasPerspective:t("perspective")in i,hasTouch:"ontouchstart"in window,hasPointer:!(!window.PointerEvent&&!window.MSPointerEvent),hasTransition:t("transition")in i}),e.isBadAndroid=function(){var t=window.navigator.appVersion;if(/Android/.test(t)&&!/Chrome\/\d/.test(t)){var e=t.match(/Safari\/(\d+.\d)/);return!(e&&"object"==typeof e&&e.length>=2)||parseFloat(e[1])<535.19}return!1}(),e.extend(e.style={},{transform:s,transitionTimingFunction:t("transitionTimingFunction"),transitionDuration:t("transitionDuration"),transitionDelay:t("transitionDelay"),transformOrigin:t("transformOrigin")}),e.hasClass=function(t,e){var i=new RegExp("(^|\\s)"+e+"(\\s|$)");return i.test(t.className)},e.addClass=function(t,i){if(!e.hasClass(t,i)){var n=t.className.split(" ");n.push(i),t.className=n.join(" ")}},e.removeClass=function(t,i){if(e.hasClass(t,i)){var n=new RegExp("(^|\\s)"+i+"(\\s|$)","g");t.className=t.className.replace(n," ")}},e.offset=function(t){for(var e=-t.offsetLeft,i=-t.offsetTop;t=t.offsetParent;)e-=t.offsetLeft,i-=t.offsetTop;return{left:e,top:i}},e.preventDefaultException=function(t,e){for(var i in e)if(e[i].test(t[i]))return!0;return!1},e.extend(e.eventType={},{touchstart:1,touchmove:1,touchend:1,mousedown:2,mousemove:2,mouseup:2,pointerdown:3,pointermove:3,pointerup:3,MSPointerDown:3,MSPointerMove:3,MSPointerUp:3}),e.extend(e.ease={},{quadratic:{style:"cubic-bezier(0.25, 0.46, 0.45, 0.94)",fn:function(t){return t*(2-t)}},circular:{style:"cubic-bezier(0.1, 0.57, 0.1, 1)",fn:function(t){return Math.sqrt(1- --t*t)}},back:{style:"cubic-bezier(0.175, 0.885, 0.32, 1.275)",fn:function(t){var e=4;return(t-=1)*t*((e+1)*t+e)+1}},bounce:{style:"",fn:function(t){return(t/=1)<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375}},elastic:{style:"",fn:function(t){var e=.22,i=.4;return 0===t?0:1==t?1:i*Math.pow(2,-10*t)*Math.sin((t-e/4)*(2*Math.PI)/e)+1}}}),e.tap=function(t,e){var i=document.createEvent("Event");i.initEvent(e,!0,!0),i.pageX=t.pageX,i.pageY=t.pageY,t.target.dispatchEvent(i)},e.click=function(t){var e,i=t.target;/(SELECT|INPUT|TEXTAREA)/i.test(i.tagName)||(e=document.createEvent(window.MouseEvent?"MouseEvents":"Event"),e.initEvent("click",!0,!0),e.view=t.view||window,e.detail=1,e.screenX=i.screenX||0,e.screenY=i.screenY||0,e.clientX=i.clientX||0,e.clientY=i.clientY||0,e.ctrlKey=!!t.ctrlKey,e.altKey=!!t.altKey,e.shiftKey=!!t.shiftKey,e.metaKey=!!t.metaKey,e.button=0,e.relatedTarget=null,e._constructed=!0,i.dispatchEvent(e))},e}();n.prototype={version:"5.2.0",_init:function(){this._initEvents()},destroy:function(){this._initEvents(!0),clearTimeout(this.resizeTimeout),this.resizeTimeout=null,this._execEvent("destroy")},_transitionEnd:function(t){t.target==this.scroller&&this.isInTransition&&(this._transitionTime(),this.resetPosition(this.options.bounceTime)||(this.isInTransition=!1,this._execEvent("scrollEnd")))},_start:function(t){if(1!=a.eventType[t.type]){var e;if(e=t.which?t.button:t.button<2?0:4==t.button?1:2,0!==e)return}if(this.enabled&&(!this.initiated||a.eventType[t.type]===this.initiated)){!this.options.preventDefault||a.isBadAndroid||a.preventDefaultException(t.target,this.options.preventDefaultException)||t.preventDefault();var i,n=t.touches?t.touches[0]:t;this.initiated=a.eventType[t.type],this.moved=!1,this.distX=0,this.distY=0,this.directionX=0,this.directionY=0,this.directionLocked=0,this.startTime=a.getTime(),this.options.useTransition&&this.isInTransition?(this._transitionTime(),this.isInTransition=!1,i=this.getComputedPosition(),this._translate(Math.round(i.x),Math.round(i.y)),this._execEvent("scrollEnd")):!this.options.useTransition&&this.isAnimating&&(this.isAnimating=!1, +this._execEvent("scrollEnd")),this.startX=this.x,this.startY=this.y,this.absStartX=this.x,this.absStartY=this.y,this.pointX=n.pageX,this.pointY=n.pageY,this._execEvent("beforeScrollStart")}},_move:function(t){if(this.enabled&&a.eventType[t.type]===this.initiated){this.options.preventDefault&&t.preventDefault();var e,i,n,s,o=t.touches?t.touches[0]:t,r=o.pageX-this.pointX,l=o.pageY-this.pointY,c=a.getTime();if(this.pointX=o.pageX,this.pointY=o.pageY,this.distX+=r,this.distY+=l,n=Math.abs(this.distX),s=Math.abs(this.distY),!(c-this.endTime>300&&n<10&&s<10)){if(this.directionLocked||this.options.freeScroll||(n>s+this.options.directionLockThreshold?this.directionLocked="h":s>=n+this.options.directionLockThreshold?this.directionLocked="v":this.directionLocked="n"),"h"==this.directionLocked){if("vertical"==this.options.eventPassthrough)t.preventDefault();else if("horizontal"==this.options.eventPassthrough)return void(this.initiated=!1);l=0}else if("v"==this.directionLocked){if("horizontal"==this.options.eventPassthrough)t.preventDefault();else if("vertical"==this.options.eventPassthrough)return void(this.initiated=!1);r=0}r=this.hasHorizontalScroll?r:0,l=this.hasVerticalScroll?l:0,e=this.x+r,i=this.y+l,(e>0||e0?0:this.maxScrollX),(i>0||i0?0:this.maxScrollY),this.directionX=r>0?-1:r<0?1:0,this.directionY=l>0?-1:l<0?1:0,this.moved||this._execEvent("scrollStart"),this.moved=!0,this._translate(e,i),c-this.startTime>300&&(this.startTime=c,this.startX=this.x,this.startY=this.y)}}},_end:function(t){if(this.enabled&&a.eventType[t.type]===this.initiated){this.options.preventDefault&&!a.preventDefaultException(t.target,this.options.preventDefaultException)&&t.preventDefault();var e,i,n=(t.changedTouches?t.changedTouches[0]:t,a.getTime()-this.startTime),s=Math.round(this.x),o=Math.round(this.y),r=Math.abs(s-this.startX),l=Math.abs(o-this.startY),c=0,u="";if(this.isInTransition=0,this.initiated=0,this.endTime=a.getTime(),!this.resetPosition(this.options.bounceTime))return this.scrollTo(s,o),this.moved?this._events.flick&&n<200&&r<100&&l<100?void this._execEvent("flick"):(this.options.momentum&&n<300&&(e=this.hasHorizontalScroll?a.momentum(this.x,this.startX,n,this.maxScrollX,this.options.bounce?this.wrapperWidth:0,this.options.deceleration):{destination:s,duration:0},i=this.hasVerticalScroll?a.momentum(this.y,this.startY,n,this.maxScrollY,this.options.bounce?this.wrapperHeight:0,this.options.deceleration):{destination:o,duration:0},s=e.destination,o=i.destination,c=Math.max(e.duration,i.duration),this.isInTransition=1),s!=this.x||o!=this.y?((s>0||s0||o0?e=0:this.x0?i=0:this.y-1&&this._events[t].splice(i,1)}},_execEvent:function(t){if(this._events[t]){var e=0,i=this._events[t].length;if(i)for(;e0;var s=this.options.useTransition&&n.style;!i||s?(s&&(this._transitionTimingFunction(n.style),this._transitionTime(i)),this._translate(t,e)):this._animate(t,e,i,n.fn)},scrollToElement:function(t,e,i,n,s){if(t=t.nodeType?t:this.scroller.querySelector(t)){var o=a.offset(t);o.left-=this.wrapperOffset.left,o.top-=this.wrapperOffset.top,i===!0&&(i=Math.round(t.offsetWidth/2-this.wrapper.offsetWidth/2)),n===!0&&(n=Math.round(t.offsetHeight/2-this.wrapper.offsetHeight/2)),o.left-=i||0,o.top-=n||0,o.left=o.left>0?0:o.left0?0:o.top=h?(r.isAnimating=!1,r._translate(t,e),void(r.resetPosition(r.options.bounceTime)||r._execEvent("scrollEnd"))):(f=(f-u)/i,m=n(f),d=(t-l)*m+l,p=(e-c)*m+c,r._translate(d,p),void(r.isAnimating&&o(s)))}var r=this,l=this.x,c=this.y,u=a.getTime(),h=u+i;this.isAnimating=!0,s()},handleEvent:function(t){switch(t.type){case"touchstart":case"pointerdown":case"MSPointerDown":case"mousedown":this._start(t);break;case"touchmove":case"pointermove":case"MSPointerMove":case"mousemove":this._move(t);break;case"touchend":case"pointerup":case"MSPointerUp":case"mouseup":case"touchcancel":case"pointercancel":case"MSPointerCancel":case"mousecancel":this._end(t);break;case"orientationchange":case"resize":this._resize();break;case"transitionend":case"webkitTransitionEnd":case"oTransitionEnd":case"MSTransitionEnd":this._transitionEnd(t);break;case"wheel":case"DOMMouseScroll":case"mousewheel":this._wheel(t);break;case"keydown":this._key(t);break;case"click":this.enabled&&!t._constructed&&(t.preventDefault(),t.stopPropagation())}}},n.utils=a,t.exports=s.iScroll=n},function(t,e,i){"use strict";function n(t,e){return this.each(function(){var i=s(this),n=i.data("amui.modal"),o="object"==typeof t&&t;n||i.data("amui.modal",n=new c(this,o)),"string"==typeof t?n[t]&&n[t](e):n.toggle(t&&t.relatedTarget||void 0)})}var s=i(1),o=i(2),a=i(9),r=s(document),l=o.support.transition,c=function(t,e){this.options=s.extend({},c.DEFAULTS,e||{}),this.$element=s(t),this.$dialog=this.$element.find(".am-modal-dialog"),this.$element.attr("id")||this.$element.attr("id",o.utils.generateGUID("am-modal")),this.isPopup=this.$element.hasClass("am-popup"),this.isActions=this.$element.hasClass("am-modal-actions"),this.isPrompt=this.$element.hasClass("am-modal-prompt"),this.isLoading=this.$element.hasClass("am-modal-loading"),this.active=this.transitioning=this.relatedTarget=null,this.dimmer=this.options.dimmer?a:{open:function(){},close:function(){}},this.events()};c.DEFAULTS={className:{active:"am-modal-active",out:"am-modal-out"},selector:{modal:".am-modal",active:".am-modal-active"},closeViaDimmer:!0,cancelable:!0,onConfirm:function(){},onCancel:function(){},closeOnCancel:!0,closeOnConfirm:!0,dimmer:!0,height:void 0,width:void 0,duration:300,transitionEnd:l&&l.end+".modal.amui"},c.prototype.toggle=function(t){return this.active?this.close():this.open(t)},c.prototype.open=function(t){var e=this.$element,i=this.options,n=this.isPopup,o=i.width,a=i.height,r={};if(!this.active&&this.$element.length){t&&(this.relatedTarget=t),this.transitioning&&(clearTimeout(e.transitionEndTimmer),e.transitionEndTimmer=null,e.trigger(i.transitionEnd).off(i.transitionEnd)),n&&this.$element.show(),this.active=!0,e.trigger(s.Event("open.modal.amui",{relatedTarget:t})),this.dimmer.open(e),e.show().redraw(),n||this.isActions||(o&&(r.width=parseInt(o,10)+"px"),a&&(r.height=parseInt(a,10)+"px"),this.$dialog.css(r)),e.removeClass(i.className.out).addClass(i.className.active),this.transitioning=1;var c=function(){e.trigger(s.Event("opened.modal.amui",{relatedTarget:t})),this.transitioning=0,this.isPrompt&&this.$dialog.find("input").eq(0).focus()};return l?void e.one(i.transitionEnd,s.proxy(c,this)).emulateTransitionEnd(i.duration):c.call(this)}},c.prototype.close=function(t){if(this.active){var e=this.$element,i=this.options,n=this.isPopup;this.transitioning&&(clearTimeout(e.transitionEndTimmer),e.transitionEndTimmer=null,e.trigger(i.transitionEnd).off(i.transitionEnd),this.dimmer.close(e,!0)),this.$element.trigger(s.Event("close.modal.amui",{relatedTarget:t})),this.transitioning=1;var o=function(){e.trigger("closed.modal.amui"),n&&e.removeClass(i.className.out),e.hide(),this.transitioning=0,this.dimmer.close(e,!1),this.active=!1};return e.removeClass(i.className.active).addClass(i.className.out),l?void e.one(i.transitionEnd,s.proxy(o,this)).emulateTransitionEnd(i.duration):o.call(this)}},c.prototype.events=function(){var t=this,e=this.options,i=this.$element,n=this.dimmer.$element,o=i.find(".am-modal-prompt-input"),a=i.find("[data-am-modal-confirm]"),r=i.find("[data-am-modal-cancel]"),l=function(){var t=[];return o.each(function(){t.push(s(this).val())}),0===t.length?void 0:1===t.length?t[0]:t};this.options.cancelable&&i.on("keyup.modal.amui",function(e){t.active&&27===e.which&&(i.trigger("cancel.modal.amui"),t.close())}),this.options.dimmer&&this.options.closeViaDimmer&&!this.isLoading&&n.on("click.dimmer.modal.amui",function(){t.close()}),i.on("click.close.modal.amui","[data-am-modal-close], .am-modal-btn",function(i){i.preventDefault();var n=s(this);n.is(a)?e.closeOnConfirm&&t.close():n.is(r)?e.closeOnCancel&&t.close():t.close()}).on("click",function(t){s(t.target).is(i)&&n.trigger("click.dimmer.modal.amui")}),a.on("click.confirm.modal.amui",function(){i.trigger(s.Event("confirm.modal.amui",{trigger:this}))}),r.on("click.cancel.modal.amui",function(){i.trigger(s.Event("cancel.modal.amui",{trigger:this}))}),i.on("confirm.modal.amui",function(e){e.data=l(),t.options.onConfirm.call(t,e)}).on("cancel.modal.amui",function(e){e.data=l(),t.options.onCancel.call(t,e)})},s.fn.modal=n,r.on("click.modal.amui.data-api","[data-am-modal]",function(){var t=s(this),e=o.utils.parseOptions(t.attr("data-am-modal")),i=s(e.target||this.href&&this.href.replace(/.*(?=#[^\s]+$)/,"")),a=i.data("amui.modal")?"toggle":e;n.call(i,a,this)}),t.exports=o.modal=c},function(t,e,i){"use strict";function n(t,e){var i=Array.prototype.slice.call(arguments,1);return this.each(function(){var n=s(this),o=n.data("amui.offcanvas"),a=s.extend({},"object"==typeof t&&t);o||(n.data("amui.offcanvas",o=new c(this,a)),(!t||"object"==typeof t)&&o.open(e)),"string"==typeof t&&o[t]&&o[t].apply(o,i)})}var s=i(1),o=i(2);i(3);var a,r=s(window),l=s(document),c=function(t,e){this.$element=s(t),this.options=s.extend({},c.DEFAULTS,e),this.active=null,this.bindEvents()};c.DEFAULTS={duration:300,effect:"overlay"},c.prototype.open=function(t){var e=this,i=this.$element;if(i.length&&!i.hasClass("am-active")){var n=this.options.effect,o=s("html"),l=s("body"),c=i.find(".am-offcanvas-bar").first(),u=c.hasClass("am-offcanvas-bar-flip")?-1:1;c.addClass("am-offcanvas-bar-"+n),a={x:window.scrollX,y:window.scrollY},i.addClass("am-active"),l.css({width:window.innerWidth,height:r.height()}).addClass("am-offcanvas-page"),"overlay"!==n&&l.css({"margin-left":c.outerWidth()*u}).width(),o.css("margin-top",a.y*-1),setTimeout(function(){c.addClass("am-offcanvas-bar-active").width()},0),i.trigger("open.offcanvas.amui"),this.active=1,i.on("click.offcanvas.amui",function(t){var i=s(t.target);i.hasClass("am-offcanvas-bar")||i.parents(".am-offcanvas-bar").first().length||(t.stopImmediatePropagation(),e.close())}),o.on("keydown.offcanvas.amui",function(t){27===t.keyCode&&e.close()})}},c.prototype.close=function(t){function e(){r.removeClass("am-offcanvas-page").css({width:"",height:"","margin-left":"","margin-right":""}),l.removeClass("am-active"),c.removeClass("am-offcanvas-bar-active"),n.css("margin-top",""),window.scrollTo(a.x,a.y),l.trigger("closed.offcanvas.amui"),i.active=0}var i=this,n=s("html"),r=s("body"),l=this.$element,c=l.find(".am-offcanvas-bar").first();l.length&&this.active&&l.hasClass("am-active")&&(l.trigger("close.offcanvas.amui"),o.support.transition?(setTimeout(function(){c.removeClass("am-offcanvas-bar-active")},0),r.css("margin-left","").one(o.support.transition.end,function(){e()}).emulateTransitionEnd(this.options.duration)):e(),l.off("click.offcanvas.amui"),n.off(".offcanvas.amui"))},c.prototype.bindEvents=function(){var t=this;return l.on("click.offcanvas.amui",'[data-am-dismiss="offcanvas"]',function(e){e.preventDefault(),t.close()}),r.on("resize.offcanvas.amui orientationchange.offcanvas.amui",function(){t.active&&t.close()}),this.$element.hammer().on("swipeleft swipeleft",function(e){e.preventDefault(),t.close()}),this},s.fn.offCanvas=n,l.on("click.offcanvas.amui","[data-am-offcanvas]",function(t){t.preventDefault();var e=s(this),i=o.utils.parseOptions(e.data("amOffcanvas")),a=s(i.target||this.href&&this.href.replace(/.*(?=#[^\s]+$)/,"")),r=a.data("amui.offcanvas")?"open":i;n.call(a,r,this)}),t.exports=o.offcanvas=c},function(t,e,i){"use strict";var n=i(1),s=i(2),o=s.utils.rAF,a=function(t){var e=function(e,i){this.el=t(e),this.zoomFactor=1,this.lastScale=1,this.offset={x:0,y:0},this.options=t.extend({},this.defaults,i),this.setupMarkup(),this.bindEvents(),this.update(),this.enable()},i=function(t,e){return t+e},n=function(t,e){return t>e-.01&&t1){var e=this.getTouches(t)[0];this.drag(e,this.lastDragPosition),this.offset=this.sanitizeOffset(this.offset),this.lastDragPosition=e}},handleDragEnd:function(){this.el.trigger(this.options.dragEndEventName),this.end()},handleZoomStart:function(t){this.el.trigger(this.options.zoomStartEventName),this.stopAnimation(),this.lastScale=1,this.nthZoom=0,this.lastZoomCenter=!1,this.hasInteraction=!0},handleZoom:function(t,e){var i=this.getTouchCenter(this.getTouches(t)),n=e/this.lastScale;this.lastScale=e,this.nthZoom+=1,this.nthZoom>3&&(this.scale(n,i),this.drag(i,this.lastZoomCenter)),this.lastZoomCenter=i},handleZoomEnd:function(){this.el.trigger(this.options.zoomEndEventName),this.end()},handleDoubleTap:function(t){var e=this.getTouches(t)[0],i=this.zoomFactor>1?1:this.options.tapZoomFactor,n=this.zoomFactor,s=function(t){this.scaleTo(n+t*(i-n),e)}.bind(this);this.hasInteraction||(n>i&&(e=this.getCurrentZoomCenter()),this.animate(this.options.animationDuration,s,this.swing),this.el.trigger(this.options.doubleTapEventName))},sanitizeOffset:function(t){var e=(this.zoomFactor-1)*this.getContainerX(),i=(this.zoomFactor-1)*this.getContainerY(),n=Math.max(e,0),s=Math.max(i,0),o=Math.min(e,0),a=Math.min(i,0);return{x:Math.min(Math.max(t.x,o),n),y:Math.min(Math.max(t.y,a),s)}},scaleTo:function(t,e){this.scale(t/this.zoomFactor,e)},scale:function(t,e){t=this.scaleZoomFactor(t),this.addOffset({x:(t-1)*(e.x+this.offset.x),y:(t-1)*(e.y+this.offset.y)})},scaleZoomFactor:function(t){var e=this.zoomFactor;return this.zoomFactor*=t,this.zoomFactor=Math.min(this.options.maxZoom,Math.max(this.zoomFactor,this.options.minZoom)),this.zoomFactor/e},drag:function(t,e){e&&(this.options.lockDragAxis?Math.abs(t.x-e.x)>Math.abs(t.y-e.y)?this.addOffset({x:-(t.x-e.x),y:0}):this.addOffset({y:-(t.y-e.y),x:0}):this.addOffset({y:-(t.y-e.y),x:-(t.x-e.x)}))},getTouchCenter:function(t){return this.getVectorAvg(t)},getVectorAvg:function(t){return{x:t.map(function(t){return t.x}).reduce(i)/t.length,y:t.map(function(t){return t.y}).reduce(i)/t.length}},addOffset:function(t){this.offset={x:this.offset.x+t.x,y:this.offset.y+t.y}},sanitize:function(){this.zoomFactor=t?(e(1),n&&n(),this.update(),this.stopAnimation(),this.update()):(i&&(l=i(l)),e(l),this.update(),o(a))}}.bind(this);this.inAnimation=!0,o(a)},stopAnimation:function(){this.inAnimation=!1},swing:function(t){return-Math.cos(t*Math.PI)/2+.5},getContainerX:function(){return this.container[0].offsetWidth},getContainerY:function(){return this.container[0].offsetHeight},setContainerY:function(t){return this.container.height(t)},setupMarkup:function(){this.container=t('
    '),this.el.before(this.container),this.container.append(this.el),this.container.css({overflow:"hidden",position:"relative"}),this.el.css({"-webkit-transform-origin":"0% 0%","-moz-transform-origin":"0% 0%","-ms-transform-origin":"0% 0%","-o-transform-origin":"0% 0%","transform-origin":"0% 0%",position:"absolute"})},end:function(){this.hasInteraction=!1,this.sanitize(),this.update()},bindEvents:function(){s(this.container.get(0),this),t(window).on("resize",this.update.bind(this)),t(this.el).find("img").on("load",this.update.bind(this))},update:function(){this.updatePlaned||(this.updatePlaned=!0,setTimeout(function(){this.updatePlaned=!1,this.updateAspectRatio();var t=this.getInitialZoomFactor()*this.zoomFactor,e=-this.offset.x/t,i=-this.offset.y/t,n="scale3d("+t+", "+t+",1) translate3d("+e+"px,"+i+"px,0px)",s="scale("+t+", "+t+") translate("+e+"px,"+i+"px)",o=function(){this.clone&&(this.clone.remove(),delete this.clone)}.bind(this);!this.options.use2d||this.hasInteraction||this.inAnimation?(this.is3d=!0,o(),this.el.css({"-webkit-transform":n,"-o-transform":s,"-ms-transform":s,"-moz-transform":s,transform:n})):(this.is3d&&(this.clone=this.el.clone(),this.clone.css("pointer-events","none"),this.clone.appendTo(this.container),setTimeout(o,200)),this.el.css({"-webkit-transform":s,"-o-transform":s,"-ms-transform":s,"-moz-transform":s,transform:s}),this.is3d=!1)}.bind(this),0))},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1}};var s=function(t,e){var i=null,n=0,s=null,o=null,a=function(t,n){if(i!==t){if(i&&!t)switch(i){case"zoom":e.handleZoomEnd(n);break;case"drag":e.handleDragEnd(n)}switch(t){case"zoom":e.handleZoomStart(n);break;case"drag":e.handleDragStart(n)}}i=t},r=function(t){2===n?a("zoom"):1===n&&e.canDrag()?a("drag",t):a(null,t)},l=function(t){return Array.prototype.slice.call(t).map(function(t){return{x:t.pageX,y:t.pageY}})},c=function(t,e){var i,n;return i=t.x-e.x,n=t.y-e.y,Math.sqrt(i*i+n*n)},u=function(t,e){var i=c(t[0],t[1]),n=c(e[0],e[1]);return n/i},h=function(t){t.stopPropagation(),t.preventDefault()},d=function(t){var o=(new Date).getTime();if(n>1&&(s=null),o-s<300)switch(h(t),e.handleDoubleTap(t),i){case"zoom":e.handleZoomEnd(t);break;case"drag":e.handleDragEnd(t)}1===n&&(s=o)},p=!0;t.addEventListener("touchstart",function(t){e.enabled&&(p=!0,n=t.touches.length,d(t))}),t.addEventListener("touchmove",function(t){if(e.enabled){if(p)r(t),i&&h(t),o=l(t.touches);else{switch(i){case"zoom":e.handleZoom(t,u(o,l(t.touches)));break;case"drag":e.handleDrag(t)}i&&(h(t),e.update())}p=!1}}),t.addEventListener("touchend",function(t){e.enabled&&(n=t.touches.length,r(t))})};return e};t.exports=s.pichzoom=a(n)},function(t,e,i){"use strict";var n=i(1),s=i(2),o=n(window),a=function(t,e){this.options=n.extend({},a.DEFAULTS,e),this.$element=n(t),this.active=null,this.$popover=this.options.target&&n(this.options.target)||null,this.init(),this._bindEvents()};a.DEFAULTS={theme:null,trigger:"click",content:"",open:!1,target:null,tpl:'
    '},a.prototype.init=function(){function t(){i.sizePopover()}var e,i=this,o=this.$element;this.options.target||(this.$popover=this.getPopover(),this.setContent()),e=this.$popover,e.appendTo(n("body")),this.sizePopover(),o.on("open.popover.amui",function(){n(window).on("resize.popover.amui",s.utils.debounce(t,50))}),o.on("close.popover.amui",function(){n(window).off("resize.popover.amui",t)}),this.options.open&&this.open()},a.prototype.sizePopover=function(){var t=this.$element,e=this.$popover;if(e&&e.length){var i=e.outerWidth(),n=e.outerHeight(),s=e.find(".am-popover-caret"),a=s.outerWidth()/2||8,r=n+8,l=t.outerWidth(),c=t.outerHeight(),u=t.offset(),h=t[0].getBoundingClientRect(),d=o.height(),p=o.width(),m=0,f=0,v=0,g=2,y="top";e.css({left:"",top:""}).removeClass("am-popover-left am-popover-right am-popover-top am-popover-bottom"),r-gp&&(f=p-i-20),"top"===y&&e.addClass("am-popover-top"),"bottom"===y&&e.addClass("am-popover-bottom"),v-=f):"middle"===y&&(f=u.left-i-a,e.addClass("am-popover-left"),f<5&&(f=u.left+l+a,e.removeClass("am-popover-left").addClass("am-popover-right")),f+i>p&&(f=p-i-5,e.removeClass("am-popover-left").addClass("am-popover-right"))),e.css({top:m+"px",left:f+"px"})}},a.prototype.toggle=function(){return this[this.active?"close":"open"]()},a.prototype.open=function(){var t=this.$popover;this.$element.trigger("open.popover.amui"),this.sizePopover(),t.show().addClass("am-active"),this.active=!0},a.prototype.close=function(){var t=this.$popover;this.$element.trigger("close.popover.amui"),t.removeClass("am-active").trigger("closed.popover.amui").hide(),this.active=!1},a.prototype.getPopover=function(){var t=s.utils.generateGUID("am-popover"),e=[];return this.options.theme&&n.each(this.options.theme.split(" "),function(t,i){e.push("am-popover-"+n.trim(i))}),n(this.options.tpl).attr("id",t).addClass(e.join(" "))},a.prototype.setContent=function(t){t=t||this.options.content,this.$popover&&this.$popover.find(".am-popover-inner").empty().html(t)},a.prototype._bindEvents=function(){for(var t="popover.amui",e=this.options.trigger.split(" "),i=e.length;i--;){var s=e[i];if("click"===s)this.$element.on("click."+t,n.proxy(this.toggle,this));else{var o="hover"==s?"mouseenter":"focusin",a="hover"==s?"mouseleave":"focusout";this.$element.on(o+"."+t,n.proxy(this.open,this)),this.$element.on(a+"."+t,n.proxy(this.close,this))}}},a.prototype.destroy=function(){this.$element.off(".popover.amui").removeData("amui.popover"),this.$popover.remove()},s.plugin("popover",a),s.ready(function(t){n("[data-am-popover]",t).popover()}),t.exports=a},function(t,e,i){"use strict";var n=i(2),s=function(){function t(t,e,i){return ti?i:t}function e(t){return 100*(-1+t)}function i(t,i,n){var s;return s="translate3d"===c.positionUsing?{transform:"translate3d("+e(t)+"%,0,0)"}:"translate"===c.positionUsing?{transform:"translate("+e(t)+"%,0)"}:{"margin-left":e(t)+"%"},s.transition="all "+i+"ms "+n,s}function n(t,e){var i="string"==typeof t?t:a(t);return i.indexOf(" "+e+" ")>=0}function s(t,e){var i=a(t),s=i+e;n(i,e)||(t.className=s.substring(1))}function o(t,e){var i,s=a(t);n(t,e)&&(i=s.replace(" "+e+" "," "),t.className=i.substring(1,i.length-1))}function a(t){return(" "+(t.className||"")+" ").replace(/\s+/gi," ")}function r(t){t&&t.parentNode&&t.parentNode.removeChild(t)}var l={};l.version="0.2.0";var c=l.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,parent:"body",barSelector:'[role="nprogress-bar"]',spinnerSelector:'[role="nprogress-spinner"]',template:'
    '};l.configure=function(t){var e,i;for(e in t)i=t[e],void 0!==i&&t.hasOwnProperty(e)&&(c[e]=i);return this},l.status=null,l.set=function(e){var n=l.isStarted();e=t(e,c.minimum,1),l.status=1===e?null:e;var s=l.render(!n),o=s.querySelector(c.barSelector),a=c.speed,r=c.easing;return s.offsetWidth,u(function(t){""===c.positionUsing&&(c.positionUsing=l.getPositioningCSS()),h(o,i(e,a,r)),1===e?(h(s,{transition:"none",opacity:1}),s.offsetWidth,setTimeout(function(){h(s,{transition:"all "+a+"ms linear",opacity:0}),setTimeout(function(){l.remove(),t()},a)},a)):setTimeout(t,a)}),this},l.isStarted=function(){return"number"==typeof l.status},l.start=function(){l.status||l.set(0);var t=function(){setTimeout(function(){l.status&&(l.trickle(),t())},c.trickleSpeed)};return c.trickle&&t(),this},l.done=function(t){return t||l.status?l.inc(.3+.5*Math.random()).set(1):this},l.inc=function(e){var i=l.status;return i?("number"!=typeof e&&(e=(1-i)*t(Math.random()*i,.1,.95)),i=t(i+e,0,.994),l.set(i)):l.start()},l.trickle=function(){return l.inc(Math.random()*c.trickleRate)},function(){var t=0,e=0;l.promise=function(i){return i&&"resolved"!==i.state()?(0===e&&l.start(),t++,e++,i.always(function(){e--,0===e?(t=0,l.done()):l.set((t-e)/t)}),this):this}}(),l.render=function(t){if(l.isRendered())return document.getElementById("nprogress");s(document.documentElement,"nprogress-busy");var i=document.createElement("div");i.id="nprogress",i.innerHTML=c.template;var n,o=i.querySelector(c.barSelector),a=t?"-100":e(l.status||0),u=document.querySelector(c.parent);return h(o,{transition:"all 0 linear",transform:"translate3d("+a+"%,0,0)"}),c.showSpinner||(n=i.querySelector(c.spinnerSelector),n&&r(n)),u!=document.body&&s(u,"nprogress-custom-parent"),u.appendChild(i),i},l.remove=function(){o(document.documentElement,"nprogress-busy"),o(document.querySelector(c.parent),"nprogress-custom-parent");var t=document.getElementById("nprogress");t&&r(t)},l.isRendered=function(){return!!document.getElementById("nprogress")},l.getPositioningCSS=function(){var t=document.body.style,e="WebkitTransform"in t?"Webkit":"MozTransform"in t?"Moz":"msTransform"in t?"ms":"OTransform"in t?"O":"";return e+"Perspective"in t?"translate3d":e+"Transform"in t?"translate":"margin"};var u=function(){function t(){var i=e.shift();i&&i(t)}var e=[];return function(i){e.push(i),1==e.length&&t()}}(),h=function(){function t(t){return t.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(t,e){return e.toUpperCase()})}function e(t){var e=document.body.style;if(t in e)return t;for(var i,n=s.length,o=t.charAt(0).toUpperCase()+t.slice(1);n--;)if(i=s[n]+o,i in e)return i;return t}function i(i){return i=t(i),o[i]||(o[i]=e(i))}function n(t,e,n){e=i(e),t.style[e]=n}var s=["Webkit","O","Moz","ms"],o={};return function(t,e){var i,s,o=arguments;if(2==o.length)for(i in e)s=e[i],void 0!==s&&e.hasOwnProperty(i)&&n(t,i,s);else n(t,o[1],o[2])}}();return l}();t.exports=n.progress=s},function(t,e,i){"use strict";var n=i(1),s=i(2),o=i(17),a=i(3),r=s.support.animation,l=s.support.transition,c=function(t,e){this.$element=n(t),this.$body=n(document.body),this.options=n.extend({},c.DEFAULTS,e),this.$pureview=n(this.options.tpl).attr("id",s.utils.generateGUID("am-pureview")),this.$slides=null,this.transitioning=null,this.scrollbarWidth=0,this.init()};c.DEFAULTS={tpl:'
        /
        ',className:{prevSlide:"am-pureview-slide-prev",nextSlide:"am-pureview-slide-next",onlyOne:"am-pureview-only",active:"am-active",barActive:"am-pureview-bar-active",activeBody:"am-pureview-active"},selector:{slider:".am-pureview-slider",close:'[data-am-close="pureview"]',total:".am-pureview-total",current:".am-pureview-current",title:".am-pureview-title",actions:".am-pureview-actions", +bar:".am-pureview-bar",pinchZoom:".am-pinch-zoom",nav:".am-pureview-nav"},shareBtn:!1,toggleToolbar:!0,target:"img",weChatImagePreview:!0},c.prototype.init=function(){var t=this,e=this.options,i=this.$element,s=this.$pureview;this.refreshSlides(),n("body").append(s),this.$title=s.find(e.selector.title),this.$current=s.find(e.selector.current),this.$bar=s.find(e.selector.bar),this.$actions=s.find(e.selector.actions),e.shareBtn&&this.$actions.append(''),this.$element.on("click.pureview.amui",e.target,function(i){i.preventDefault();var n=t.$images.index(this);e.weChatImagePreview&&window.WeixinJSBridge?window.WeixinJSBridge.invoke("imagePreview",{current:t.imgUrls[n],urls:t.imgUrls}):t.open(n)}),s.find(".am-pureview-direction").on("click.direction.pureview.amui","li",function(e){e.preventDefault(),n(this).is(".am-pureview-prev")?t.prevSlide():t.nextSlide()}),s.find(e.selector.nav).on("click.nav.pureview.amui","li",function(){var e=t.$navItems.index(n(this));t.activate(t.$slides.eq(e))}),s.find(e.selector.close).on("click.close.pureview.amui",function(e){e.preventDefault(),t.close()}),this.$slider.hammer().on("swipeleft.pureview.amui",function(e){e.preventDefault(),t.nextSlide()}).on("swiperight.pureview.amui",function(e){e.preventDefault(),t.prevSlide()}).on("press.pureview.amui",function(i){i.preventDefault(),e.toggleToolbar&&t.toggleToolBar()}),this.$slider.data("hammer").get("swipe").set({direction:a.DIRECTION_HORIZONTAL,velocity:.35}),i.DOMObserve({childList:!0,subtree:!0},function(t,e){}),i.on("changed.dom.amui",function(e){e.stopPropagation(),t.refreshSlides()}),n(document).on("keydown.pureview.amui",n.proxy(function(t){var e=t.keyCode;37==e?this.prevSlide():39==e?this.nextSlide():27==e&&this.close()},this))},c.prototype.refreshSlides=function(){this.$images=this.$element.find(this.options.target);var t=this,e=this.options,i=this.$pureview,o=n([]),a=n([]),r=this.$images,l=r.length;this.$slider=i.find(e.selector.slider),this.$nav=i.find(e.selector.nav);var c="data-am-pureviewed";this.imgUrls=this.imgUrls||[],l&&(1===l&&i.addClass(e.className.onlyOne),r.not("["+c+"]").each(function(e,i){var r,l;"A"===i.nodeName?(r=i.href,l=i.title||""):(r=n(i).data("rel")||i.src,r=s.utils.getAbsoluteUrl(r),l=n(i).attr("alt")||""),i.setAttribute(c,"1"),t.imgUrls.push(r),o=o.add(n('
      1. ')),a=a.add(n("
      2. "+(e+1)+"
      3. "))}),i.find(e.selector.total).text(l),this.$slider.append(o),this.$nav.append(a),this.$navItems=this.$nav.find("li"),this.$slides=this.$slider.find("li"))},c.prototype.loadImage=function(t,e){var i="image-appended";if(!t.data(i)){var s=n("",{src:t.data("src"),alt:t.data("title")});t.html(s).wrapInner('
        ').redraw();var a=t.find(this.options.selector.pinchZoom);a.data("amui.pinchzoom",new o(a[0],{})),t.data("image-appended",!0)}e&&e.call(this)},c.prototype.activate=function(t){var e=this.options,i=this.$slides,o=i.index(t),a=t.data("title")||"",r=e.className.active;i.find("."+r).is(t)||this.transitioning||(this.loadImage(t,function(){s.utils.imageLoader(t.find("img"),function(e){t.find(".am-pinch-zoom").addClass("am-pureview-loaded"),n(e).addClass("am-img-loaded")})}),this.transitioning=1,this.$title.text(a),this.$current.text(o+1),i.removeClass(),t.addClass(r),i.eq(o-1).addClass(e.className.prevSlide),i.eq(o+1).addClass(e.className.nextSlide),this.$navItems.removeClass().eq(o).addClass(e.className.active),l?t.one(l.end,n.proxy(function(){this.transitioning=0},this)).emulateTransitionEnd(300):this.transitioning=0)},c.prototype.nextSlide=function(){if(1!==this.$slides.length){var t=this.$slides,e=t.filter(".am-active"),i=t.index(e),n="am-animation-right-spring";i+1>=t.length?r&&e.addClass(n).on(r.end,function(){e.removeClass(n)}):this.activate(t.eq(i+1))}},c.prototype.prevSlide=function(){if(1!==this.$slides.length){var t=this.$slides,e=t.filter(".am-active"),i=this.$slides.index(e),n="am-animation-left-spring";0===i?r&&e.addClass(n).on(r.end,function(){e.removeClass(n)}):this.activate(t.eq(i-1))}},c.prototype.toggleToolBar=function(){this.$pureview.toggleClass(this.options.className.barActive)},c.prototype.open=function(t){var e=t||0;this.checkScrollbar(),this.setScrollbar(),this.activate(this.$slides.eq(e)),this.$pureview.show().redraw().addClass(this.options.className.active),this.$body.addClass(this.options.className.activeBody)},c.prototype.close=function(){function t(){this.$pureview.hide(),this.$body.removeClass(e.className.activeBody),this.resetScrollbar()}var e=this.options;this.$pureview.removeClass(e.className.active),this.$slides.removeClass(),l?this.$pureview.one(l.end,n.proxy(t,this)).emulateTransitionEnd(300):t.call(this)},c.prototype.checkScrollbar=function(){this.scrollbarWidth=s.utils.measureScrollbar()},c.prototype.setScrollbar=function(){var t=parseInt(this.$body.css("padding-right")||0,10);this.scrollbarWidth&&this.$body.css("padding-right",t+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},s.plugin("pureview",c),s.ready(function(t){n("[data-am-pureview]",t).pureview()}),t.exports=c},function(t,e,i){"use strict";var n=i(1),s=i(2),o=function(t,e){if(s.support.animation){this.options=n.extend({},o.DEFAULTS,e),this.$element=n(t);var i=function(){s.utils.rAF.call(window,n.proxy(this.checkView,this))}.bind(this);this.$window=n(window).on("scroll.scrollspy.amui",i).on("resize.scrollspy.amui orientationchange.scrollspy.amui",s.utils.debounce(i,50)),this.timer=this.inViewState=this.initInView=null,i()}};o.DEFAULTS={animation:"fade",className:{inView:"am-scrollspy-inview",init:"am-scrollspy-init"},repeat:!0,delay:0,topOffset:0,leftOffset:0},o.prototype.checkView=function(){var t=this.$element,e=this.options,i=s.utils.isInView(t,e),n=e.animation?" am-animation-"+e.animation:"";i&&!this.inViewState&&(this.timer&&clearTimeout(this.timer),this.initInView||(t.addClass(e.className.init),this.offset=t.offset(),this.initInView=!0,t.trigger("init.scrollspy.amui")),this.timer=setTimeout(function(){i&&t.addClass(e.className.inView+n).width()},e.delay),this.inViewState=!0,t.trigger("inview.scrollspy.amui")),!i&&this.inViewState&&e.repeat&&(t.removeClass(e.className.inView+n),this.inViewState=!1,t.trigger("outview.scrollspy.amui"))},o.prototype.check=function(){s.utils.rAF.call(window,n.proxy(this.checkView,this))},s.plugin("scrollspy",o),s.ready(function(t){n("[data-am-scrollspy]",t).scrollspy()}),t.exports=o},function(t,e,i){"use strict";var n=i(1),s=i(2);i(23);var o=function(t,e){this.options=n.extend({},o.DEFAULTS,e),this.$element=n(t),this.anchors=[],this.$links=this.$element.find('a[href^="#"]').each(function(t,e){this.anchors.push(n(e).attr("href"))}.bind(this)),this.$targets=n(this.anchors.join(", "));var i=function(){s.utils.rAF.call(window,n.proxy(this.process,this))}.bind(this);this.$window=n(window).on("scroll.scrollspynav.amui",i).on("resize.scrollspynav.amui orientationchange.scrollspynav.amui",s.utils.debounce(i,50)),i(),this.scrollProcess()};o.DEFAULTS={className:{active:"am-active"},closest:!1,smooth:!0,offsetTop:0},o.prototype.process=function(){var t=this.$window.scrollTop(),e=this.options,i=[],o=this.$links,a=this.$targets;if(a.each(function(t,n){s.utils.isInView(n,e)&&i.push(n)}),i.length){var r;if(n.each(i,function(e,i){if(n(i).offset().top>=t)return r=n(i),!1}),!r)return;e.closest?(o.closest(e.closest).removeClass(e.className.active),o.filter('a[href="#'+r.attr("id")+'"]').closest(e.closest).addClass(e.className.active)):o.removeClass(e.className.active).filter('a[href="#'+r.attr("id")+'"]').addClass(e.className.active)}},o.prototype.scrollProcess=function(){var t=this.$links,e=this.options;e.smooth&&n.fn.smoothScroll&&t.on("click",function(t){t.preventDefault();var i=n(this),s=n(i.attr("href"));if(s){var o=e.offsetTop&&!isNaN(parseInt(e.offsetTop))&&parseInt(e.offsetTop)||0;n(window).smoothScroll({position:s.offset().top-o})}})},s.plugin("scrollspynav",o),s.ready(function(t){n("[data-am-scrollspynav]",t).scrollspynav()}),t.exports=o},function(t,e,i){"use strict";var n=i(1),s=i(2),o=s.utils.rAF,a=s.utils.cancelAF,r=!1,l=function(t,e){function i(t){return(t/=.5)<1?.5*Math.pow(t,5):.5*(Math.pow(t-2,5)+2)}function s(){p.off("touchstart.smoothscroll.amui",w),r=!1}function c(t){r&&(u||(u=t),h=Math.min(1,Math.max((t-u)/y,0)),d=Math.round(f+g*i(h)),g>0&&d>m&&(d=m),g<0&&d=0};var o=function(t,e){this.$element=n(t),this.options=n.extend({},o.DEFAULTS,{placeholder:t.getAttribute("placeholder")||o.DEFAULTS.placeholder},e),this.$originalOptions=this.$element.find("option"),this.multiple=t.multiple,this.$selector=null,this.initialized=!1,this.init()};o.DEFAULTS={btnWidth:null,btnSize:null,btnStyle:"default",dropUp:0,maxHeight:null,maxChecked:null,placeholder:"\u70b9\u51fb\u9009\u62e9...",selectedClass:"am-checked",disabledClass:"am-disabled",searchBox:!1,tpl:'

        \u8fd4\u56de

        <% if (searchBox) { %> <% } %>
          <% for (var i = 0; i < options.length; i++) { %> <% var option = options[i] %> <% if (option.header) { %>
        • <%= option.text %>
        • <% } else { %>
        • <%= option.text %>
        • <% } %> <% } %>
        ',listTpl:'<% for (var i = 0; i < options.length; i++) { %> <% var option = options[i] %> <% if (option.header) { %>
      4. <%= option.text %>
      5. <% } else { %>
      6. <%= option.text %>
      7. <% } %> <% } %>'},o.prototype.init=function(){var t=this,e=this.$element,i=this.options;e.hide();var o={id:s.utils.generateGUID("am-selected"),multiple:this.multiple,options:[],searchBox:i.searchBox,dropUp:i.dropUp,placeholder:i.placeholder};this.$selector=n(s.template(this.options.tpl,o)),this.$selector.css({width:this.options.btnWidth}),this.$list=this.$selector.find(".am-selected-list"),this.$searchField=this.$selector.find(".am-selected-search input"),this.$hint=this.$selector.find(".am-selected-hint");var a=this.$selector.find(".am-selected-btn"),r=[];i.btnSize&&r.push("am-btn-"+i.btnSize),i.btnStyle&&r.push("am-btn-"+i.btnStyle),a.addClass(r.join(" ")),this.$selector.dropdown({justify:a}),e[0].disabled&&this.disable(),i.maxHeight&&this.$selector.find(".am-selected-list").css({"max-height":i.maxHeight,"overflow-y":"scroll"});var l=[],c=e.attr("minchecked"),u=e.attr("maxchecked")||i.maxChecked;this.maxChecked=u||1/0,e[0].required&&l.push("\u5fc5\u9009"),(c||u)&&(c&&l.push("\u81f3\u5c11\u9009\u62e9 "+c+" \u9879"),u&&l.push("\u81f3\u591a\u9009\u62e9 "+u+" \u9879")),this.$hint.text(l.join("\uff0c")),this.renderOptions(),this.$element.after(this.$selector),this.dropdown=this.$selector.data("amui.dropdown"),this.$status=this.$selector.find(".am-selected-status"),setTimeout(function(){t.syncData(),t.initialized=!0},0),this.bindEvents()},o.prototype.renderOptions=function(){function t(t,e,s){if(""===e.value)return!0;var o="";e.disabled&&(o+=i.disabledClass),!e.disabled&&e.selected&&(o+=i.selectedClass),n.push({group:s,index:t,classNames:o,text:e.text,value:e.value})}var e=this.$element,i=this.options,n=[],o=e.find("optgroup");this.$originalOptions=this.$element.find("option"),this.multiple||null!==e.val()||this.$originalOptions.length&&(this.$originalOptions.get(0).selected=!0),o.length?o.each(function(e){n.push({header:!0,group:e+1,text:this.label}),o.eq(e).find("option").each(function(i,n){t(i,n,e)})}):this.$originalOptions.each(function(e,i){t(e,i,null)}),this.$list.html(s.template(i.listTpl,{options:n})),this.$shadowOptions=this.$list.find("> li").not(".am-selected-list-header")},o.prototype.setChecked=function(t){var e=this.options,i=n(t),s=i.hasClass(e.selectedClass);if(this.multiple){var o=this.$list.find("."+e.selectedClass).length;if(!s&&this.maxChecked<=o)return this.$element.trigger("checkedOverflow.selected.amui",{selected:this}),!1}else{if(this.dropdown.close(),s)return!1;this.$shadowOptions.not(i).removeClass(e.selectedClass)}i.toggleClass(e.selectedClass),this.syncData(t)},o.prototype.syncData=function(t){var e=this,i=this.options,s=[],o=n([]);if(this.$shadowOptions.filter("."+i.selectedClass).each(function(){var i=n(this);s.push(i.find(".am-selected-text").text()),t||(o=o.add(e.$originalOptions.filter('[value="'+i.data("value")+'"]').prop("selected",!0)))}),t){var a=n(t);this.$originalOptions.filter('[value="'+a.data("value")+'"]').prop("selected",a.hasClass(i.selectedClass))}else this.$originalOptions.not(o).prop("selected",!1);this.$element.val()||(s=[i.placeholder]),this.$status.text(s.join(", ")),this.initialized&&this.$element.trigger("change")},o.prototype.bindEvents=function(){var t=this,e="am-selected-list-header",i=s.utils.debounce(function(i){t.$shadowOptions.not("."+e).hide().filter(':containsNC("'+i.target.value+'")').show()},100);this.$list.on("click","> li",function(i){var s=n(this);!s.hasClass(t.options.disabledClass)&&!s.hasClass(e)&&t.setChecked(this)}),this.$searchField.on("keyup.selected.amui",i),this.$selector.on("closed.dropdown.amui",function(){t.$searchField.val(""),t.$shadowOptions.css({display:""})}),this.$element.on("validated.field.validator.amui",function(e){if(e.validity){var i=e.validity.valid,n="am-invalid";t.$selector[(i?"remove":"add")+"Class"](n)}}),s.support.mutationobserver&&(this.observer=new s.support.mutationobserver(function(){t.$element.trigger("changed.selected.amui")}),this.observer.observe(this.$element[0],{childList:!0,subtree:!0,characterData:!0})),this.$element.on("changed.selected.amui",function(){t.renderOptions(),t.syncData()})},o.prototype.select=function(t){var e;e="number"==typeof t?this.$list.find("> li").not(".am-selected-list-header").eq(t):"string"==typeof t?this.$list.find(t):n(t),e.trigger("click")},o.prototype.enable=function(){this.$element.prop("disable",!1),this.$selector.dropdown("enable")},o.prototype.disable=function(){this.$element.prop("disable",!0),this.$selector.dropdown("disable")},o.prototype.destroy=function(){this.$element.removeData("amui.selected").show(),this.$selector.remove()},s.plugin("selected",o),s.ready(function(t){n("[data-am-selected]",t).selected()}),t.exports=o},function(t,e,i){"use strict";i(15);var n=i(1),s=i(2),o=i(26),a=document,r=n(a),l=function(t){this.options=n.extend({},l.DEFAULTS,t||{}),this.$element=null,this.$wechatQr=null,this.pics=null,this.inited=!1,this.active=!1};l.DEFAULTS={sns:["weibo","qq","qzone","tqq","wechat","renren"],title:"\u5206\u4eab\u5230",cancel:"\u53d6\u6d88",closeOnShare:!0,id:s.utils.generateGUID("am-share"),desc:"Hi\uff0c\u5b64\u591c\u89c2\u5929\u8c61\uff0c\u53d1\u73b0\u4e00\u4e2a\u4e0d\u9519\u7684\u897f\u897f\uff0c\u5206\u4eab\u4e00\u4e0b\u4e0b ;-)",via:"Amaze UI",tpl:''},l.SNS={weibo:{title:"\u65b0\u6d6a\u5fae\u535a",url:"http://service.weibo.com/share/share.php",width:620,height:450,icon:"weibo"},qq:{title:"QQ \u597d\u53cb",url:"http://connect.qq.com/widget/shareqq/index.html",icon:"qq"},qzone:{title:"QQ \u7a7a\u95f4",url:"http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey",icon:"star"},tqq:{title:"\u817e\u8baf\u5fae\u535a",url:"http://v.t.qq.com/share/share.php",icon:"tencent-weibo"},wechat:{title:"\u5fae\u4fe1",url:"[qrcode]",icon:"wechat"},renren:{title:"\u4eba\u4eba\u7f51",url:"http://widget.renren.com/dialog/share",icon:"renren"},douban:{title:"\u8c46\u74e3",url:"http://www.douban.com/recommend/",icon:"share-alt"},mail:{title:"\u90ae\u4ef6\u5206\u4eab",url:"mailto:",icon:"envelope-o"},sms:{title:"\u77ed\u4fe1\u5206\u4eab",url:"sms:",icon:"comment"}},l.prototype.render=function(){var t=this.options,e=[],i=encodeURIComponent(a.title),o=encodeURIComponent(a.location),r="?body="+i+o;return t.sns.forEach(function(n,s){if(l.SNS[n]){var a,c=l.SNS[n];c.id=n,a="mail"===n?r+"&subject="+t.desc:"sms"===n?r:"?url="+o+"&title="+i,c.shareUrl=c.url+a,e.push(c)}}),s.template(t.tpl,n.extend({},t,{sns:e}))},l.prototype.init=function(){if(!this.inited){var t=this,e="[data-am-share-to]";r.ready(n.proxy(function(){n("body").append(this.render()),this.$element=n("#"+this.options.id),this.$element.find("[data-am-share-close]").on("click.share.amui",function(){t.close()})},this)),r.on("click.share.amui",e,n.proxy(function(t){var i=n(t.target),s=i.is(e)&&i||i.parent(e),o=s.attr("data-am-share-to");"mail"!==o&&"sms"!==o&&(t.preventDefault(),this.shareTo(o,this.setData(o))),this.close()},this)),this.inited=!0}},l.prototype.open=function(){!this.inited&&this.init(),this.$element&&this.$element.modal("open"),this.$element.trigger("open.share.amui"),this.active=!0},l.prototype.close=function(){this.$element&&this.$element.modal("close"),this.$element.trigger("close.share.amui"),this.active=!1},l.prototype.toggle=function(){this.active?this.close():this.open()},l.prototype.setData=function(t){if(t){var e={url:a.location,title:a.title},i=this.options.desc,n=this.pics||[],s=/^(qzone|qq|tqq)$/;if(s.test(t)&&!n.length){for(var o=a.images,r=0;r